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.remote.CallbackClientImpl;
import mage.client.table.TablesPane;
import mage.client.table.TablesPanel;
import mage.client.tournament.TournamentPane;
import mage.client.util.*;
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);
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);

View file

@ -18,7 +18,6 @@ import mage.interfaces.callback.CallbackClient;
import mage.interfaces.callback.ClientCallback;
import mage.remote.ActionData;
import mage.remote.Session;
import mage.utils.CompressUtil;
import mage.view.*;
import mage.view.ChatMessage.MessageType;
import org.apache.log4j.Logger;
@ -44,8 +43,8 @@ public class CallbackClientImpl implements CallbackClient {
@Override
public synchronized void processCallback(final ClientCallback callback) {
callback.decompressData();
SaveObjectUtil.saveObject(callback.getData(), callback.getMethod().toString());
callback.setData(CompressUtil.decompress(callback.getData()));
SwingUtilities.invokeLater(() -> {
try {
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 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
public static final int REFRESH_ACTIVE_TABLES_SECS = 5;
public static final int REFRESH_FINISHED_TABLES_SECS = 30;

View file

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

View file

@ -14,7 +14,7 @@ public final class CompressUtil {
* Defines should data be compressed or not. True by default. Read from
* system property:
*/
private static boolean compressData = true;
private static boolean compressData;
/**
* 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);
}
pingTaskExecutor.scheduleAtFixedRate(() -> session.ping(), 60, 60, TimeUnit.SECONDS);
pingTaskExecutor.scheduleAtFixedRate(() -> session.ping(), 20, 20, TimeUnit.SECONDS);
}
public boolean connect(Connection connection) {

View file

@ -5,6 +5,7 @@ import mage.cards.repository.CardRepository;
import mage.server.exceptions.UserNotFoundException;
import mage.server.game.GameController;
import mage.server.game.GameManager;
import mage.server.game.GamesRoomManager;
import mage.server.util.SystemUtil;
import mage.view.ChatMessage.MessageColor;
import mage.view.ChatMessage.MessageType;
@ -316,12 +317,16 @@ public enum ChatManager {
}
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
-> getChatSessions()
.stream()
.filter(chat -> !chat.getChatId().equals(GamesRoomManager.instance.getMainChatId())) // ignore main lobby
.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) {

View file

@ -22,6 +22,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
public enum UserManager {
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_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) {
if (userId != null) {
User user = users.get(userId);
@ -163,6 +180,8 @@ public enum UserManager {
*/
private void checkExpired() {
try {
Calendar calendarInform = Calendar.getInstance();
calendarInform.add(Calendar.SECOND, -1 * SERVER_TIMEOUTS_USER_INFORM_OPPONENTS_ABOUT_DISCONNECT_AFTER_SECS);
Calendar calendarExp = Calendar.getInstance();
calendarExp.add(Calendar.SECOND, -1 * SERVER_TIMEOUTS_USER_DISCONNECT_FROM_SERVER_AFTER_SECS);
Calendar calendarRemove = Calendar.getInstance();
@ -179,6 +198,12 @@ public enum UserManager {
}
for (User user : userList) {
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.isExpired(calendarRemove.getTime())) {
// removes from users list

View file

@ -50,7 +50,7 @@ import java.util.zip.GZIPOutputStream;
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_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 Logger logger = Logger.getLogger(GameController.class);
@ -336,7 +336,10 @@ public class GameController implements GameCallback {
GameManager.instance.joinGame(game.getId(), user.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) {
// 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
@ -1201,6 +1204,7 @@ public class GameController implements GameCallback {
if (activePlayer != null && activePlayer.hasLeft()) {
sb.append("<br>Found disconnected player! Concede...");
activePlayer.concede(game);
activePlayer.leave(); // abort any wait response actions
Phase currentPhase = game.getPhase();
if (currentPhase != null) {
@ -1221,6 +1225,7 @@ public class GameController implements GameCallback {
Player p = game.getPlayer(state.getChoosingPlayerId());
if (p != null) {
p.concede(game);
p.leave(); // abort any wait response actions
}
Phase currentPhase = game.getPhase();
if (currentPhase != null && !fixedAlready) {
@ -1235,14 +1240,14 @@ public class GameController implements GameCallback {
}
// fix lost priority
Player p = game.getPlayer(state.getPriorityPlayerId());
sb.append("<br>Checking priority player: " + getName(game.getPlayer(state.getPriorityPlayerId())));
if (state.getPriorityPlayerId() != null) {
if (game.getPlayer(state.getPriorityPlayerId()).hasLeft()) {
if (p != null) {
if (p.hasLeft()) {
sb.append("<br>Found disconnected player! Concede...");
Player p = game.getPlayer(state.getPriorityPlayerId());
if (p != null) {
p.concede(game);
}
p.concede(game);
p.leave(); // abort any wait response actions
Phase currentPhase = game.getPhase();
if (currentPhase != null && !fixedAlready) {
currentPhase.getStep().skipStep(game, state.getActivePlayerId());
@ -1250,7 +1255,6 @@ public class GameController implements GameCallback {
sb.append("<br>Forcibly passing the phase!");
}
}
sb.append(game.getPlayer(state.getPriorityPlayerId()).getName());
sb.append("</font>");
}
@ -1272,6 +1276,8 @@ public class GameController implements GameCallback {
sb.append("Not using future Timeout!");
}
sb.append("</font>");
return sb.toString();
}
}

View file

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

View file

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

View file

@ -1,5 +1,6 @@
package mage.cards.d;
import mage.abilities.Ability;
import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
@ -60,10 +61,10 @@ class DeathbellowWarCryTarget extends TargetCardInLibrary {
}
@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);
return card != null
&& filter.match(card, game)
&& filter.match(card, playerId, game)
&& this
.getTargets()
.stream()

View file

@ -1,7 +1,5 @@
package mage.cards.g;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.cards.*;
@ -15,14 +13,15 @@ import mage.target.TargetCard;
import mage.target.common.TargetCardInLibrary;
import mage.target.common.TargetOpponent;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class GiftsUngiven extends CardImpl {
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.
this.getSpellAbility().addEffect(new GiftsUngivenEffect());
@ -115,7 +114,7 @@ class GiftsUngivenTarget extends TargetCardInLibrary {
}
@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);
if (card != null) {
for (UUID targetId : this.getTargets()) {
@ -124,7 +123,7 @@ class GiftsUngivenTarget extends TargetCardInLibrary {
return false;
}
}
return filter.match(card, game);
return filter.match(card, playerId, game);
}
return false;
}

View file

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

View file

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

View file

@ -1,31 +1,35 @@
package mage.cards.o;
import java.util.UUID;
import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.Cards;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.FilterCard;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.SubtypePredicate;
import mage.game.Game;
import mage.target.common.TargetCardInLibrary;
import java.util.UUID;
/**
*
* @author escplan9 (Derek Monturo - dmontur1 at gmail dot com)
*/
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) {
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.
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) {
@ -36,36 +40,4 @@ public final class OpenTheArmory extends CardImpl {
public OpenTheArmory copy() {
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;
import java.util.Set;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.cards.*;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Zone;
@ -23,14 +16,16 @@ import mage.target.TargetCard;
import mage.target.common.TargetCardInLibrary;
import mage.target.common.TargetOpponent;
import java.util.Set;
import java.util.UUID;
/**
*
* @author North
*/
public final class RealmsUncharted extends CardImpl {
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.
this.getSpellAbility().addEffect(new RealmsUnchartedEffect());
@ -128,7 +123,7 @@ class RealmsUnchartedTarget extends TargetCardInLibrary {
}
@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);
if (card != null) {
for (UUID targetId : this.getTargets()) {
@ -137,7 +132,7 @@ class RealmsUnchartedTarget extends TargetCardInLibrary {
return false;
}
}
return filter.match(card, game);
return filter.match(card, playerId, game);
}
return false;
}

View file

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

View file

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

View file

@ -1,7 +1,6 @@
package mage.cards.t;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
@ -14,14 +13,15 @@ import mage.filter.predicate.mageobject.SubtypePredicate;
import mage.game.Game;
import mage.target.common.TargetCardInLibrary;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class ThreeDreams extends CardImpl {
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.
@ -41,6 +41,7 @@ public final class ThreeDreams extends CardImpl {
class ThreeDreamsTarget extends TargetCardInLibrary {
private static final FilterCard aurafilter = new FilterCard("Aura cards with different names");
static {
aurafilter.add(new SubtypePredicate(SubType.AURA));
}
@ -59,7 +60,7 @@ class ThreeDreamsTarget extends TargetCardInLibrary {
}
@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);
if (card != null) {
// check if card with that name was selected before
@ -69,7 +70,7 @@ class ThreeDreamsTarget extends TargetCardInLibrary {
return false;
}
}
return filter.match(card, game);
return filter.match(card, playerId, game);
}
return false;
}

View file

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

View file

@ -4,7 +4,6 @@ import mage.constants.PlayerAction;
import mage.interfaces.callback.CallbackClient;
import mage.interfaces.callback.ClientCallback;
import mage.remote.Session;
import mage.utils.CompressUtil;
import mage.view.*;
import org.apache.log4j.Logger;
@ -36,9 +35,18 @@ public class LoadCallbackClient implements CallbackClient {
@Override
public void processCallback(ClientCallback callback) {
callback.decompressData();
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()) {
@ -69,7 +77,7 @@ public class LoadCallbackClient implements CallbackClient {
case GAME_INFORM_PERSONAL: {
GameClientMessage message = (GameClientMessage) callback.getData();
gameView = message.getGameView();
log.info(getLogStartInfo() + "Inform: " + message.getMessage());
//log.info(getLogStartInfo() + "Inform: " + message.getMessage());
break;
}

View file

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