Merge pull request #6131 from magefree/game_freezes_and_lost_priorities

Server stability and freezes improves
This commit is contained in:
Oleg Agafonov 2019-12-31 11:48:29 +01:00 committed by GitHub
commit 3b1433989a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 491 additions and 337 deletions

View file

@ -26,6 +26,7 @@ import mage.client.plugins.impl.Plugins;
import mage.client.preference.MagePreferences; import mage.client.preference.MagePreferences;
import mage.client.remote.CallbackClientImpl; import mage.client.remote.CallbackClientImpl;
import mage.client.table.TablesPane; import mage.client.table.TablesPane;
import mage.client.table.TablesPanel;
import mage.client.tournament.TournamentPane; import mage.client.tournament.TournamentPane;
import mage.client.util.*; import mage.client.util.*;
import mage.client.util.audio.MusicPlayer; import mage.client.util.audio.MusicPlayer;
@ -261,7 +262,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
desktopPane.add(errorDialog, JLayeredPane.MODAL_LAYER); desktopPane.add(errorDialog, JLayeredPane.MODAL_LAYER);
UI.addComponent(MageComponents.DESKTOP_PANE, desktopPane); UI.addComponent(MageComponents.DESKTOP_PANE, desktopPane);
PING_TASK_EXECUTOR.scheduleAtFixedRate(() -> SessionHandler.ping(), 60, 60, TimeUnit.SECONDS); PING_TASK_EXECUTOR.scheduleAtFixedRate(() -> SessionHandler.ping(), TablesPanel.PING_SERVER_SECS, TablesPanel.PING_SERVER_SECS, TimeUnit.SECONDS);
updateMemUsageTask = new UpdateMemUsageTask(jMemUsageLabel); updateMemUsageTask = new UpdateMemUsageTask(jMemUsageLabel);

View file

@ -18,7 +18,6 @@ import mage.interfaces.callback.CallbackClient;
import mage.interfaces.callback.ClientCallback; import mage.interfaces.callback.ClientCallback;
import mage.remote.ActionData; import mage.remote.ActionData;
import mage.remote.Session; import mage.remote.Session;
import mage.utils.CompressUtil;
import mage.view.*; import mage.view.*;
import mage.view.ChatMessage.MessageType; import mage.view.ChatMessage.MessageType;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
@ -44,8 +43,8 @@ public class CallbackClientImpl implements CallbackClient {
@Override @Override
public synchronized void processCallback(final ClientCallback callback) { public synchronized void processCallback(final ClientCallback callback) {
callback.decompressData();
SaveObjectUtil.saveObject(callback.getData(), callback.getMethod().toString()); SaveObjectUtil.saveObject(callback.getData(), callback.getMethod().toString());
callback.setData(CompressUtil.decompress(callback.getData()));
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
try { try {
logger.debug(callback.getMessageId() + " -- " + callback.getMethod()); logger.debug(callback.getMessageId() + " -- " + callback.getMethod());

View file

@ -64,6 +64,9 @@ public class TablesPanel extends javax.swing.JPanel {
private static final Logger LOGGER = Logger.getLogger(TablesPanel.class); private static final Logger LOGGER = Logger.getLogger(TablesPanel.class);
private static final int[] DEFAULT_COLUMNS_WIDTH = {35, 150, 100, 50, 120, 180, 80, 120, 80, 60, 40, 40, 60}; private static final int[] DEFAULT_COLUMNS_WIDTH = {35, 150, 100, 50, 120, 180, 80, 120, 80, 60, 40, 40, 60};
// ping timeout (warning, must be less than SERVER_TIMEOUTS_USER_INFORM_OPPONENTS_ABOUT_DISCONNECT_AFTER_SECS)
public static final int PING_SERVER_SECS = 20;
// refresh timeouts for data downloads from server // refresh timeouts for data downloads from server
public static final int REFRESH_ACTIVE_TABLES_SECS = 5; public static final int REFRESH_ACTIVE_TABLES_SECS = 5;
public static final int REFRESH_FINISHED_TABLES_SECS = 30; public static final int REFRESH_FINISHED_TABLES_SECS = 30;

View file

@ -1,12 +1,12 @@
package mage.interfaces.callback; package mage.interfaces.callback;
import mage.remote.traffic.ZippedObject;
import mage.utils.CompressUtil;
import java.io.Serializable; import java.io.Serializable;
import java.util.UUID; import java.util.UUID;
/** /**
*
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
*/ */
public class ClientCallback implements Serializable { public class ClientCallback implements Serializable {
@ -16,12 +16,14 @@ public class ClientCallback implements Serializable {
private ClientCallbackMethod method; private ClientCallbackMethod method;
private int messageId; private int messageId;
public ClientCallback() {}
public ClientCallback(ClientCallbackMethod method, UUID objectId, Object data) { public ClientCallback(ClientCallbackMethod method, UUID objectId, Object data) {
this(method, objectId, data, true);
}
public ClientCallback(ClientCallbackMethod method, UUID objectId, Object data, boolean useCompress) {
this.method = method; this.method = method;
this.objectId = objectId; this.objectId = objectId;
this.data = data; this.setData(data, useCompress);
} }
public ClientCallback(ClientCallbackMethod method, UUID objectId) { public ClientCallback(ClientCallbackMethod method, UUID objectId) {
@ -42,13 +44,28 @@ public class ClientCallback implements Serializable {
} }
public Object getData() { public Object getData() {
if (this.data instanceof ZippedObject) {
throw new IllegalStateException("Client data must be decompressed first");
}
return data; return data;
} }
public void setData(Object data) { public void setData(Object data, boolean useCompress) {
if (!useCompress || data == null || data instanceof ZippedObject) {
this.data = data;
} else {
this.data = CompressUtil.compress(data);
}
this.data = data; this.data = data;
} }
public void decompressData() {
if (this.data instanceof ZippedObject) {
this.data = CompressUtil.decompress(this.data);
}
}
public ClientCallbackMethod getMethod() { public ClientCallbackMethod getMethod() {
return method; return method;
} }

View file

@ -14,7 +14,7 @@ public final class CompressUtil {
* Defines should data be compressed or not. True by default. Read from * Defines should data be compressed or not. True by default. Read from
* system property: * system property:
*/ */
private static boolean compressData = true; private static boolean compressData;
/** /**
* Defines the system property name to disable any compressing. * Defines the system property name to disable any compressing.

View file

@ -67,7 +67,7 @@ public class ConsoleFrame extends javax.swing.JFrame implements MageClient {
logger.fatal("", ex); logger.fatal("", ex);
} }
pingTaskExecutor.scheduleAtFixedRate(() -> session.ping(), 60, 60, TimeUnit.SECONDS); pingTaskExecutor.scheduleAtFixedRate(() -> session.ping(), 20, 20, TimeUnit.SECONDS);
} }
public boolean connect(Connection connection) { public boolean connect(Connection connection) {

View file

@ -5,6 +5,7 @@ import mage.cards.repository.CardRepository;
import mage.server.exceptions.UserNotFoundException; import mage.server.exceptions.UserNotFoundException;
import mage.server.game.GameController; import mage.server.game.GameController;
import mage.server.game.GameManager; import mage.server.game.GameManager;
import mage.server.game.GamesRoomManager;
import mage.server.util.SystemUtil; import mage.server.util.SystemUtil;
import mage.view.ChatMessage.MessageColor; import mage.view.ChatMessage.MessageColor;
import mage.view.ChatMessage.MessageType; import mage.view.ChatMessage.MessageType;
@ -316,12 +317,16 @@ public enum ChatManager {
} }
public void sendLostConnectionMessage(UUID userId, DisconnectReason reason) { public void sendLostConnectionMessage(UUID userId, DisconnectReason reason) {
UserManager.instance.getUser(userId).ifPresent(user -> sendMessageToUserChats(userId, user.getName() + " " + reason.getMessage()));
}
public void sendMessageToUserChats(UUID userId, String message) {
UserManager.instance.getUser(userId).ifPresent(user UserManager.instance.getUser(userId).ifPresent(user
-> getChatSessions() -> getChatSessions()
.stream() .stream()
.filter(chat -> !chat.getChatId().equals(GamesRoomManager.instance.getMainChatId())) // ignore main lobby
.filter(chat -> chat.hasUser(userId)) .filter(chat -> chat.hasUser(userId))
.forEach(chatSession -> chatSession.broadcast(null, user.getName() + reason.getMessage(), MessageColor.BLUE, true, MessageType.STATUS, null))); .forEach(chatSession -> chatSession.broadcast(null, message, MessageColor.BLUE, true, MessageType.STATUS, null)));
} }
public void removeUser(UUID userId, DisconnectReason reason) { public void removeUser(UUID userId, DisconnectReason reason) {

View file

@ -22,6 +22,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
public enum UserManager { public enum UserManager {
instance; instance;
private static final int SERVER_TIMEOUTS_USER_INFORM_OPPONENTS_ABOUT_DISCONNECT_AFTER_SECS = 30; // send to chat info about disconnection troubles, must be more than ping timeout
private static final int SERVER_TIMEOUTS_USER_DISCONNECT_FROM_SERVER_AFTER_SECS = 3 * 60; // removes from all games and chats too (can be seen in users list with disconnected status) private static final int SERVER_TIMEOUTS_USER_DISCONNECT_FROM_SERVER_AFTER_SECS = 3 * 60; // removes from all games and chats too (can be seen in users list with disconnected status)
private static final int SERVER_TIMEOUTS_USER_REMOVE_FROM_SERVER_AFTER_SECS = 8 * 60; // removes from users list private static final int SERVER_TIMEOUTS_USER_REMOVE_FROM_SERVER_AFTER_SECS = 8 * 60; // removes from users list
@ -145,6 +146,22 @@ public enum UserManager {
} }
} }
public void informUserOpponents(final UUID userId, final String message) {
if (userId != null) {
getUser(userId).ifPresent(user
-> USER_EXECUTOR.execute(
() -> {
try {
logger.info("INFORM OPPONENTS by " + user.getName() + ": " + message);
ChatManager.instance.sendMessageToUserChats(user.getId(), message);
} catch (Exception ex) {
handleException(ex);
}
}
));
}
}
public boolean extendUserSession(UUID userId, String pingInfo) { public boolean extendUserSession(UUID userId, String pingInfo) {
if (userId != null) { if (userId != null) {
User user = users.get(userId); User user = users.get(userId);
@ -163,6 +180,8 @@ public enum UserManager {
*/ */
private void checkExpired() { private void checkExpired() {
try { try {
Calendar calendarInform = Calendar.getInstance();
calendarInform.add(Calendar.SECOND, -1 * SERVER_TIMEOUTS_USER_INFORM_OPPONENTS_ABOUT_DISCONNECT_AFTER_SECS);
Calendar calendarExp = Calendar.getInstance(); Calendar calendarExp = Calendar.getInstance();
calendarExp.add(Calendar.SECOND, -1 * SERVER_TIMEOUTS_USER_DISCONNECT_FROM_SERVER_AFTER_SECS); calendarExp.add(Calendar.SECOND, -1 * SERVER_TIMEOUTS_USER_DISCONNECT_FROM_SERVER_AFTER_SECS);
Calendar calendarRemove = Calendar.getInstance(); Calendar calendarRemove = Calendar.getInstance();
@ -179,6 +198,12 @@ public enum UserManager {
} }
for (User user : userList) { for (User user : userList) {
try { try {
if (user.getUserState() != UserState.Offline
&& user.isExpired(calendarInform.getTime())) {
long secsInfo = (Calendar.getInstance().getTimeInMillis() - user.getLastActivity().getTime()) / 1000;
informUserOpponents(user.getId(), user.getName() + " got connection problem for " + secsInfo + " secs");
}
if (user.getUserState() == UserState.Offline) { if (user.getUserState() == UserState.Offline) {
if (user.isExpired(calendarRemove.getTime())) { if (user.isExpired(calendarRemove.getTime())) {
// removes from users list // removes from users list

View file

@ -50,7 +50,7 @@ import java.util.zip.GZIPOutputStream;
public class GameController implements GameCallback { public class GameController implements GameCallback {
private static final int GAME_TIMEOUTS_CHECK_JOINING_STATUS_EVERY_SECS = 15; // checks and inform players about joining status private static final int GAME_TIMEOUTS_CHECK_JOINING_STATUS_EVERY_SECS = 15; // checks and inform players about joining status
private static final int GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS = 4 * 60; // leave player from game if it don't join and inactive on server private static final int GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS = 2 * 60; // leave player from game if it don't join and inactive on server
private static final ExecutorService gameExecutor = ThreadExecutor.instance.getGameExecutor(); private static final ExecutorService gameExecutor = ThreadExecutor.instance.getGameExecutor();
private static final Logger logger = Logger.getLogger(GameController.class); private static final Logger logger = Logger.getLogger(GameController.class);
@ -336,7 +336,10 @@ public class GameController implements GameCallback {
GameManager.instance.joinGame(game.getId(), user.getId()); GameManager.instance.joinGame(game.getId(), user.getId());
logger.debug("Player " + player.getName() + " (disconnected) has joined gameId: " + game.getId()); logger.debug("Player " + player.getName() + " (disconnected) has joined gameId: " + game.getId());
} }
ChatManager.instance.broadcast(chatId, player.getName(), user.getPingInfo() + " is pending to join the game", MessageColor.BLUE, true, ChatMessage.MessageType.STATUS, null); ChatManager.instance.broadcast(chatId, player.getName(), user.getPingInfo()
+ " is pending to join the game (waiting " + user.getSecondsDisconnected() + " of "
+ GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS + " secs)",
MessageColor.BLUE, true, ChatMessage.MessageType.STATUS, null);
if (user.getSecondsDisconnected() > GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS) { if (user.getSecondsDisconnected() > GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS) {
// TODO: 2019.04.22 - if user playing another game on server but not joining (that's the reason?), then that's check will never trigger // TODO: 2019.04.22 - if user playing another game on server but not joining (that's the reason?), then that's check will never trigger
// Cancel player join possibility lately after 4 minutes // Cancel player join possibility lately after 4 minutes
@ -1201,6 +1204,7 @@ public class GameController implements GameCallback {
if (activePlayer != null && activePlayer.hasLeft()) { if (activePlayer != null && activePlayer.hasLeft()) {
sb.append("<br>Found disconnected player! Concede..."); sb.append("<br>Found disconnected player! Concede...");
activePlayer.concede(game); activePlayer.concede(game);
activePlayer.leave(); // abort any wait response actions
Phase currentPhase = game.getPhase(); Phase currentPhase = game.getPhase();
if (currentPhase != null) { if (currentPhase != null) {
@ -1221,6 +1225,7 @@ public class GameController implements GameCallback {
Player p = game.getPlayer(state.getChoosingPlayerId()); Player p = game.getPlayer(state.getChoosingPlayerId());
if (p != null) { if (p != null) {
p.concede(game); p.concede(game);
p.leave(); // abort any wait response actions
} }
Phase currentPhase = game.getPhase(); Phase currentPhase = game.getPhase();
if (currentPhase != null && !fixedAlready) { if (currentPhase != null && !fixedAlready) {
@ -1235,14 +1240,14 @@ public class GameController implements GameCallback {
} }
// fix lost priority // fix lost priority
Player p = game.getPlayer(state.getPriorityPlayerId());
sb.append("<br>Checking priority player: " + getName(game.getPlayer(state.getPriorityPlayerId()))); sb.append("<br>Checking priority player: " + getName(game.getPlayer(state.getPriorityPlayerId())));
if (state.getPriorityPlayerId() != null) { if (p != null) {
if (game.getPlayer(state.getPriorityPlayerId()).hasLeft()) { if (p.hasLeft()) {
sb.append("<br>Found disconnected player! Concede..."); sb.append("<br>Found disconnected player! Concede...");
Player p = game.getPlayer(state.getPriorityPlayerId()); p.concede(game);
if (p != null) { p.leave(); // abort any wait response actions
p.concede(game);
}
Phase currentPhase = game.getPhase(); Phase currentPhase = game.getPhase();
if (currentPhase != null && !fixedAlready) { if (currentPhase != null && !fixedAlready) {
currentPhase.getStep().skipStep(game, state.getActivePlayerId()); currentPhase.getStep().skipStep(game, state.getActivePlayerId());
@ -1250,7 +1255,6 @@ public class GameController implements GameCallback {
sb.append("<br>Forcibly passing the phase!"); sb.append("<br>Forcibly passing the phase!");
} }
} }
sb.append(game.getPlayer(state.getPriorityPlayerId()).getName());
sb.append("</font>"); sb.append("</font>");
} }
@ -1272,6 +1276,8 @@ public class GameController implements GameCallback {
sb.append("Not using future Timeout!"); sb.append("Not using future Timeout!");
} }
sb.append("</font>"); sb.append("</font>");
return sb.toString(); return sb.toString();
} }
} }

View file

@ -1,5 +1,3 @@
package mage.server.game; package mage.server.game;
import mage.game.Game; import mage.game.Game;
@ -101,7 +99,6 @@ public class GameSessionWatcher {
GameView gameView = new GameView(game.getState(), game, null, userId); GameView gameView = new GameView(game.getState(), game, null, userId);
processWatchedHands(userId, gameView); processWatchedHands(userId, gameView);
return gameView; return gameView;
} }
protected void processWatchedHands(UUID userId, GameView gameView) { protected void processWatchedHands(UUID userId, GameView gameView) {

View file

@ -1,5 +1,3 @@
package mage.server.game; package mage.server.game;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
@ -16,12 +14,14 @@ public enum GamesRoomManager {
private final ConcurrentHashMap<UUID, GamesRoom> rooms = new ConcurrentHashMap<>(); private final ConcurrentHashMap<UUID, GamesRoom> rooms = new ConcurrentHashMap<>();
private final UUID mainRoomId; private final UUID mainRoomId;
private final UUID mainChatId;
private static final Logger logger = Logger.getLogger(GamesRoomManager.class); private static final Logger logger = Logger.getLogger(GamesRoomManager.class);
GamesRoomManager() { GamesRoomManager() {
GamesRoom mainRoom = new GamesRoomImpl(); GamesRoom mainRoom = new GamesRoomImpl();
mainRoomId = mainRoom.getRoomId(); mainRoomId = mainRoom.getRoomId();
mainChatId = mainRoom.getChatId();
rooms.put(mainRoomId, mainRoom); rooms.put(mainRoomId, mainRoom);
} }
@ -35,8 +35,12 @@ public enum GamesRoomManager {
return mainRoomId; return mainRoomId;
} }
public UUID getMainChatId() {
return mainChatId;
}
public Optional<GamesRoom> getRoom(UUID roomId) { public Optional<GamesRoom> getRoom(UUID roomId) {
if(rooms.containsKey(roomId)) { if (rooms.containsKey(roomId)) {
return Optional.of(rooms.get(roomId)); return Optional.of(rooms.get(roomId));
} }
logger.error("room not found : " + roomId); logger.error("room not found : " + roomId);

View file

@ -1,5 +1,6 @@
package mage.cards.d; package mage.cards.d;
import mage.abilities.Ability;
import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.CardImpl; import mage.cards.CardImpl;
@ -60,10 +61,10 @@ class DeathbellowWarCryTarget extends TargetCardInLibrary {
} }
@Override @Override
public boolean canTarget(UUID id, Cards cards, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Cards cards, Game game) {
Card card = cards.get(id, game); Card card = cards.get(id, game);
return card != null return card != null
&& filter.match(card, game) && filter.match(card, playerId, game)
&& this && this
.getTargets() .getTargets()
.stream() .stream()

View file

@ -1,7 +1,5 @@
package mage.cards.g; package mage.cards.g;
import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.cards.*; import mage.cards.*;
@ -15,14 +13,15 @@ import mage.target.TargetCard;
import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInLibrary;
import mage.target.common.TargetOpponent; import mage.target.common.TargetOpponent;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class GiftsUngiven extends CardImpl { public final class GiftsUngiven extends CardImpl {
public GiftsUngiven(UUID ownerId, CardSetInfo setInfo) { public GiftsUngiven(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{3}{U}"); super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{U}");
// Search your library for up to four cards with different names and reveal them. Target opponent chooses two of those cards. Put the chosen cards into your graveyard and the rest into your hand. Then shuffle your library. // Search your library for up to four cards with different names and reveal them. Target opponent chooses two of those cards. Put the chosen cards into your graveyard and the rest into your hand. Then shuffle your library.
this.getSpellAbility().addEffect(new GiftsUngivenEffect()); this.getSpellAbility().addEffect(new GiftsUngivenEffect());
@ -115,7 +114,7 @@ class GiftsUngivenTarget extends TargetCardInLibrary {
} }
@Override @Override
public boolean canTarget(UUID id, Cards cards, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Cards cards, Game game) {
Card card = cards.get(id, game); Card card = cards.get(id, game);
if (card != null) { if (card != null) {
for (UUID targetId : this.getTargets()) { for (UUID targetId : this.getTargets()) {
@ -124,7 +123,7 @@ class GiftsUngivenTarget extends TargetCardInLibrary {
return false; return false;
} }
} }
return filter.match(card, game); return filter.match(card, playerId, game);
} }
return false; return false;
} }

View file

@ -88,8 +88,8 @@ class TargetCardInLibrarySharingLandType extends TargetCardInLibrary {
} }
@Override @Override
public boolean canTarget(UUID id, Cards cards, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Cards cards, Game game) {
if (super.canTarget(id, cards, game)) { if (super.canTarget(playerId, id, source, cards, game)) {
if (!getTargets().isEmpty()) { if (!getTargets().isEmpty()) {
// check if new target shares a Land Type // check if new target shares a Land Type
SubTypeList landTypes = new SubTypeList(); SubTypeList landTypes = new SubTypeList();

View file

@ -1,15 +1,8 @@
package mage.cards.n; package mage.cards.n;
import java.util.HashMap;
import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.cards.Card; import mage.cards.*;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone; import mage.constants.Zone;
@ -21,8 +14,10 @@ import mage.players.Player;
import mage.target.TargetCard; import mage.target.TargetCard;
import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInLibrary;
import java.util.HashMap;
import java.util.UUID;
/** /**
*
* @author spjspj * @author spjspj
*/ */
public final class NissasEncouragement extends CardImpl { public final class NissasEncouragement extends CardImpl {
@ -89,8 +84,8 @@ class NissasEncouragementEffect extends OneShotEffect {
foundCards.put("Brambleweft Behemoth", 0); foundCards.put("Brambleweft Behemoth", 0);
foundCards.put("Nissa, Genesis Mage", 0); foundCards.put("Nissa, Genesis Mage", 0);
Cards cards = new CardsImpl(); Cards cards = new CardsImpl();
if (!target.getTargets().isEmpty()) { if (!target.getTargets().isEmpty()) {
for (UUID cardId : target.getTargets()) { for (UUID cardId : target.getTargets()) {
Card card = player.getLibrary().remove(cardId, game); Card card = player.getLibrary().remove(cardId, game);
@ -120,7 +115,7 @@ class NissasEncouragementEffect extends OneShotEffect {
} }
} }
} }
if (!cards.isEmpty()) { if (!cards.isEmpty()) {
player.revealCards(sourceCard.getIdName(), cards, game); player.revealCards(sourceCard.getIdName(), cards, game);
player.moveCards(cards, Zone.HAND, source, game); player.moveCards(cards, Zone.HAND, source, game);
@ -149,7 +144,7 @@ class NissasEncouragementTarget extends TargetCardInLibrary {
} }
@Override @Override
public boolean canTarget(UUID id, Cards cards, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Cards cards, Game game) {
Card card = cards.get(id, game); Card card = cards.get(id, game);
if (card != null) { if (card != null) {
for (UUID targetId : this.getTargets()) { for (UUID targetId : this.getTargets()) {
@ -158,7 +153,7 @@ class NissasEncouragementTarget extends TargetCardInLibrary {
return false; return false;
} }
} }
return filter.match(card, game); return filter.match(card, playerId, game);
} }
return false; return false;
} }

View file

@ -1,31 +1,35 @@
package mage.cards.o; package mage.cards.o;
import java.util.UUID;
import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect;
import mage.cards.Card;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.cards.Cards;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.filter.FilterCard; import mage.filter.FilterCard;
import mage.filter.predicate.Predicates; import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.SubtypePredicate; import mage.filter.predicate.mageobject.SubtypePredicate;
import mage.game.Game;
import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInLibrary;
import java.util.UUID;
/** /**
*
* @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com)
*/ */
public final class OpenTheArmory extends CardImpl { public final class OpenTheArmory extends CardImpl {
private static final FilterCard auraOrEquipmentTarget = new FilterCard("Aura or Equipment card");
static {
auraOrEquipmentTarget.add(Predicates.or(
new SubtypePredicate(SubType.EQUIPMENT),
new SubtypePredicate(SubType.AURA)));
}
public OpenTheArmory(UUID ownerId, CardSetInfo setInfo) { public OpenTheArmory(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{1}{W}"); super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{W}");
// Search your library for an Aura or Equipment card, reveal it, and put it into your hand. Then shuffle your library. // Search your library for an Aura or Equipment card, reveal it, and put it into your hand. Then shuffle your library.
this.getSpellAbility().addEffect(new SearchLibraryPutInHandEffect(new OpenTheArmoryTarget(), true, true)); this.getSpellAbility().addEffect(new SearchLibraryPutInHandEffect(new TargetCardInLibrary(1, 1, auraOrEquipmentTarget), true, true));
} }
public OpenTheArmory(final OpenTheArmory card) { public OpenTheArmory(final OpenTheArmory card) {
@ -36,36 +40,4 @@ public final class OpenTheArmory extends CardImpl {
public OpenTheArmory copy() { public OpenTheArmory copy() {
return new OpenTheArmory(this); return new OpenTheArmory(this);
} }
} }
class OpenTheArmoryTarget extends TargetCardInLibrary {
private static final FilterCard auraOrEquipmentTarget = new FilterCard("Aura or Equipment card");
static {
auraOrEquipmentTarget.add(Predicates.or(
new SubtypePredicate(SubType.EQUIPMENT),
new SubtypePredicate(SubType.AURA)));
}
public OpenTheArmoryTarget() {
super(1, 1, auraOrEquipmentTarget.copy());
}
public OpenTheArmoryTarget(final OpenTheArmoryTarget target) {
super(target);
}
@Override
public OpenTheArmoryTarget copy() {
return new OpenTheArmoryTarget(this);
}
@Override
public boolean canTarget(UUID id, Cards cards, Game game) {
Card card = cards.get(id, game);
if (card != null) {
return auraOrEquipmentTarget.match(card, game);
}
return false;
}
}

View file

@ -1,16 +1,9 @@
package mage.cards.r; package mage.cards.r;
import java.util.Set;
import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.cards.Card; import mage.cards.*;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone; import mage.constants.Zone;
@ -23,14 +16,16 @@ import mage.target.TargetCard;
import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInLibrary;
import mage.target.common.TargetOpponent; import mage.target.common.TargetOpponent;
import java.util.Set;
import java.util.UUID;
/** /**
*
* @author North * @author North
*/ */
public final class RealmsUncharted extends CardImpl { public final class RealmsUncharted extends CardImpl {
public RealmsUncharted(UUID ownerId, CardSetInfo setInfo) { public RealmsUncharted(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{2}{G}"); super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}");
// Search your library for four land cards with different names and reveal them. An opponent chooses two of those cards. Put the chosen cards into your graveyard and the rest into your hand. Then shuffle your library. // Search your library for four land cards with different names and reveal them. An opponent chooses two of those cards. Put the chosen cards into your graveyard and the rest into your hand. Then shuffle your library.
this.getSpellAbility().addEffect(new RealmsUnchartedEffect()); this.getSpellAbility().addEffect(new RealmsUnchartedEffect());
@ -128,7 +123,7 @@ class RealmsUnchartedTarget extends TargetCardInLibrary {
} }
@Override @Override
public boolean canTarget(UUID id, Cards cards, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Cards cards, Game game) {
Card card = cards.get(id, game); Card card = cards.get(id, game);
if (card != null) { if (card != null) {
for (UUID targetId : this.getTargets()) { for (UUID targetId : this.getTargets()) {
@ -137,7 +132,7 @@ class RealmsUnchartedTarget extends TargetCardInLibrary {
return false; return false;
} }
} }
return filter.match(card, game); return filter.match(card, playerId, game);
} }
return false; return false;
} }

View file

@ -1,7 +1,5 @@
package mage.cards.s; package mage.cards.s;
import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.DelayedTriggeredAbility; import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.LoyaltyAbility; import mage.abilities.LoyaltyAbility;
@ -9,20 +7,16 @@ import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenCopyTargetEffect;
import mage.abilities.effects.common.DamagePlayersEffect; import mage.abilities.effects.common.DamagePlayersEffect;
import mage.abilities.effects.common.ExileTargetEffect; import mage.abilities.effects.common.ExileTargetEffect;
import mage.abilities.effects.common.CreateTokenCopyTargetEffect;
import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect;
import mage.abilities.effects.keyword.ScryEffect; import mage.abilities.effects.keyword.ScryEffect;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.cards.Cards; import mage.cards.Cards;
import mage.constants.CardType; import mage.constants.*;
import mage.constants.SubType;
import mage.constants.Outcome;
import mage.constants.SuperType;
import mage.constants.TargetController;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.filter.common.FilterArtifactCard; import mage.filter.common.FilterArtifactCard;
import mage.game.Game; import mage.game.Game;
@ -31,14 +25,15 @@ import mage.target.common.TargetCardInLibrary;
import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetControlledPermanent;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
/** /**
*
* @author emerald000 * @author emerald000
*/ */
public final class SaheeliRai extends CardImpl { public final class SaheeliRai extends CardImpl {
public SaheeliRai(UUID ownerId, CardSetInfo setInfo) { public SaheeliRai(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.PLANESWALKER},"{1}{U}{R}"); super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{1}{U}{R}");
this.addSuperType(SuperType.LEGENDARY); this.addSuperType(SuperType.LEGENDARY);
this.subtype.add(SubType.SAHEELI); this.subtype.add(SubType.SAHEELI);
@ -121,7 +116,7 @@ class SaheeliRaiTarget extends TargetCardInLibrary {
} }
@Override @Override
public boolean canTarget(UUID id, Cards cards, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Cards cards, Game game) {
Card card = cards.get(id, game); Card card = cards.get(id, game);
if (card != null) { if (card != null) {
for (UUID targetId : this.getTargets()) { for (UUID targetId : this.getTargets()) {
@ -130,7 +125,7 @@ class SaheeliRaiTarget extends TargetCardInLibrary {
return false; return false;
} }
} }
return filter.match(card, game); return filter.match(card, playerId, game);
} }
return false; return false;
} }

View file

@ -1,5 +1,6 @@
package mage.cards.s; package mage.cards.s;
import mage.abilities.Ability;
import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.CardImpl; import mage.cards.CardImpl;
@ -55,15 +56,20 @@ class SharedSummonsTarget extends TargetCardInLibrary {
} }
@Override @Override
public boolean canTarget(UUID id, Cards cards, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Cards cards, Game game) {
Card card = cards.get(id, game); Card card = cards.get(id, game);
if (card == null || !card.isCreature()) { if (card == null || !card.isCreature()) {
return false; return false;
} }
return !this
if (!filter.match(card, playerId, game)) {
return false;
}
return this
.getTargets() .getTargets()
.stream() .stream()
.map(uuid -> game.getCard(uuid)) .map(game::getCard)
.anyMatch(c -> c != null && c.getName().equals(card.getName())); .noneMatch(c -> c != null && c.getName().equals(card.getName()));
} }
} }

View file

@ -1,7 +1,6 @@
package mage.cards.t; package mage.cards.t;
import java.util.UUID; import mage.abilities.Ability;
import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.CardImpl; import mage.cards.CardImpl;
@ -14,14 +13,15 @@ import mage.filter.predicate.mageobject.SubtypePredicate;
import mage.game.Game; import mage.game.Game;
import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInLibrary;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class ThreeDreams extends CardImpl { public final class ThreeDreams extends CardImpl {
public ThreeDreams(UUID ownerId, CardSetInfo setInfo) { public ThreeDreams(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{4}{W}"); super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{W}");
// Search your library for up to three Aura cards with different names, reveal them, and put them into your hand. Then shuffle your library. // Search your library for up to three Aura cards with different names, reveal them, and put them into your hand. Then shuffle your library.
@ -41,6 +41,7 @@ public final class ThreeDreams extends CardImpl {
class ThreeDreamsTarget extends TargetCardInLibrary { class ThreeDreamsTarget extends TargetCardInLibrary {
private static final FilterCard aurafilter = new FilterCard("Aura cards with different names"); private static final FilterCard aurafilter = new FilterCard("Aura cards with different names");
static { static {
aurafilter.add(new SubtypePredicate(SubType.AURA)); aurafilter.add(new SubtypePredicate(SubType.AURA));
} }
@ -59,7 +60,7 @@ class ThreeDreamsTarget extends TargetCardInLibrary {
} }
@Override @Override
public boolean canTarget(UUID id, Cards cards, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Cards cards, Game game) {
Card card = cards.get(id, game); Card card = cards.get(id, game);
if (card != null) { if (card != null) {
// check if card with that name was selected before // check if card with that name was selected before
@ -69,7 +70,7 @@ class ThreeDreamsTarget extends TargetCardInLibrary {
return false; return false;
} }
} }
return filter.match(card, game); return filter.match(card, playerId, game);
} }
return false; return false;
} }

View file

@ -1,14 +1,8 @@
package mage.cards.u; package mage.cards.u;
import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.cards.Card; import mage.cards.*;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone; import mage.constants.Zone;
@ -19,8 +13,9 @@ import mage.game.Game;
import mage.players.Player; import mage.players.Player;
import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInLibrary;
import java.util.UUID;
/** /**
*
* @author spjspj * @author spjspj
*/ */
public final class UncageTheMenagerie extends CardImpl { public final class UncageTheMenagerie extends CardImpl {
@ -115,7 +110,7 @@ class UncageTheMenagerieTarget extends TargetCardInLibrary {
} }
@Override @Override
public boolean canTarget(UUID id, Cards cards, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Cards cards, Game game) {
Card card = cards.get(id, game); Card card = cards.get(id, game);
if (card != null) { if (card != null) {
for (UUID targetId : this.getTargets()) { for (UUID targetId : this.getTargets()) {
@ -124,11 +119,12 @@ class UncageTheMenagerieTarget extends TargetCardInLibrary {
return false; return false;
} }
} }
if (!(card.isCreature() && card.getConvertedManaCost() == xValue)) { if (!(card.isCreature() && card.getConvertedManaCost() == xValue)) {
return false; return false;
} }
return filter.match(card, game); return filter.match(card, playerId, game);
} }
return false; return false;
} }

View file

@ -4,7 +4,6 @@ import mage.constants.PlayerAction;
import mage.interfaces.callback.CallbackClient; import mage.interfaces.callback.CallbackClient;
import mage.interfaces.callback.ClientCallback; import mage.interfaces.callback.ClientCallback;
import mage.remote.Session; import mage.remote.Session;
import mage.utils.CompressUtil;
import mage.view.*; import mage.view.*;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
@ -36,9 +35,18 @@ public class LoadCallbackClient implements CallbackClient {
@Override @Override
public void processCallback(ClientCallback callback) { public void processCallback(ClientCallback callback) {
callback.decompressData();
controlCount = 0; controlCount = 0;
callback.setData(CompressUtil.decompress(callback.getData()));
log.info(getLogStartInfo() + "callback: " + callback.getMethod()); // ignore bloaded logs
switch (callback.getMethod()) {
case CHATMESSAGE:
case GAME_INFORM:
case GAME_UPDATE:
break;
default:
log.info(getLogStartInfo() + "callback: " + callback.getMethod());
}
switch (callback.getMethod()) { switch (callback.getMethod()) {
@ -69,7 +77,7 @@ public class LoadCallbackClient implements CallbackClient {
case GAME_INFORM_PERSONAL: { case GAME_INFORM_PERSONAL: {
GameClientMessage message = (GameClientMessage) callback.getData(); GameClientMessage message = (GameClientMessage) callback.getData();
gameView = message.getGameView(); gameView = message.getGameView();
log.info(getLogStartInfo() + "Inform: " + message.getMessage()); //log.info(getLogStartInfo() + "Inform: " + message.getMessage());
break; break;
} }

View file

@ -208,8 +208,8 @@ public class TargetCard extends TargetObject {
&& getFilter() != null && getFilter().match(card, playerId, game); && getFilter() != null && getFilter().match(card, playerId, game);
} }
public boolean canTarget(UUID id, Cards cards, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Cards cards, Game game) {
return cards.contains(id) && canTarget(id, game); return cards.contains(id) && canTarget(playerId, id, source, game);
} }
@Override @Override