diff --git a/Mage.Server/src/main/java/mage/server/MageServerImpl.java b/Mage.Server/src/main/java/mage/server/MageServerImpl.java index 0d75682167..039a25a81a 100644 --- a/Mage.Server/src/main/java/mage/server/MageServerImpl.java +++ b/Mage.Server/src/main/java/mage/server/MageServerImpl.java @@ -636,7 +636,8 @@ public class MageServerImpl implements MageServer { } }); } else { - logger.error("table not found : " + tableId); + // this can happen if a game ends and a player quits XMage or a match nearly at the same time as the game ends + logger.trace("table not found : " + tableId); } return true; } @@ -1119,12 +1120,12 @@ public class MageServerImpl implements MageServer { public void toggleActivation(final String sessionId, final String userName) throws MageException { execute("toggleActivation", sessionId, () -> UserManager.instance.getUserByName(userName).ifPresent(user - -> { - user.setActive(!user.isActive()); - if (!user.isActive() && user.isConnected()) { - SessionManager.instance.disconnectUser(sessionId, user.getSessionId()); - } - })); + -> { + user.setActive(!user.isActive()); + if (!user.isActive() && user.isConnected()) { + SessionManager.instance.disconnectUser(sessionId, user.getSessionId()); + } + })); } @Override @@ -1159,8 +1160,8 @@ public class MageServerImpl implements MageServer { if (title != null && message != null) { execute("sendFeedbackMessage", sessionId, () -> SessionManager.instance.getSession(sessionId).ifPresent( - session -> FeedbackServiceImpl.instance.feedback(username, title, type, message, email, session.getHost()) - )); + session -> FeedbackServiceImpl.instance.feedback(username, title, type, message, email, session.getHost()) + )); } } diff --git a/Mage.Server/src/main/java/mage/server/Session.java b/Mage.Server/src/main/java/mage/server/Session.java index bdc2744cac..e06a561335 100644 --- a/Mage.Server/src/main/java/mage/server/Session.java +++ b/Mage.Server/src/main/java/mage/server/Session.java @@ -27,6 +27,11 @@ */ package mage.server; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import mage.MageException; import mage.constants.Constants; import mage.interfaces.callback.ClientCallback; @@ -44,12 +49,6 @@ import org.jboss.remoting.callback.Callback; import org.jboss.remoting.callback.HandleCallbackException; import org.jboss.remoting.callback.InvokerCallbackHandler; -import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - /** * @author BetaSteward_at_googlemail.com */ @@ -218,8 +217,8 @@ public class Session { if (authorizedUser.lockedUntil.compareTo(Calendar.getInstance().getTime()) > 0) { return "Your profile is deactivated until " + SystemUtil.dateFormat.format(authorizedUser.lockedUntil); } else { - UserManager.instance.createUser(userName, host, authorizedUser).ifPresent(user -> - user.setLockedUntil(null) + UserManager.instance.createUser(userName, host, authorizedUser).ifPresent(user + -> user.setLockedUntil(null) ); } @@ -263,7 +262,6 @@ public class Session { ChatManager.instance.sendReconnectMessage(userId); } - return null; } @@ -337,7 +335,7 @@ public class Session { lockSet = true; logger.debug("SESSION LOCK SET sessionId: " + sessionId); } else { - logger.error("CAN'T GET LOCK - userId: " + userId + " hold count: " + lock.getHoldCount()); + logger.warn("CAN'T GET LOCK - userId: " + userId + " hold count: " + lock.getHoldCount()); } Optional _user = UserManager.instance.getUser(userId); if (!_user.isPresent()) { @@ -393,7 +391,7 @@ public class Session { call.setMessageId(messageId++); callbackHandler.handleCallbackOneway(new Callback(call)); } catch (HandleCallbackException ex) { - ex.printStackTrace(); + // ex.printStackTrace(); UserManager.instance.getUser(userId).ifPresent(user -> { logger.warn("SESSION CALLBACK EXCEPTION - " + user.getName() + " userId " + userId); logger.warn(" - method: " + call.getMethod()); diff --git a/Mage.Server/src/main/java/mage/server/SessionManager.java b/Mage.Server/src/main/java/mage/server/SessionManager.java index 5ddc333875..1b66fce0c1 100644 --- a/Mage.Server/src/main/java/mage/server/SessionManager.java +++ b/Mage.Server/src/main/java/mage/server/SessionManager.java @@ -144,8 +144,10 @@ public enum SessionManager { case LostConnection: // user lost connection - session expires countdaoun starts session.userLostConnection(); break; + case ConnectingOtherInstance: + break; default: - logger.error("endSession: unexpected reason " + reason.toString() + " - sessionId: " + sessionId); + logger.trace("endSession: unexpected reason " + reason.toString() + " - sessionId: " + sessionId); } } else { sessions.remove(sessionId); diff --git a/Mage.Sets/src/mage/cards/t/TorrentialGearhulk.java b/Mage.Sets/src/mage/cards/t/TorrentialGearhulk.java index 0f5bcee906..c0a86a5e3e 100644 --- a/Mage.Sets/src/mage/cards/t/TorrentialGearhulk.java +++ b/Mage.Sets/src/mage/cards/t/TorrentialGearhulk.java @@ -50,6 +50,7 @@ import mage.game.events.ZoneChangeEvent; import mage.players.Player; import mage.target.common.TargetCardInYourGraveyard; import mage.target.targetpointer.FixedTarget; +import org.apache.log4j.Logger; /** * @@ -64,7 +65,7 @@ public class TorrentialGearhulk extends CardImpl { } public TorrentialGearhulk(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT,CardType.CREATURE},"{4}{U}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{4}{U}{U}"); this.subtype.add("Construct"); this.power = new MageInt(5); this.toughness = new MageInt(6); @@ -111,7 +112,7 @@ class TorrentialGearhulkEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { Card card = game.getCard(this.getTargetPointer().getFirst(game, source)); - if (card != null) { + if (card != null && card.getSpellAbility() != null) { if (controller.chooseUse(outcome, "Cast " + card.getLogName() + '?', source, game)) { if (controller.cast(card.getSpellAbility(), game, true)) { ContinuousEffect effect = new TorrentialGearhulkReplacementEffect(card.getId()); @@ -119,6 +120,9 @@ class TorrentialGearhulkEffect extends OneShotEffect { game.addEffect(effect, source); } } + } else { + Logger.getLogger(TorrentialGearhulkEffect.class).error("Torrential Gearhulk - Instant card without spellAbility : " + card.getName()); + return false; } return true; } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/EndTurnEffectTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/EndTurnEffectTest.java index 1a0462e4ac..af4fad430f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/EndTurnEffectTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/EndTurnEffectTest.java @@ -108,4 +108,37 @@ public class EndTurnEffectTest extends CardTestPlayerBase { assertHandCount(playerB, 0); } + + /** + * Test to remove a Aftermath card from spell + */ + @Test + public void testSpellAftermath() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + // Insult Sorcery {2}{R} + // Damage can't be prevented this turn. If a source you control would deal damage this turn, it deals double that damage instead. + // Injury Sorcery {2}{R} + // Aftermath (Cast this spell only from your graveyard. Then exile it.) + // Injury deals 2 damage to target creature and 2 damage to target player. + addCard(Zone.GRAVEYARD, playerA, "Insult // Injury"); + + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3); + // End the turn. + // At the beginning of your next end step, you lose the game. + addCard(Zone.HAND, playerB, "Glorious End"); //Instant {2}{R} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Injury", "Silvercoat Lion"); + addTarget(playerA, playerB); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Glorious End", NO_TARGET, "Injury"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertExileCount(playerA, "Insult // Injury", 1); + assertGraveyardCount(playerB, "Glorious End", 0); + assertHandCount(playerA, 0); + assertHandCount(playerB, 0); + + } } diff --git a/Mage/src/main/java/mage/abilities/common/LicidAbility.java b/Mage/src/main/java/mage/abilities/common/LicidAbility.java index f9db3f2439..545eb9313a 100644 --- a/Mage/src/main/java/mage/abilities/common/LicidAbility.java +++ b/Mage/src/main/java/mage/abilities/common/LicidAbility.java @@ -27,6 +27,7 @@ */ package mage.abilities.common; +import java.util.ArrayList; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.ActivatedAbilityImpl; @@ -144,14 +145,16 @@ class LicidContinuousEffect extends ContinuousEffectImpl { licid.getSubtype(game).add("Aura"); break; case AbilityAddingRemovingEffects_6: + ArrayList toRemove = new ArrayList<>(); for (Ability ability : licid.getAbilities(game)) { for (Effect effect : ability.getEffects()) { if (effect instanceof LicidEffect) { - licid.getAbilities(game).remove(ability); + toRemove.add(ability); break; } } } + licid.getAbilities(game).removeAll(toRemove); Ability ability = new EnchantAbility("creature"); ability.setRuleAtTheTop(true); licid.addAbility(ability, source.getSourceId(), game); diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java index ea7067e0d2..5d5454c5e1 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java @@ -1218,7 +1218,10 @@ public class ContinuousEffects implements Serializable { } } } else { - logger.error("Replacement effect without ability: " + entry.getKey().toString()); + if (!(entry.getKey() instanceof AuraReplacementEffect) + && !(entry.getKey() instanceof PlaneswalkerRedirectionEffect)) { + logger.error("Replacement effect without ability: " + entry.getKey().toString()); + } } } return texts; diff --git a/Mage/src/main/java/mage/abilities/effects/common/counter/RemoveCounterTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/counter/RemoveCounterTargetEffect.java index 5820f6166d..e99b328e2f 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/counter/RemoveCounterTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/counter/RemoveCounterTargetEffect.java @@ -25,7 +25,6 @@ * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ - package mage.abilities.effects.common.counter; import java.util.HashSet; @@ -47,8 +46,8 @@ import mage.util.CardUtil; * * @author LevelX2 */ - public class RemoveCounterTargetEffect extends OneShotEffect { + private final Counter counter; public RemoveCounterTargetEffect() { @@ -69,23 +68,25 @@ public class RemoveCounterTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Permanent p = game.getPermanent(targetPointer.getFirst(game, source)); - if(p != null) { + if (p != null) { Counter toRemove = (counter == null ? selectCounterType(game, source, p) : counter); - if(toRemove != null && p.getCounters(game).getCount(toRemove.getName()) >= toRemove.getCount()) { + if (toRemove != null && p.getCounters(game).getCount(toRemove.getName()) >= toRemove.getCount()) { p.removeCounters(toRemove.getName(), toRemove.getCount(), game); - if(!game.isSimulation()) + if (!game.isSimulation()) { game.informPlayers("Removed " + toRemove.getCount() + ' ' + toRemove.getName() - + " counter from " + p.getName()); + + " counter from " + p.getName()); + } return true; } } Card c = game.getCard(targetPointer.getFirst(game, source)); if (c != null && counter != null && c.getCounters(game).getCount(counter.getName()) >= counter.getCount()) { c.removeCounters(counter.getName(), counter.getCount(), game); - if (!game.isSimulation()) + if (!game.isSimulation()) { game.informPlayers(new StringBuilder("Removed ").append(counter.getCount()).append(' ').append(counter.getName()) - .append(" counter from ").append(c.getName()) - .append(" (").append(c.getCounters(game).getCount(counter.getName())).append(" left)").toString()); + .append(" counter from ").append(c.getName()) + .append(" (").append(c.getCounters(game).getCount(counter.getName())).append(" left)").toString()); + } return true; } return false; @@ -93,12 +94,12 @@ public class RemoveCounterTargetEffect extends OneShotEffect { private Counter selectCounterType(Game game, Ability source, Permanent permanent) { Player controller = game.getPlayer(source.getControllerId()); - if(controller != null && !permanent.getCounters(game).isEmpty()) { + if (controller != null && !permanent.getCounters(game).isEmpty()) { String counterName = null; - if(permanent.getCounters(game).size() > 1) { + if (permanent.getCounters(game).size() > 1) { Choice choice = new ChoiceImpl(true); Set choices = new HashSet<>(); - for(Counter counter : permanent.getCounters(game).values()) { + for (Counter counter : permanent.getCounters(game).values()) { if (permanent.getCounters(game).getCount(counter.getName()) > 0) { choices.add(counter.getName()); } @@ -108,8 +109,8 @@ public class RemoveCounterTargetEffect extends OneShotEffect { controller.choose(Outcome.Detriment, choice, game); counterName = choice.getChoice(); } else { - for(Counter counter : permanent.getCounters(game).values()) { - if(counter.getCount() > 0) { + for (Counter counter : permanent.getCounters(game).values()) { + if (counter.getCount() > 0) { counterName = counter.getName(); } } @@ -131,14 +132,13 @@ public class RemoveCounterTargetEffect extends OneShotEffect { } String text = "remove "; - if(counter == null) { + if (counter == null) { text += "a counter"; + } else { + text += CardUtil.numberToText(counter.getCount(), "a") + ' ' + counter.getName(); + text += counter.getCount() > 1 ? " counters" : " counter"; } - else { - text += CardUtil.numberToText(counter.getCount(), "a") + ' ' + counter.getName(); - text += counter.getCount() > 1 ? " counters" : " counter"; - } - text += " from target " + mode.getTargets().get(0).getTargetName(); + text += " from target " + (mode.getTargets().isEmpty() ? " object" : mode.getTargets().get(0).getTargetName()); return text; } } diff --git a/Mage/src/main/java/mage/game/turn/Turn.java b/Mage/src/main/java/mage/game/turn/Turn.java index 1de664ff84..dcf5dd8bfa 100644 --- a/Mage/src/main/java/mage/game/turn/Turn.java +++ b/Mage/src/main/java/mage/game/turn/Turn.java @@ -276,7 +276,7 @@ public class Turn implements Serializable { setEndTurnRequested(true); - // 1) All spells and abilities on the stack are exiled. This includes Time Stop, though it will continue to resolve. + // 1) All spells and abilities on the stack are exiled. This includes (e.g.) Time Stop, though it will continue to resolve. // It also includes spells and abilities that can't be countered. while (!game.getStack().isEmpty()) { StackObject stackObject = game.getStack().peekFirst();