diff --git a/Mage.Client/src/main/java/mage/client/cards/Cards.java b/Mage.Client/src/main/java/mage/client/cards/Cards.java index f3241bc0039..7b27867cfa0 100644 --- a/Mage.Client/src/main/java/mage/client/cards/Cards.java +++ b/Mage.Client/src/main/java/mage/client/cards/Cards.java @@ -168,8 +168,13 @@ } } if (card instanceof StackAbilityView) { + // replace ability by original card CardView tmp = ((StackAbilityView) card).getSourceCard(); tmp.overrideRules(card.getRules()); + tmp.setChoosable(card.isChoosable()); + tmp.setPlayable(card.isPlayable()); + tmp.setPlayableAmount(card.getPlayableAmount()); + tmp.setSelected(card.isSelected()); tmp.setIsAbility(true); tmp.overrideTargets(card.getTargets()); tmp.overrideId(card.getId()); diff --git a/Mage.Client/src/main/java/mage/client/components/ext/dlg/impl/StackDialog.java b/Mage.Client/src/main/java/mage/client/components/ext/dlg/impl/StackDialog.java index d18b5c78c9f..6175b4e8a8e 100644 --- a/Mage.Client/src/main/java/mage/client/components/ext/dlg/impl/StackDialog.java +++ b/Mage.Client/src/main/java/mage/client/components/ext/dlg/impl/StackDialog.java @@ -112,8 +112,13 @@ public class StackDialog extends IDialogPanel { for (CardView card : cards.values()) { if (card instanceof StackAbilityView) { + // replace ability by original card CardView tmp = ((StackAbilityView) card).getSourceCard(); tmp.overrideRules(card.getRules()); + tmp.setChoosable(card.isChoosable()); + tmp.setPlayable(card.isPlayable()); + tmp.setPlayableAmount(card.getPlayableAmount()); + tmp.setSelected(card.isSelected()); tmp.setIsAbility(true); tmp.overrideTargets(card.getTargets()); tmp.overrideId(card.getId()); diff --git a/Mage.Common/pom.xml b/Mage.Common/pom.xml index d1e40dd02ff..d922a77a108 100644 --- a/Mage.Common/pom.xml +++ b/Mage.Common/pom.xml @@ -56,6 +56,21 @@ gson 2.8.6 + + org.junit.jupiter + junit-jupiter + test + + + org.assertj + assertj-core + test + + + org.apache.commons + commons-lang3 + test + @@ -82,7 +97,10 @@ UTF-8 - + + org.apache.maven.plugins + maven-surefire-plugin + mage-common diff --git a/Mage.Common/src/main/java/mage/utils/FluentBuilder.java b/Mage.Common/src/main/java/mage/utils/FluentBuilder.java new file mode 100644 index 00000000000..003cd65167a --- /dev/null +++ b/Mage.Common/src/main/java/mage/utils/FluentBuilder.java @@ -0,0 +1,41 @@ +package mage.utils; + +import java.util.ArrayList; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * A base class for fluent, immutable, composable builders. + * + * @see Builder + */ +public abstract class FluentBuilder> { + + final ArrayList> buildSequence; + private final Supplier newReference; + + protected FluentBuilder(Supplier newReference) { + this.buildSequence = new ArrayList<>(); + this.newReference = newReference; + } + + private RealBuilder copy() { + final RealBuilder realBuilder = newReference.get(); + realBuilder.buildSequence.addAll(buildSequence); + return realBuilder; + } + + protected abstract ToBuild makeValue(); + + public RealBuilder with(Consumer consumer) { + final RealBuilder nextBuilder = this.copy(); + nextBuilder.buildSequence.add(consumer); + return nextBuilder; + } + + public ToBuild build() { + final RealBuilder instance = this.copy(); + instance.buildSequence.forEach(c -> c.accept(instance)); + return instance.makeValue(); + } +} diff --git a/Mage.Common/src/main/java/mage/view/CardView.java b/Mage.Common/src/main/java/mage/view/CardView.java index e5fd2d57df9..de649000493 100644 --- a/Mage.Common/src/main/java/mage/view/CardView.java +++ b/Mage.Common/src/main/java/mage/view/CardView.java @@ -121,8 +121,17 @@ public class CardView extends SimpleCardView { protected Card originalCard = null; + /** + * Non game usage like deck editor + * + * @param card + */ public CardView(Card card) { - this(card, null, false); + this(card, (Game) null); + } + + public CardView(Card card, Game game) { + this(card, game, false); } public CardView(Card card, SimpleCardView simpleCardView) { @@ -450,7 +459,7 @@ public class CardView extends SimpleCardView { Card secondSideCard = card.getSecondCardFace(); if (secondSideCard != null) { - this.secondCardFace = new CardView(secondSideCard); + this.secondCardFace = new CardView(secondSideCard, game); this.alternateName = secondCardFace.getName(); this.originalName = card.getName(); } @@ -464,7 +473,7 @@ public class CardView extends SimpleCardView { if (card instanceof ModalDoubleFacesCard) { this.transformable = true; // enable GUI day/night button ModalDoubleFacesCard mdfCard = (ModalDoubleFacesCard) card; - this.secondCardFace = new CardView(mdfCard.getRightHalfCard()); + this.secondCardFace = new CardView(mdfCard.getRightHalfCard(), game); this.alternateName = mdfCard.getRightHalfCard().getName(); this.originalName = card.getName(); } diff --git a/Mage.Common/src/main/java/mage/view/CardsView.java b/Mage.Common/src/main/java/mage/view/CardsView.java index d83ac6a91e1..084cd0895a5 100644 --- a/Mage.Common/src/main/java/mage/view/CardsView.java +++ b/Mage.Common/src/main/java/mage/view/CardsView.java @@ -6,7 +6,6 @@ import mage.abilities.effects.Effect; import mage.cards.Card; import mage.constants.Zone; import mage.game.Game; -import mage.game.GameState; import mage.game.command.Emblem; import mage.game.command.Plane; import mage.game.permanent.Permanent; @@ -29,7 +28,7 @@ public class CardsView extends LinkedHashMap { } /** - * Uses for card render tests + * Non game usage like card render tests * * @param cardViews */ @@ -39,6 +38,11 @@ public class CardsView extends LinkedHashMap { } } + /** + * Non game usage like deck editor + * + * @param cards + */ public CardsView(Collection cards) { for (Card card : cards) { this.put(card.getId(), new CardView(card)); @@ -116,9 +120,9 @@ public class CardsView extends LinkedHashMap { if (abilityView == null) { CardView sourceCardView; if (isPermanent) { - sourceCardView = new CardView((Permanent) sourceObject); + sourceCardView = new CardView((Permanent) sourceObject, game); } else if (isCard) { - sourceCardView = new CardView((Card) sourceObject); + sourceCardView = new CardView((Card) sourceObject, game); } else { sourceCardView = new CardView(sourceObject, game); } @@ -164,14 +168,4 @@ public class CardsView extends LinkedHashMap { + abilities.stream().map(a -> a.getClass().getSimpleName() + " - " + a.getRule()).collect(Collectors.joining("\n"))); } } - - public CardsView(Collection abilities, GameState state) { - for (Ability ability : abilities) { - Card sourceCard = state.getPermanent(ability.getSourceId()); - if (sourceCard != null) { - this.put(ability.getId(), new AbilityView(ability, sourceCard.getName(), new CardView(sourceCard))); - } - } - } - } diff --git a/Mage.Common/src/main/java/mage/view/GameView.java b/Mage.Common/src/main/java/mage/view/GameView.java index 3f703c7678a..14bd2c34056 100644 --- a/Mage.Common/src/main/java/mage/view/GameView.java +++ b/Mage.Common/src/main/java/mage/view/GameView.java @@ -94,7 +94,7 @@ public class GameView implements Serializable { stack.put(stackObject.getId(), new StackAbilityView(game, (StackAbility) stackObject, card.getName(), new CardView(card, game, false, false, false))); } } else { - stack.put(stackObject.getId(), new StackAbilityView(game, (StackAbility) stackObject, "", new CardView(card))); + stack.put(stackObject.getId(), new StackAbilityView(game, (StackAbility) stackObject, "", new CardView(card, game))); } if (card.isTransformable()) { updateLatestCardView(game, card, stackObject.getId()); @@ -103,7 +103,7 @@ public class GameView implements Serializable { } else if (object != null) { if (object instanceof PermanentToken) { PermanentToken token = (PermanentToken) object; - stack.put(stackObject.getId(), new StackAbilityView(game, (StackAbility) stackObject, token.getName(), new CardView(token))); + stack.put(stackObject.getId(), new StackAbilityView(game, (StackAbility) stackObject, token.getName(), new CardView(token, game))); checkPaid(stackObject.getId(), (StackAbility) stackObject); } else if (object instanceof Emblem) { CardView cardView = new CardView(new EmblemView((Emblem) object)); diff --git a/Mage.Common/src/main/java/mage/view/PermanentView.java b/Mage.Common/src/main/java/mage/view/PermanentView.java index 2031e797ee0..c72f910a9b6 100644 --- a/Mage.Common/src/main/java/mage/view/PermanentView.java +++ b/Mage.Common/src/main/java/mage/view/PermanentView.java @@ -1,9 +1,5 @@ - package mage.view; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.TurnFaceUpAbility; import mage.cards.Card; @@ -12,8 +8,11 @@ import mage.game.permanent.Permanent; import mage.game.permanent.PermanentToken; import mage.players.Player; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + /** - * * @author BetaSteward_at_googlemail.com */ public class PermanentView extends CardView { @@ -36,8 +35,8 @@ public class PermanentView extends CardView { private final boolean attachedToPermanent; public PermanentView(Permanent permanent, Card card, UUID createdForPlayerId, Game game) { - super(permanent, game, (permanent.getControllerId() == null) ? false : permanent.getControllerId().equals(createdForPlayerId)); - this.controlled = (permanent.getControllerId() == null) ? false : permanent.getControllerId().equals(createdForPlayerId); + super(permanent, game, permanent.getControllerId() != null && permanent.getControllerId().equals(createdForPlayerId)); + this.controlled = permanent.getControllerId() != null && permanent.getControllerId().equals(createdForPlayerId); this.rules = permanent.getRules(game); this.tapped = permanent.isTapped(); this.flipped = permanent.isFlipped(); @@ -59,7 +58,7 @@ public class PermanentView extends CardView { } else { if (card != null) { // original may not be face down - original = new CardView(card); + original = new CardView(card, game); } else { original = null; } diff --git a/Mage.Common/src/main/java/mage/view/PlayerView.java b/Mage.Common/src/main/java/mage/view/PlayerView.java index 2269530dc69..fd31146a6cb 100644 --- a/Mage.Common/src/main/java/mage/view/PlayerView.java +++ b/Mage.Common/src/main/java/mage/view/PlayerView.java @@ -1,13 +1,5 @@ - package mage.view; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.ConcurrentModificationException; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; import mage.cards.Card; import mage.counters.Counters; import mage.designations.Designation; @@ -22,6 +14,9 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.players.net.UserData; +import java.io.Serializable; +import java.util.*; + /** * @author BetaSteward_at_googlemail.com */ @@ -105,7 +100,7 @@ public class PlayerView implements Serializable { } Card cardOnTop = (player.isTopCardRevealed() && player.getLibrary().hasCards()) ? player.getLibrary().getFromTop(game) : null; - this.topCard = cardOnTop != null ? new CardView(cardOnTop) : null; + this.topCard = cardOnTop != null ? new CardView(cardOnTop, game) : null; if (player.getUserData() != null) { this.userData = player.getUserData(); } else { diff --git a/Mage.Common/src/main/java/mage/view/StackAbilityView.java b/Mage.Common/src/main/java/mage/view/StackAbilityView.java index 4c2d6fc275e..cfe29e4786a 100644 --- a/Mage.Common/src/main/java/mage/view/StackAbilityView.java +++ b/Mage.Common/src/main/java/mage/view/StackAbilityView.java @@ -27,6 +27,8 @@ public class StackAbilityView extends CardView { private static final long serialVersionUID = 1L; + // in GUI: that's view will be replaced by sourceCard, so don't forget to sync settings like + // selectable, chooseable, etc. Search by getSourceCard private final CardView sourceCard; public StackAbilityView(Game game, StackAbility ability, String sourceName, CardView sourceCard) { diff --git a/Mage.Common/src/test/java/mage/remote/ConnectionTest.java b/Mage.Common/src/test/java/mage/remote/ConnectionTest.java new file mode 100644 index 00000000000..1e49ed9c377 --- /dev/null +++ b/Mage.Common/src/test/java/mage/remote/ConnectionTest.java @@ -0,0 +1,123 @@ +package mage.remote; + +import mage.utils.FluentBuilder; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.RandomUtils; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.net.URI; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ConnectionTest { + + static class ConnectionBuilder extends FluentBuilder { + + public int port; + public String host; + public String parameter; + + private ConnectionBuilder() { + super(ConnectionBuilder::new); + } + + @Override + protected Connection makeValue() { + final Connection result = new Connection(parameter); + result.setHost(host); + result.setPort(port); + return result; + } + } + + private ConnectionBuilder baseBuilder() { + return new ConnectionBuilder(); + } + + class TestsTemplate { + final ConnectionBuilder testeeBuilder; + + TestsTemplate(ConnectionBuilder testeeBuilder) { + this.testeeBuilder = testeeBuilder; + } + + @Test + @DisplayName("produce the expected scheme") + void scheme() throws Exception { + final URI testee = make(testeeBuilder); + assertThat(testee.getScheme()).isEqualTo("bisocket"); + } + + URI make(ConnectionBuilder builder) { + try { + return new URI(builder.build().getURI()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + @DisplayName("generate the expected port") + void port() { + final int expected = RandomUtils.nextInt(1000, 65000); + final int port = make(testeeBuilder.with(c -> c.port = expected)).getPort(); + + assertThat(port).isEqualTo(expected); + } + + @Test + @DisplayName("generate the expected serialisation parameter") + void serialisation() { + final String query = make(testeeBuilder).getQuery(); + + assertThat(query).contains("serializationtype=jboss"); + } + + @Test + @DisplayName("generate the expected threadpool parameter") + void threadpool() { + final String parameter = RandomStringUtils.randomAlphanumeric(12); + final String query = make(testeeBuilder.with(c -> c.parameter = parameter)).getQuery(); + + assertThat(query).contains("onewayThreadPool=mage.remote.CustomThreadPool" + parameter); + } + + } + + @Nested + @DisplayName("getUri when host is localhost should") + class LocalhostTest extends TestsTemplate { + + LocalhostTest() { + super(baseBuilder().with(c -> c.host = "localhost")); + } + + @Test + @DisplayName("generate an ipv4 as host") + void ipv4Gen() { + final String host = make(testeeBuilder).getHost(); + + assertThat(host).matches("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+"); + } + } + + private final String randomHost = RandomStringUtils.randomAlphabetic(15); + + @Nested + @DisplayName("getUri when host is not localhost should") + class StandardHostTest extends TestsTemplate { + StandardHostTest() { + super(baseBuilder().with(c -> c.host = randomHost)); + } + + @Test + @DisplayName("generate the selected host as host") + void hostGen() { + final String host = make(testeeBuilder).getHost(); + + assertThat(host).isEqualTo(randomHost); + } + } +} diff --git a/Mage.Common/src/test/java/mage/utils/FluentBuilderTest.java b/Mage.Common/src/test/java/mage/utils/FluentBuilderTest.java new file mode 100644 index 00000000000..180f82ef200 --- /dev/null +++ b/Mage.Common/src/test/java/mage/utils/FluentBuilderTest.java @@ -0,0 +1,116 @@ +package mage.utils; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +public class FluentBuilderTest { + + + private ABuilder baseBuilder() { + return new ABuilder(); + } + + @Test + @DisplayName("build with default parameters") + void testDefault() { + final A actual = baseBuilder().build(); + + verifyAB(actual, null, 0); + } + + private void verifyAB(A actual, String a, int b) { + assertThat(actual.getA()).isEqualTo(a); + assertThat(actual.getB()).isEqualTo(b); + } + + @Test + @DisplayName("chain with clause and add new parameters") + void testBaseChain() { + final A actual = baseBuilder().with(a -> a.a = "hello").build(); + + verifyAB(actual, "hello", 0); + } + + @Test + @DisplayName("chain multiple with clauses and add new parameters") + void testMultiChain() { + final A actual = baseBuilder().with(a -> a.a = "world").with(a -> a.b = 6).build(); + + verifyAB(actual, "world", 6); + } + + @Test + @DisplayName("chain multiple with clauses and override latest writes") + void testMultiChainOverride() { + final A actual = baseBuilder().with(a -> a.a = "world").with(a -> a.b = 4).with(a -> a.a = "foobar").build(); + + verifyAB(actual, "foobar", 4); + } + + @Test + @DisplayName("not mutate the state of previous builder in the chain") + void testImmutability() { + final ABuilder builder1 = baseBuilder().with(a -> a.a = "world"); + final ABuilder builder2 = builder1.with(a -> { + a.a = "hello"; + a.b = 42; + }); + + verifyAB(builder1.build(), "world", 0); + verifyAB(builder2.build(), "hello", 42); + } + + @Test + @DisplayName("produce different objects") + void differentObjects() { + final ABuilder builder = baseBuilder().with(a -> { + a.a = "hello"; + a.b = 42; + }); + final A a1 = builder.build(); + final A a2 = builder.build(); + + assertThat(a1).isNotSameAs(a2); + verifyAB(a1, "hello", 42); + verifyAB(a2, "hello", 42); + } + + static class A { + public final String a; + private int b; + + public A(String a) { + this.a = a; + } + + public String getA() { + return a; + } + + public int getB() { + return b; + } + + public void setB(int b) { + this.b = b; + } + } + + static class ABuilder extends FluentBuilder { + public String a; + public int b; + + private ABuilder() { + super(ABuilder::new); + } + + @Override + protected A makeValue() { + final A result = new A(a); + result.setB(b); + return result; + } + } +} diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java index caed86ffc6b..15b49248b98 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java @@ -1,5 +1,6 @@ package mage.player.ai; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.ActivatedAbility; import mage.abilities.SpellAbility; @@ -29,6 +30,7 @@ import mage.player.ai.util.CombatInfo; import mage.player.ai.util.CombatUtil; import mage.players.Player; import mage.target.Target; +import mage.target.TargetAmount; import mage.target.TargetCard; import mage.target.Targets; import mage.util.RandomUtil; @@ -37,6 +39,7 @@ import org.apache.log4j.Logger; import java.io.File; import java.util.*; import java.util.concurrent.*; +import java.util.stream.Collectors; /** * @author nantuko @@ -155,7 +158,13 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { boolean usedStack = false; while (actions.peek() != null) { Ability ability = actions.poll(); - logger.info(new StringBuilder("===> Act [").append(game.getPlayer(playerId).getName()).append("] Action: ").append(ability.toString()).toString()); + // log example: ===> Act [PlayerA] Action: Cast Blessings of Nature (target 1; target 2) + logger.info(new StringBuilder("===> Act [") + .append(game.getPlayer(playerId).getName()) + .append("] Action: ") + .append(ability.toString()) + .append(listTargets(game, ability.getTargets(), " (targeting %s)", "")) + .toString()); if (!ability.getTargets().isEmpty()) { for (Target target : ability.getTargets()) { for (UUID id : target.getTargets()) { @@ -506,7 +515,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { StringBuilder sb = new StringBuilder("Sim Prio [").append(depth).append("] #").append(counter) .append(" <").append(val).append("> (").append(action) .append(action.isModal() ? " Mode = " + action.getModes().getMode().toString() : "") - .append(listTargets(game, action.getTargets())).append(')') + .append(listTargets(game, action.getTargets(), " (targeting %s)", "")).append(')') .append(logger.isTraceEnabled() ? " #" + newNode.hashCode() : ""); SimulationNode2 logNode = newNode; while (logNode.getChildren() != null @@ -541,7 +550,11 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { if (depth == maxDepth) { GameStateEvaluator2.PlayerEvaluateScore score = GameStateEvaluator2.evaluate(this.getId(), bestNode.game); String scoreInfo = " [" + score.getPlayerInfoShort() + "-" + score.getOpponentInfoShort() + "]"; - logger.info("Sim Prio [" + depth + "] -- Saved best node yet <" + bestNode.getScore() + scoreInfo + "> " + bestNode.getAbilities().toString()); + String abilitiesInfo = bestNode.getAbilities() + .stream() + .map(a -> a.toString() + listTargets(game, a.getTargets(), " (targeting %s)", "")) + .collect(Collectors.joining("; ")); + logger.info("Sim Prio [" + depth + "] -- Saved best node yet <" + bestNode.getScore() + scoreInfo + "> " + abilitiesInfo); node.children.clear(); node.children.add(bestNode); node.setScore(bestNode.getScore()); @@ -1009,17 +1022,36 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { return suggestedActions.size(); } - protected String listTargets(Game game, Targets targets) { - StringBuilder sb = new StringBuilder(); - if (targets != null) { - for (Target target : targets) { - sb.append('[').append(target.getTargetedName(game)).append(']'); - } - if (sb.length() > 0) { - sb.insert(0, " targeting "); + /** + * Return info about targets list (targeting objects) + * + * @param game + * @param targets + * @param format example: my %s in data + * @param emptyText default text for empty targets list + * @return + */ + protected String listTargets(Game game, Targets targets, String format, String emptyText) { + List res = new ArrayList<>(); + for (Target target : targets) { + for (UUID id : target.getTargets()) { + MageObject object = game.getObject(id); + if (object != null) { + String prefix = ""; + if (target instanceof TargetAmount) { + prefix = " " + target.getTargetAmount(id) + "x "; + } + res.add(prefix + object.getIdName()); + } } } - return sb.toString(); + String info = String.join("; ", res); + + if (info.isEmpty()) { + return emptyText; + } else { + return String.format(format, info); + } } @Override diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java index 37edf439bb2..b2c2ed02f05 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java @@ -1130,7 +1130,8 @@ public class ComputerPlayer extends PlayerImpl implements Player { } } - log.warn("No proper AI target handling: " + target.getClass().getName()); + // it's ok on no targets available + log.warn("No proper AI target handling or can't find permanents/cards to target: " + target.getClass().getName()); return false; } diff --git a/Mage.Server/pom.xml b/Mage.Server/pom.xml index 07f87d21ccf..3f3f2c23f8f 100644 --- a/Mage.Server/pom.xml +++ b/Mage.Server/pom.xml @@ -238,7 +238,7 @@ com.google.api-client google-api-client - 1.30.10 + 1.31.1 jar @@ -256,7 +256,7 @@ com.google.oauth-client google-oauth-client-jetty - 1.31.0 + 1.31.2 jar @@ -285,6 +285,18 @@ sqlite-jdbc 3.32.3.2 + + + + org.junit.jupiter + junit-jupiter + test + + + org.assertj + assertj-core + test + @@ -358,6 +370,10 @@ + + org.apache.maven.plugins + maven-surefire-plugin + mage-server diff --git a/Mage.Server/src/main/java/mage/server/ChatManager.java b/Mage.Server/src/main/java/mage/server/ChatManagerImpl.java similarity index 89% rename from Mage.Server/src/main/java/mage/server/ChatManager.java rename to Mage.Server/src/main/java/mage/server/ChatManagerImpl.java index 3ff86009637..1bd283e03eb 100644 --- a/Mage.Server/src/main/java/mage/server/ChatManager.java +++ b/Mage.Server/src/main/java/mage/server/ChatManagerImpl.java @@ -5,8 +5,8 @@ import mage.cards.repository.CardRepository; import mage.game.Game; import mage.server.exceptions.UserNotFoundException; import mage.server.game.GameController; -import mage.server.game.GameManager; -import mage.server.game.GamesRoomManager; +import mage.server.managers.ChatManager; +import mage.server.managers.ManagerFactory; import mage.server.util.SystemUtil; import mage.view.ChatMessage.MessageColor; import mage.view.ChatMessage.MessageType; @@ -26,21 +26,27 @@ import java.util.stream.Collectors; /** * @author BetaSteward_at_googlemail.com */ -public enum ChatManager { +public class ChatManagerImpl implements ChatManager { - instance; - private static final Logger logger = Logger.getLogger(ChatManager.class); + private static final Logger logger = Logger.getLogger(ChatManagerImpl.class); private static final HashMap userMessages = new HashMap<>(); + private final ManagerFactory managerFactory; private final ConcurrentHashMap chatSessions = new ConcurrentHashMap<>(); private final ReadWriteLock lock = new ReentrantReadWriteLock(); + public ChatManagerImpl(ManagerFactory managerFactory) { + this.managerFactory = managerFactory; + } + + @Override public UUID createChatSession(String info) { - ChatSession chatSession = new ChatSession(info); + ChatSession chatSession = new ChatSession(managerFactory, info); chatSessions.put(chatSession.getChatId(), chatSession); return chatSession.getChatId(); } + @Override public void joinChat(UUID chatId, UUID userId) { ChatSession chatSession = chatSessions.get(chatId); if (chatSession != null) { @@ -51,10 +57,12 @@ public enum ChatManager { } + @Override public void clearUserMessageStorage() { userMessages.clear(); } + @Override public void leaveChat(UUID chatId, UUID userId) { ChatSession chatSession = chatSessions.get(chatId); if (chatSession != null && chatSession.hasUser(userId)) { @@ -62,6 +70,7 @@ public enum ChatManager { } } + @Override public void destroyChatSession(UUID chatId) { if (chatId != null) { ChatSession chatSession = chatSessions.get(chatId); @@ -84,11 +93,12 @@ public enum ChatManager { final Pattern cardNamePattern = Pattern.compile("\\[(.*?)\\]"); + @Override public void broadcast(UUID chatId, String userName, String message, MessageColor color, boolean withTime, Game game, MessageType messageType, SoundToPlay soundToPlay) { ChatSession chatSession = chatSessions.get(chatId); if (chatSession != null) { if (message.startsWith("\\") || message.startsWith("/")) { - Optional user = UserManager.instance.getUserByName(userName); + Optional user = managerFactory.userManager().getUserByName(userName); if (user.isPresent()) { if (!performUserCommand(user.get(), message, chatId, false)) { performUserCommand(user.get(), message, chatId, true); @@ -98,7 +108,7 @@ public enum ChatManager { } if (messageType != MessageType.GAME && !userName.isEmpty()) { - Optional u = UserManager.instance.getUserByName(userName); + Optional u = managerFactory.userManager().getUserByName(userName); if (u.isPresent()) { User user = u.get(); @@ -184,12 +194,12 @@ public enum ChatManager { } if (command.startsWith("H ") || command.startsWith("HISTORY ")) { - message += "
" + UserManager.instance.getUserHistory(message.substring(command.startsWith("H ") ? 3 : 9)); + message += "
" + managerFactory.userManager().getUserHistory(message.substring(command.startsWith("H ") ? 3 : 9)); chatSessions.get(chatId).broadcastInfoToUser(user, message); return true; } if (command.equals("ME")) { - message += "
" + UserManager.instance.getUserHistory(user.getName()); + message += "
" + managerFactory.userManager().getUserHistory(user.getName()); chatSessions.get(chatId).broadcastInfoToUser(user, message); return true; } @@ -200,7 +210,7 @@ public enum ChatManager { String gameId = session.getInfo(); if (gameId.startsWith("Game ")) { UUID id = java.util.UUID.fromString(gameId.substring(5)); - for (Entry entry : GameManager.instance.getGameController().entrySet()) { + for (Entry entry : managerFactory.gameManager().getGameController().entrySet()) { if (entry.getKey().equals(id)) { GameController controller = entry.getValue(); if (controller != null) { @@ -221,7 +231,7 @@ public enum ChatManager { String gameId = session.getInfo(); if (gameId.startsWith("Game ")) { UUID id = java.util.UUID.fromString(gameId.substring(5)); - for (Entry entry : GameManager.instance.getGameController().entrySet()) { + for (Entry entry : managerFactory.gameManager().getGameController().entrySet()) { if (entry.getKey().equals(id)) { GameController controller = entry.getValue(); if (controller != null) { @@ -242,7 +252,7 @@ public enum ChatManager { String gameId = session.getInfo(); if (gameId.startsWith("Game ")) { UUID id = java.util.UUID.fromString(gameId.substring(5)); - for (Entry entry : GameManager.instance.getGameController().entrySet()) { + for (Entry entry : managerFactory.gameManager().getGameController().entrySet()) { if (entry.getKey().equals(id)) { GameController controller = entry.getValue(); if (controller != null) { @@ -281,7 +291,7 @@ public enum ChatManager { if (first > 1) { String userToName = rest.substring(0, first); rest = rest.substring(first + 1).trim(); - Optional userTo = UserManager.instance.getUserByName(userToName); + Optional userTo = managerFactory.userManager().getUserByName(userToName); if (userTo.isPresent()) { if (!chatSessions.get(chatId).broadcastWhisperToUser(user, userTo.get(), rest)) { message += new StringBuilder("
User ").append(userToName).append(" not found").toString(); @@ -312,8 +322,9 @@ public enum ChatManager { * @param color * @throws mage.server.exceptions.UserNotFoundException */ + @Override public void broadcast(UUID userId, String message, MessageColor color) throws UserNotFoundException { - UserManager.instance.getUser(userId).ifPresent(user -> { + managerFactory.userManager().getUser(userId).ifPresent(user -> { getChatSessions() .stream() .filter(chat -> chat.hasUser(userId)) @@ -322,8 +333,9 @@ public enum ChatManager { }); } + @Override public void sendReconnectMessage(UUID userId) { - UserManager.instance.getUser(userId).ifPresent(user + managerFactory.userManager().getUser(userId).ifPresent(user -> getChatSessions() .stream() .filter(chat -> chat.hasUser(userId)) @@ -331,8 +343,9 @@ public enum ChatManager { } + @Override public void sendLostConnectionMessage(UUID userId, DisconnectReason reason) { - UserManager.instance.getUser(userId).ifPresent(user -> sendMessageToUserChats(userId, user.getName() + " " + reason.getMessage())); + managerFactory.userManager().getUser(userId).ifPresent(user -> sendMessageToUserChats(userId, user.getName() + " " + reason.getMessage())); } /** @@ -341,10 +354,11 @@ public enum ChatManager { * @param userId * @param message */ + @Override public void sendMessageToUserChats(UUID userId, String message) { - UserManager.instance.getUser(userId).ifPresent(user -> { + managerFactory.userManager().getUser(userId).ifPresent(user -> { List chatSessions = getChatSessions().stream() - .filter(chat -> !chat.getChatId().equals(GamesRoomManager.instance.getMainChatId())) // ignore main lobby + .filter(chat -> !chat.getChatId().equals(managerFactory.gamesRoomManager().getMainChatId())) // ignore main lobby .filter(chat -> chat.hasUser(userId)) .collect(Collectors.toList()); @@ -355,6 +369,7 @@ public enum ChatManager { }); } + @Override public void removeUser(UUID userId, DisconnectReason reason) { for (ChatSession chatSession : getChatSessions()) { if (chatSession.hasUser(userId)) { @@ -363,6 +378,7 @@ public enum ChatManager { } } + @Override public List getChatSessions() { final Lock r = lock.readLock(); r.lock(); diff --git a/Mage.Server/src/main/java/mage/server/ChatSession.java b/Mage.Server/src/main/java/mage/server/ChatSession.java index fad1424573f..73ffa28fe77 100644 --- a/Mage.Server/src/main/java/mage/server/ChatSession.java +++ b/Mage.Server/src/main/java/mage/server/ChatSession.java @@ -3,6 +3,7 @@ package mage.server; import mage.game.Game; import mage.interfaces.callback.ClientCallback; import mage.interfaces.callback.ClientCallbackMethod; +import mage.server.managers.ManagerFactory; import mage.view.ChatMessage; import mage.view.ChatMessage.MessageColor; import mage.view.ChatMessage.MessageType; @@ -25,6 +26,7 @@ public class ChatSession { private static final Logger logger = Logger.getLogger(ChatSession.class); private static final DateFormat timeFormatter = DateFormat.getTimeInstance(DateFormat.SHORT); + private final ManagerFactory managerFactory; private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final ConcurrentMap clients = new ConcurrentHashMap<>(); @@ -32,14 +34,15 @@ public class ChatSession { private final Date createTime; private final String info; - public ChatSession(String info) { + public ChatSession(ManagerFactory managerFactory, String info) { + this.managerFactory = managerFactory; chatId = UUID.randomUUID(); this.createTime = new Date(); this.info = info; } public void join(UUID userId) { - UserManager.instance.getUser(userId).ifPresent(user -> { + managerFactory.userManager().getUser(userId).ifPresent(user -> { if (!clients.containsKey(userId)) { String userName = user.getName(); final Lock w = lock.writeLock(); @@ -121,7 +124,7 @@ public class ChatSession { r.unlock(); } for (UUID userId : chatUserIds) { - Optional user = UserManager.instance.getUser(userId); + Optional user = managerFactory.userManager().getUser(userId); if (user.isPresent()) { user.get().fireCallback(clientCallback); } else { diff --git a/Mage.Server/src/main/java/mage/server/GmailClient.java b/Mage.Server/src/main/java/mage/server/GmailClient.java deleted file mode 100644 index a091ede4d4a..00000000000 --- a/Mage.Server/src/main/java/mage/server/GmailClient.java +++ /dev/null @@ -1,91 +0,0 @@ -package mage.server; - -import com.google.api.client.auth.oauth2.Credential; -import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp; -import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver; -import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; -import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; -import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; -import com.google.api.client.http.HttpTransport; -import com.google.api.client.json.JsonFactory; -import com.google.api.client.json.jackson2.JacksonFactory; -import com.google.api.client.util.Base64; -import com.google.api.client.util.store.FileDataStoreFactory; -import com.google.api.services.gmail.Gmail; -import com.google.api.services.gmail.Gmail.Builder; -import com.google.api.services.gmail.GmailScopes; -import com.google.api.services.gmail.model.Message; -import java.io.ByteArrayOutputStream; -import java.io.FileReader; -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.util.Collections; -import java.util.Properties; -import javax.mail.MessagingException; -import javax.mail.Session; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeMessage; -import mage.server.util.ConfigSettings; -import org.apache.log4j.Logger; - -public final class GmailClient { - - private static final Logger logger = Logger.getLogger(Main.class); - private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance(); - private static final java.io.File DATA_STORE_DIR = new java.io.File(System.getProperty("user.home"), ".store/xmage"); - private static FileDataStoreFactory dataStoreFactory; - private static HttpTransport httpTransport; - private static Credential credential; - - public static boolean initilize() { - try { - dataStoreFactory = new FileDataStoreFactory(DATA_STORE_DIR); - httpTransport = GoogleNetHttpTransport.newTrustedTransport(); - - GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new FileReader("client_secrets.json")); - if (clientSecrets.getDetails().getClientId().startsWith("Enter") - || clientSecrets.getDetails().getClientSecret().startsWith("Enter ")) { - logger.error("client_secrets.json not found"); - return false; - } - - GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( - httpTransport, JSON_FACTORY, clientSecrets, - Collections.singleton(GmailScopes.GMAIL_COMPOSE)).setDataStoreFactory( - dataStoreFactory).build(); - - credential = new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user"); - return true; - } catch (IOException | GeneralSecurityException ex) { - logger.error("Error initializing GmailClient", ex); - } - return false; - } - - public static boolean sendMessage(String email, String subject, String text) { - if (email.isEmpty()) { - logger.info("Email is not sent because the address is empty"); - return false; - } - try { - Gmail gmail = new Builder(httpTransport, JSON_FACTORY, credential).setApplicationName("XMage Server").build(); - - MimeMessage mimeMessage = new MimeMessage(Session.getDefaultInstance(new Properties())); - mimeMessage.addRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress(email)); - mimeMessage.setSubject(subject); - mimeMessage.setText(text); - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - mimeMessage.writeTo(baos); - Message message = new Message(); - message.setRaw(Base64.encodeBase64URLSafeString(baos.toByteArray())); - - gmail.users().messages().send(ConfigSettings.instance.getGoogleAccount() - + (ConfigSettings.instance.getGoogleAccount().endsWith("@gmail.com") ? "" : "@gmail.com"), message).execute(); - return true; - } catch (MessagingException | IOException ex) { - logger.error("Error sending message", ex); - } - return false; - } -} diff --git a/Mage.Server/src/main/java/mage/server/MageServerImpl.java b/Mage.Server/src/main/java/mage/server/MageServerImpl.java index e668311ac72..ed545b64679 100644 --- a/Mage.Server/src/main/java/mage/server/MageServerImpl.java +++ b/Mage.Server/src/main/java/mage/server/MageServerImpl.java @@ -23,15 +23,14 @@ import mage.players.PlayerType; import mage.players.net.UserData; import mage.remote.MageVersionException; import mage.server.draft.CubeFactory; -import mage.server.draft.DraftManager; -import mage.server.game.*; +import mage.server.game.GameFactory; +import mage.server.game.GamesRoom; +import mage.server.game.PlayerFactory; +import mage.server.managers.ManagerFactory; import mage.server.services.impl.FeedbackServiceImpl; import mage.server.tournament.TournamentFactory; -import mage.server.tournament.TournamentManager; -import mage.server.util.ConfigSettings; import mage.server.util.ServerMessagesUtil; import mage.server.util.SystemUtil; -import mage.server.util.ThreadExecutor; import mage.utils.*; import mage.view.*; import mage.view.ChatMessage.MessageColor; @@ -49,9 +48,10 @@ import java.util.concurrent.ExecutorService; public class MageServerImpl implements MageServer { private static final Logger logger = Logger.getLogger(MageServerImpl.class); - private static final ExecutorService callExecutor = ThreadExecutor.instance.getCallExecutor(); + private final ExecutorService callExecutor; private static final SecureRandom RANDOM = new SecureRandom(); + private final ManagerFactory managerFactory; private final String adminPassword; private final boolean testMode; private final LinkedHashMap activeAuthTokens = new LinkedHashMap() { @@ -62,15 +62,17 @@ public class MageServerImpl implements MageServer { } }; - public MageServerImpl(String adminPassword, boolean testMode) { + public MageServerImpl(ManagerFactory managerFactory, String adminPassword, boolean testMode) { + this.managerFactory = managerFactory; this.adminPassword = adminPassword; this.testMode = testMode; + this.callExecutor = managerFactory.threadExecutor().getCallExecutor(); ServerMessagesUtil.instance.getMessages(); } @Override public boolean registerUser(String sessionId, String userName, String password, String email) throws MageException { - return SessionManager.instance.registerUser(sessionId, userName, password, email); + return managerFactory.sessionManager().registerUser(sessionId, userName, password, email); } // generateAuthToken returns a uniformly distributed 6-digits string. @@ -80,7 +82,7 @@ public class MageServerImpl implements MageServer { @Override public boolean emailAuthToken(String sessionId, String email) throws MageException { - if (!ConfigSettings.instance.isAuthenticationActivated()) { + if (!managerFactory.configSettings().isAuthenticationActivated()) { sendErrorMessageToClient(sessionId, "Registration is disabled by the server config"); return false; } @@ -96,10 +98,10 @@ public class MageServerImpl implements MageServer { String text = "Use this auth token to reset " + authorizedUser.name + "'s password: " + authToken + '\n' + "It's valid until the next server restart."; boolean success; - if (!ConfigSettings.instance.getMailUser().isEmpty()) { - success = MailClient.sendMessage(email, subject, text); + if (!managerFactory.configSettings().getMailUser().isEmpty()) { + success = managerFactory.mailClient().sendMessage(email, subject, text); } else { - success = MailgunClient.sendMessage(email, subject, text); + success = managerFactory.mailgunClient().sendMessage(email, subject, text); } if (!success) { sendErrorMessageToClient(sessionId, "There was an error inside the server while emailing an auth token"); @@ -110,7 +112,7 @@ public class MageServerImpl implements MageServer { @Override public boolean resetPassword(String sessionId, String email, String authToken, String password) throws MageException { - if (!ConfigSettings.instance.isAuthenticationActivated()) { + if (!managerFactory.configSettings().isAuthenticationActivated()) { sendErrorMessageToClient(sessionId, "Registration is disabled by the server config"); return false; } @@ -139,7 +141,7 @@ public class MageServerImpl implements MageServer { logger.info("MageVersionException: userName=" + userName + ", version=" + version + " sessionId=" + sessionId); throw new MageVersionException(version, Main.getVersion()); } - return SessionManager.instance.connectUser(sessionId, userName, password, userIdStr); + return managerFactory.sessionManager().connectUser(sessionId, userName, password, userIdStr); } catch (MageException ex) { if (ex instanceof MageVersionException) { throw ex; @@ -154,7 +156,7 @@ public class MageServerImpl implements MageServer { return executeWithResult("setUserData", sessionId, new ActionWithBooleanResult() { @Override public Boolean execute() throws MageException { - return SessionManager.instance.setUserData(userName, sessionId, userData, clientVersion, userIdStr); + return managerFactory.sessionManager().setUserData(userName, sessionId, userData, clientVersion, userIdStr); } }); } @@ -168,7 +170,7 @@ public class MageServerImpl implements MageServer { if (!adminPassword.equals(this.adminPassword)) { throw new MageException("Wrong password"); } - return SessionManager.instance.connectAdmin(sessionId); + return managerFactory.sessionManager().connectAdmin(sessionId); } catch (Exception ex) { handleException(ex); } @@ -186,13 +188,13 @@ public class MageServerImpl implements MageServer { @Override public TableView execute() throws MageException { try { - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (!session.isPresent()) { logger.error("Session to found : " + sessionId); return null; } UUID userId = session.get().getUserId(); - Optional _user = UserManager.instance.getUser(userId); + Optional _user = managerFactory.userManager().getUser(userId); if (!_user.isPresent()) { logger.error("User for session not found. session = " + sessionId); return null; @@ -205,7 +207,7 @@ public class MageServerImpl implements MageServer { throw new MageException("No message"); } // check AI players max - String maxAiOpponents = ConfigSettings.instance.getMaxAiOpponents(); + String maxAiOpponents = managerFactory.configSettings().getMaxAiOpponents(); if (maxAiOpponents != null) { int aiPlayers = 0; for (PlayerType playerType : options.getPlayerTypes()) { @@ -241,7 +243,7 @@ public class MageServerImpl implements MageServer { user.showUserMessage("Create tournament", message); throw new MageException("No message"); } - Optional room = GamesRoomManager.instance.getRoom(roomId); + Optional room = managerFactory.gamesRoomManager().getRoom(roomId); if (!room.isPresent()) { } else { @@ -260,9 +262,9 @@ public class MageServerImpl implements MageServer { @Override public void removeTable(final String sessionId, final UUID roomId, final UUID tableId) throws MageException { execute("removeTable", sessionId, () -> { - SessionManager.instance.getSession(sessionId).ifPresent(session -> { + managerFactory.sessionManager().getSession(sessionId).ifPresent(session -> { UUID userId = session.getUserId(); - TableManager.instance.removeTable(userId, tableId); + managerFactory.tableManager().removeTable(userId, tableId); }); }); } @@ -272,7 +274,7 @@ public class MageServerImpl implements MageServer { return executeWithResult("joinTable", sessionId, new ActionWithBooleanResult() { @Override public Boolean execute() throws MageException { - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (!session.isPresent()) { return false; } @@ -282,7 +284,7 @@ public class MageServerImpl implements MageServer { logger.fatal("Got no userId from sessionId" + sessionId + " tableId" + tableId); return false; } - Optional room = GamesRoomManager.instance.getRoom(roomId); + Optional room = managerFactory.gamesRoomManager().getRoom(roomId); if (!room.isPresent()) { return false; } @@ -297,20 +299,20 @@ public class MageServerImpl implements MageServer { return executeWithResult("joinTournamentTable", sessionId, new ActionWithBooleanResult() { @Override public Boolean execute() throws MageException { - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (!session.isPresent()) { return false; } UUID userId = session.get().getUserId(); if (logger.isTraceEnabled()) { - Optional user = UserManager.instance.getUser(userId); + Optional user = managerFactory.userManager().getUser(userId); user.ifPresent(user1 -> logger.trace("join tourn. tableId: " + tableId + ' ' + name)); } if (userId == null) { logger.fatal("Got no userId from sessionId" + sessionId + " tableId" + tableId); return false; } - Optional room = GamesRoomManager.instance.getRoom(roomId); + Optional room = managerFactory.gamesRoomManager().getRoom(roomId); if (room.isPresent()) { return room.get().joinTournamentTable(userId, tableId, name, playerType, skill, deckList, password); } @@ -325,12 +327,12 @@ public class MageServerImpl implements MageServer { return executeWithResult("submitDeck", sessionId, new ActionWithBooleanResult() { @Override public Boolean execute() throws MageException { - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (!session.isPresent()) { return false; } else { UUID userId = session.get().getUserId(); - boolean ret = TableManager.instance.submitDeck(userId, tableId, deckList); + boolean ret = managerFactory.tableManager().submitDeck(userId, tableId, deckList); logger.debug("Session " + sessionId + " submitted deck"); return ret; } @@ -341,13 +343,13 @@ public class MageServerImpl implements MageServer { @Override public void updateDeck(final String sessionId, final UUID tableId, final DeckCardLists deckList) throws MageException { execute("updateDeck", sessionId, () -> { - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (!session.isPresent()) { logger.error("Session not found : " + sessionId); } else { UUID userId = session.get().getUserId(); - TableManager.instance.updateDeck(userId, tableId, deckList); + managerFactory.tableManager().updateDeck(userId, tableId, deckList); logger.trace("Session " + sessionId + " updated deck"); } }); @@ -357,7 +359,7 @@ public class MageServerImpl implements MageServer { //FIXME: why no sessionId here??? public List getTables(UUID roomId) throws MageException { try { - Optional room = GamesRoomManager.instance.getRoom(roomId); + Optional room = managerFactory.gamesRoomManager().getRoom(roomId); if (room.isPresent()) { return room.get().getTables(); } else { @@ -373,7 +375,7 @@ public class MageServerImpl implements MageServer { //FIXME: why no sessionId here??? public List getFinishedMatches(UUID roomId) throws MageException { try { - return GamesRoomManager.instance.getRoom(roomId).map(GamesRoom::getFinished).orElse(new ArrayList<>()); + return managerFactory.gamesRoomManager().getRoom(roomId).map(GamesRoom::getFinished).orElse(new ArrayList<>()); } catch (Exception ex) { handleException(ex); } @@ -383,7 +385,7 @@ public class MageServerImpl implements MageServer { @Override public List getRoomUsers(UUID roomId) throws MageException { try { - Optional room = GamesRoomManager.instance.getRoom(roomId); + Optional room = managerFactory.gamesRoomManager().getRoom(roomId); if (room.isPresent()) { return room.get().getRoomUsersInfo(); } else { @@ -399,7 +401,7 @@ public class MageServerImpl implements MageServer { //FIXME: why no sessionId here??? public TableView getTable(UUID roomId, UUID tableId) throws MageException { try { - Optional room = GamesRoomManager.instance.getRoom(roomId); + Optional room = managerFactory.gamesRoomManager().getRoom(roomId); return room.flatMap(r -> r.getTable(tableId)).orElse(null); } catch (Exception ex) { @@ -410,22 +412,12 @@ public class MageServerImpl implements MageServer { @Override public boolean ping(String sessionId, String pingInfo) { - return SessionManager.instance.extendUserSession(sessionId, pingInfo); + return managerFactory.sessionManager().extendUserSession(sessionId, pingInfo); } - // @Override -// public void deregisterClient(final String sessionId) throws MageException { -// execute("deregisterClient", sessionId, new Action() { -// @Override -// public void execute() { -// SessionManager.instance.disconnect(sessionId, true); -// logger.debug("Client deregistered ..."); -// } -// }); -// } @Override public boolean startMatch(final String sessionId, final UUID roomId, final UUID tableId) throws MageException { - Optional controller = TableManager.instance.getController(tableId); + Optional controller = managerFactory.tableManager().getController(tableId); if (!controller.isPresent()) { logger.error("table not found : " + tableId); return false; @@ -434,30 +426,20 @@ public class MageServerImpl implements MageServer { return false; } execute("startMatch", sessionId, () -> { - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (!session.isPresent()) { logger.error("Session not found : " + sessionId); } else { UUID userId = session.get().getUserId(); - TableManager.instance.startMatch(userId, roomId, tableId); + managerFactory.tableManager().startMatch(userId, roomId, tableId); } }); return true; } - // @Override -// public void startChallenge(final String sessionId, final UUID roomId, final UUID tableId, final UUID challengeId) throws MageException { -// execute("startChallenge", sessionId, new Action() { -// @Override -// public void execute() { -// UUID userId = SessionManager.instance.getSession(sessionId).getUserId(); -// TableManager.instance.startChallenge(userId, roomId, tableId, challengeId); -// } -// }); -// } @Override public boolean startTournament(final String sessionId, final UUID roomId, final UUID tableId) throws MageException { - Optional controller = TableManager.instance.getController(tableId); + Optional controller = managerFactory.tableManager().getController(tableId); if (!controller.isPresent()) { logger.error("table not found : " + tableId); return false; @@ -466,12 +448,12 @@ public class MageServerImpl implements MageServer { return false; } execute("startTournament", sessionId, () -> { - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (!session.isPresent()) { logger.error("Session not found : " + sessionId); } else { UUID userId = session.get().getUserId(); - TableManager.instance.startTournament(userId, roomId, tableId); + managerFactory.tableManager().startTournament(userId, roomId, tableId); } }); return true; @@ -481,7 +463,7 @@ public class MageServerImpl implements MageServer { //FIXME: why no sessionId here??? public TournamentView getTournament(UUID tournamentId) throws MageException { try { - return TournamentManager.instance.getTournamentView(tournamentId); + return managerFactory.tournamentManager().getTournamentView(tournamentId); } catch (Exception ex) { handleException(ex); } @@ -493,7 +475,7 @@ public class MageServerImpl implements MageServer { public void sendChatMessage(final UUID chatId, final String userName, final String message) throws MageException { try { callExecutor.execute( - () -> ChatManager.instance.broadcast(chatId, userName, StringEscapeUtils.escapeHtml4(message), MessageColor.BLUE, true, null, ChatMessage.MessageType.TALK, null) + () -> managerFactory.chatManager().broadcast(chatId, userName, StringEscapeUtils.escapeHtml4(message), MessageColor.BLUE, true, null, ChatMessage.MessageType.TALK, null) ); } catch (Exception ex) { handleException(ex); @@ -503,10 +485,10 @@ public class MageServerImpl implements MageServer { @Override public void joinChat(final UUID chatId, final String sessionId, final String userName) throws MageException { execute("joinChat", sessionId, () -> { - SessionManager.instance.getSession(sessionId).ifPresent(session -> { + managerFactory.sessionManager().getSession(sessionId).ifPresent(session -> { UUID userId = session.getUserId(); - ChatManager.instance.joinChat(chatId, userId); + managerFactory.chatManager().joinChat(chatId, userId); }); }); } @@ -515,9 +497,9 @@ public class MageServerImpl implements MageServer { public void leaveChat(final UUID chatId, final String sessionId) throws MageException { execute("leaveChat", sessionId, () -> { if (chatId != null) { - SessionManager.instance.getSession(sessionId).ifPresent(session -> { + managerFactory.sessionManager().getSession(sessionId).ifPresent(session -> { UUID userId = session.getUserId(); - ChatManager.instance.leaveChat(chatId, userId); + managerFactory.chatManager().leaveChat(chatId, userId); }); } }); @@ -527,7 +509,7 @@ public class MageServerImpl implements MageServer { //FIXME: why no sessionId here??? public UUID getMainRoomId() throws MageException { try { - return GamesRoomManager.instance.getMainRoomId(); + return managerFactory.gamesRoomManager().getMainRoomId(); } catch (Exception ex) { handleException(ex); } @@ -538,7 +520,7 @@ public class MageServerImpl implements MageServer { //FIXME: why no sessionId here??? public UUID getRoomChatId(UUID roomId) throws MageException { try { - Optional room = GamesRoomManager.instance.getRoom(roomId); + Optional room = managerFactory.gamesRoomManager().getRoom(roomId); if (!room.isPresent()) { logger.error("roomId not found : " + roomId); return null; @@ -555,12 +537,12 @@ public class MageServerImpl implements MageServer { return executeWithResult("isTableOwner", sessionId, new ActionWithBooleanResult() { @Override public Boolean execute() { - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (!session.isPresent()) { return false; } else { UUID userId = session.get().getUserId(); - return TableManager.instance.isTableOwner(tableId, userId); + return managerFactory.tableManager().isTableOwner(tableId, userId); } } }); @@ -569,16 +551,16 @@ public class MageServerImpl implements MageServer { @Override public void swapSeats(final String sessionId, final UUID roomId, final UUID tableId, final int seatNum1, final int seatNum2) throws MageException { execute("swapSeats", sessionId, () -> { - SessionManager.instance.getSession(sessionId).ifPresent(session -> { + managerFactory.sessionManager().getSession(sessionId).ifPresent(session -> { UUID userId = session.getUserId(); - TableManager.instance.swapSeats(tableId, userId, seatNum1, seatNum2); + managerFactory.tableManager().swapSeats(tableId, userId, seatNum1, seatNum2); }); }); } @Override public boolean leaveTable(final String sessionId, final UUID roomId, final UUID tableId) throws MageException { - Optional tableController = TableManager.instance.getController(tableId); + Optional tableController = managerFactory.tableManager().getController(tableId); if (tableController.isPresent()) { TableState tableState = tableController.get().getTableState(); if (tableState != TableState.WAITING && tableState != TableState.READY_TO_START) { @@ -586,9 +568,9 @@ public class MageServerImpl implements MageServer { return false; } execute("leaveTable", sessionId, () -> { - SessionManager.instance.getSession(sessionId).ifPresent(session -> { + managerFactory.sessionManager().getSession(sessionId).ifPresent(session -> { UUID userId = session.getUserId(); - GamesRoomManager.instance.getRoom(roomId).ifPresent(room -> + managerFactory.gamesRoomManager().getRoom(roomId).ifPresent(room -> room.leaveTable(userId, tableId)); }); @@ -604,7 +586,7 @@ public class MageServerImpl implements MageServer { //FIXME: why no sessionId here??? public UUID getTableChatId(UUID tableId) throws MageException { try { - return TableManager.instance.getChatId(tableId).orElse(null); + return managerFactory.tableManager().getChatId(tableId).orElse(null); } catch (Exception ex) { handleException(ex); } @@ -614,9 +596,9 @@ public class MageServerImpl implements MageServer { @Override public void joinGame(final UUID gameId, final String sessionId) throws MageException { execute("joinGame", sessionId, () -> { - SessionManager.instance.getSession(sessionId).ifPresent(session -> { + managerFactory.sessionManager().getSession(sessionId).ifPresent(session -> { UUID userId = session.getUserId(); - GameManager.instance.joinGame(gameId, userId); + managerFactory.gameManager().joinGame(gameId, userId); }); }); } @@ -624,9 +606,9 @@ public class MageServerImpl implements MageServer { @Override public void joinDraft(final UUID draftId, final String sessionId) throws MageException { execute("joinDraft", sessionId, () -> { - SessionManager.instance.getSession(sessionId).ifPresent(session -> { + managerFactory.sessionManager().getSession(sessionId).ifPresent(session -> { UUID userId = session.getUserId(); - DraftManager.instance.joinDraft(draftId, userId); + managerFactory.draftManager().joinDraft(draftId, userId); }); }); } @@ -634,12 +616,12 @@ public class MageServerImpl implements MageServer { @Override public void joinTournament(final UUID tournamentId, final String sessionId) throws MageException { execute("joinTournament", sessionId, () -> { - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (!session.isPresent()) { logger.error("Session not found : " + sessionId); } else { UUID userId = session.get().getUserId(); - TournamentManager.instance.joinTournament(tournamentId, userId); + managerFactory.tournamentManager().joinTournament(tournamentId, userId); } }); } @@ -648,7 +630,7 @@ public class MageServerImpl implements MageServer { //FIXME: why no sessionId here??? public UUID getGameChatId(UUID gameId) throws MageException { try { - return GameManager.instance.getChatId(gameId).orElse(null); + return managerFactory.gameManager().getChatId(gameId).orElse(null); } catch (Exception ex) { handleException(ex); } @@ -659,7 +641,7 @@ public class MageServerImpl implements MageServer { //FIXME: why no sessionId here??? public UUID getTournamentChatId(UUID tournamentId) throws MageException { try { - return TournamentManager.instance.getChatId(tournamentId).orElse(null); + return managerFactory.tournamentManager().getChatId(tournamentId).orElse(null); } catch (Exception ex) { handleException(ex); } @@ -669,9 +651,8 @@ public class MageServerImpl implements MageServer { @Override public void sendPlayerUUID(final UUID gameId, final String sessionId, final UUID data) throws MageException { execute("sendPlayerUUID", sessionId, () -> { - Optional user = SessionManager.instance.getUser(sessionId); + Optional user = managerFactory.sessionManager().getUser(sessionId); if (user.isPresent()) { -// logger.warn("sendPlayerUUID gameId=" + gameId + " sessionId=" + sessionId + " username=" + user.getName()); user.get().sendPlayerUUID(gameId, data); } else { logger.warn("Your session expired: gameId=" + gameId + ", sessionId=" + sessionId); @@ -682,7 +663,7 @@ public class MageServerImpl implements MageServer { @Override public void sendPlayerString(final UUID gameId, final String sessionId, final String data) throws MageException { execute("sendPlayerString", sessionId, () -> { - Optional user = SessionManager.instance.getUser(sessionId); + Optional user = managerFactory.sessionManager().getUser(sessionId); if (user.isPresent()) { user.get().sendPlayerString(gameId, data); } else { @@ -694,7 +675,7 @@ public class MageServerImpl implements MageServer { @Override public void sendPlayerManaType(final UUID gameId, final UUID playerId, final String sessionId, final ManaType data) throws MageException { execute("sendPlayerManaType", sessionId, () -> { - Optional user = SessionManager.instance.getUser(sessionId); + Optional user = managerFactory.sessionManager().getUser(sessionId); if (user.isPresent()) { user.get().sendPlayerManaType(gameId, playerId, data); } else { @@ -706,7 +687,7 @@ public class MageServerImpl implements MageServer { @Override public void sendPlayerBoolean(final UUID gameId, final String sessionId, final Boolean data) throws MageException { execute("sendPlayerBoolean", sessionId, () -> { - Optional user = SessionManager.instance.getUser(sessionId); + Optional user = managerFactory.sessionManager().getUser(sessionId); if (user.isPresent()) { user.get().sendPlayerBoolean(gameId, data); } else { @@ -718,7 +699,7 @@ public class MageServerImpl implements MageServer { @Override public void sendPlayerInteger(final UUID gameId, final String sessionId, final Integer data) throws MageException { execute("sendPlayerInteger", sessionId, () -> { - Optional user = SessionManager.instance.getUser(sessionId); + Optional user = managerFactory.sessionManager().getUser(sessionId); if (user.isPresent()) { user.get().sendPlayerInteger(gameId, data); } else { @@ -735,9 +716,9 @@ public class MageServerImpl implements MageServer { @Override public void sendCardMark(final UUID draftId, final String sessionId, final UUID cardPick) throws MageException { execute("sendCardMark", sessionId, () -> { - SessionManager.instance.getSession(sessionId).ifPresent(session -> { + managerFactory.sessionManager().getSession(sessionId).ifPresent(session -> { UUID userId = session.getUserId(); - DraftManager.instance.sendCardMark(draftId, userId, cardPick); + managerFactory.draftManager().sendCardMark(draftId, userId, cardPick); }); }); } @@ -748,9 +729,9 @@ public class MageServerImpl implements MageServer { try { callExecutor.execute( () -> { - SessionManager.instance.getSession(sessionId).ifPresent(session -> { + managerFactory.sessionManager().getSession(sessionId).ifPresent(session -> { UUID userId = session.getUserId(); - GameManager.instance.quitMatch(gameId, userId); + managerFactory.gameManager().quitMatch(gameId, userId); }); } ); @@ -766,10 +747,10 @@ public class MageServerImpl implements MageServer { try { callExecutor.execute( () -> { - SessionManager.instance.getSession(sessionId).ifPresent(session -> { + managerFactory.sessionManager().getSession(sessionId).ifPresent(session -> { UUID userId = session.getUserId(); - TournamentManager.instance.quit(tournamentId, userId); + managerFactory.tournamentManager().quit(tournamentId, userId); }); } ); @@ -786,14 +767,14 @@ public class MageServerImpl implements MageServer { try { callExecutor.execute( () -> { - SessionManager.instance.getSession(sessionId).ifPresent( + managerFactory.sessionManager().getSession(sessionId).ifPresent( session -> { UUID userId = session.getUserId(); - UUID tableId = DraftManager.instance.getControllerByDraftId(draftId).getTableId(); - Table table = TableManager.instance.getTable(tableId); + UUID tableId = managerFactory.draftManager().getControllerByDraftId(draftId).getTableId(); + Table table = managerFactory.tableManager().getTable(tableId); if (table.isTournament()) { UUID tournamentId = table.getTournament().getId(); - TournamentManager.instance.quit(tournamentId, userId); + managerFactory.tournamentManager().quit(tournamentId, userId); } }); } @@ -808,9 +789,9 @@ public class MageServerImpl implements MageServer { @Override public void sendPlayerAction(final PlayerAction playerAction, final UUID gameId, final String sessionId, final Object data) throws MageException { execute("sendPlayerAction", sessionId, () -> { - SessionManager.instance.getSession(sessionId).ifPresent(session -> { + managerFactory.sessionManager().getSession(sessionId).ifPresent(session -> { UUID userId = session.getUserId(); - GameManager.instance.sendPlayerAction(playerAction, gameId, userId, data); + managerFactory.gameManager().sendPlayerAction(playerAction, gameId, userId, data); }); }); } @@ -820,14 +801,14 @@ public class MageServerImpl implements MageServer { return executeWithResult("setUserData", sessionId, new ActionWithBooleanResult() { @Override public Boolean execute() throws MageException { - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (!session.isPresent()) { logger.error("Session not found : " + sessionId); return false; } else { UUID userId = session.get().getUserId(); - if (GamesRoomManager.instance.getRoom(roomId).isPresent()) { - return GamesRoomManager.instance.getRoom(roomId).get().watchTable(userId, tableId); + if (managerFactory.gamesRoomManager().getRoom(roomId).isPresent()) { + return managerFactory.gamesRoomManager().getRoom(roomId).get().watchTable(userId, tableId); } else { return false; } @@ -846,10 +827,10 @@ public class MageServerImpl implements MageServer { return executeWithResult("watchGame", sessionId, new ActionWithResult() { @Override public Boolean execute() throws MageException { - return SessionManager.instance.getSession(sessionId) + return managerFactory.sessionManager().getSession(sessionId) .map(session -> { UUID userId = session.getUserId(); - return GameManager.instance.watchGame(gameId, userId); + return managerFactory.gameManager().watchGame(gameId, userId); }).orElse(false); } @@ -863,10 +844,10 @@ public class MageServerImpl implements MageServer { @Override public void stopWatching(final UUID gameId, final String sessionId) throws MageException { execute("stopWatching", sessionId, () -> { - SessionManager.instance.getSession(sessionId).ifPresent(session -> { + managerFactory.sessionManager().getSession(sessionId).ifPresent(session -> { UUID userId = session.getUserId(); - UserManager.instance.getUser(userId).ifPresent(user -> { - GameManager.instance.stopWatching(gameId, userId); + managerFactory.userManager().getUser(userId).ifPresent(user -> { + managerFactory.gameManager().stopWatching(gameId, userId); user.removeGameWatchInfo(gameId); }); }); @@ -877,9 +858,9 @@ public class MageServerImpl implements MageServer { @Override public void replayGame(final UUID gameId, final String sessionId) throws MageException { execute("replayGame", sessionId, () -> { - SessionManager.instance.getSession(sessionId).ifPresent(session -> { + managerFactory.sessionManager().getSession(sessionId).ifPresent(session -> { UUID userId = session.getUserId(); - ReplayManager.instance.replayGame(gameId, userId); + managerFactory.replayManager().replayGame(gameId, userId); }); }); } @@ -887,9 +868,9 @@ public class MageServerImpl implements MageServer { @Override public void startReplay(final UUID gameId, final String sessionId) throws MageException { execute("startReplay", sessionId, () -> { - SessionManager.instance.getSession(sessionId).ifPresent(session -> { + managerFactory.sessionManager().getSession(sessionId).ifPresent(session -> { UUID userId = session.getUserId(); - ReplayManager.instance.startReplay(gameId, userId); + managerFactory.replayManager().startReplay(gameId, userId); }); }); } @@ -897,12 +878,12 @@ public class MageServerImpl implements MageServer { @Override public void stopReplay(final UUID gameId, final String sessionId) throws MageException { execute("stopReplay", sessionId, () -> { - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (!session.isPresent()) { logger.error("Session not found : " + sessionId); } else { UUID userId = session.get().getUserId(); - ReplayManager.instance.stopReplay(gameId, userId); + managerFactory.replayManager().stopReplay(gameId, userId); } }); } @@ -910,12 +891,12 @@ public class MageServerImpl implements MageServer { @Override public void nextPlay(final UUID gameId, final String sessionId) throws MageException { execute("nextPlay", sessionId, () -> { - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (!session.isPresent()) { logger.error("Session not found : " + sessionId); } else { UUID userId = session.get().getUserId(); - ReplayManager.instance.nextPlay(gameId, userId); + managerFactory.replayManager().nextPlay(gameId, userId); } }); } @@ -923,12 +904,12 @@ public class MageServerImpl implements MageServer { @Override public void previousPlay(final UUID gameId, final String sessionId) throws MageException { execute("previousPlay", sessionId, () -> { - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (!session.isPresent()) { logger.error("Session not found : " + sessionId); } else { UUID userId = session.get().getUserId(); - ReplayManager.instance.previousPlay(gameId, userId); + managerFactory.replayManager().previousPlay(gameId, userId); } }); } @@ -936,12 +917,12 @@ public class MageServerImpl implements MageServer { @Override public void skipForward(final UUID gameId, final String sessionId, final int moves) throws MageException { execute("skipForward", sessionId, () -> { - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (!session.isPresent()) { logger.error("Session not found : " + sessionId); } else { UUID userId = session.get().getUserId(); - ReplayManager.instance.skipForward(gameId, userId, moves); + managerFactory.replayManager().skipForward(gameId, userId, moves); } }); } @@ -971,9 +952,9 @@ public class MageServerImpl implements MageServer { public void cheat(final UUID gameId, final String sessionId, final UUID playerId, final DeckCardLists deckList) throws MageException { execute("cheat", sessionId, () -> { if (testMode) { - SessionManager.instance.getSession(sessionId).ifPresent(session -> { + managerFactory.sessionManager().getSession(sessionId).ifPresent(session -> { UUID userId = session.getUserId(); - GameManager.instance.cheat(gameId, userId, playerId, deckList); + managerFactory.gameManager().cheat(gameId, userId, playerId, deckList); }); } }); @@ -985,12 +966,12 @@ public class MageServerImpl implements MageServer { @Override public Boolean execute() { if (testMode) { - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (!session.isPresent()) { logger.error("Session not found : " + sessionId); } else { UUID userId = session.get().getUserId(); - return GameManager.instance.cheat(gameId, userId, playerId, cardName); + return managerFactory.gameManager().cheat(gameId, userId, playerId, cardName); } } return false; @@ -1024,13 +1005,13 @@ public class MageServerImpl implements MageServer { @Override public void disconnectUser(final String sessionId, final String userSessionId) throws MageException { - execute("disconnectUser", sessionId, () -> SessionManager.instance.disconnectUser(sessionId, userSessionId)); + execute("disconnectUser", sessionId, () -> managerFactory.sessionManager().disconnectUser(sessionId, userSessionId)); } @Override public void muteUser(final String sessionId, final String userName, final long durationMinutes) throws MageException { execute("muteUser", sessionId, () -> { - UserManager.instance.getUserByName(userName).ifPresent(user -> { + managerFactory.userManager().getUserByName(userName).ifPresent(user -> { Date muteUntil = new Date(Calendar.getInstance().getTimeInMillis() + (durationMinutes * Timer.ONE_MINUTE)); user.showUserMessage("Admin info", "You were muted for chat messages until " + SystemUtil.dateFormat.format(muteUntil) + '.'); user.setChatLockedUntil(muteUntil); @@ -1042,12 +1023,12 @@ public class MageServerImpl implements MageServer { @Override public void lockUser(final String sessionId, final String userName, final long durationMinutes) throws MageException { execute("lockUser", sessionId, () -> { - UserManager.instance.getUserByName(userName).ifPresent(user -> { + managerFactory.userManager().getUserByName(userName).ifPresent(user -> { Date lockUntil = new Date(Calendar.getInstance().getTimeInMillis() + (durationMinutes * Timer.ONE_MINUTE)); user.showUserMessage("Admin info", "Your user profile was locked until " + SystemUtil.dateFormat.format(lockUntil) + '.'); user.setLockedUntil(lockUntil); if (user.isConnected()) { - SessionManager.instance.disconnectUser(sessionId, user.getSessionId()); + managerFactory.sessionManager().disconnectUser(sessionId, user.getSessionId()); } }); @@ -1058,15 +1039,15 @@ public class MageServerImpl implements MageServer { public void setActivation(final String sessionId, final String userName, boolean active) throws MageException { execute("setActivation", sessionId, () -> { AuthorizedUser authorizedUser = AuthorizedUserRepository.instance.getByName(userName); - Optional u = UserManager.instance.getUserByName(userName); + Optional u = managerFactory.userManager().getUserByName(userName); if (u.isPresent()) { User user = u.get(); user.setActive(active); if (!user.isActive() && user.isConnected()) { - SessionManager.instance.disconnectUser(sessionId, user.getSessionId()); + managerFactory.sessionManager().disconnectUser(sessionId, user.getSessionId()); } } else if (authorizedUser != null) { - User theUser = new User(userName, "localhost", authorizedUser); + User theUser = new User(managerFactory, userName, "localhost", authorizedUser); theUser.setActive(active); } @@ -1076,18 +1057,18 @@ public class MageServerImpl implements MageServer { @Override public void toggleActivation(final String sessionId, final String userName) throws MageException { execute("toggleActivation", sessionId, () - -> UserManager.instance.getUserByName(userName).ifPresent(user + -> managerFactory.userManager().getUserByName(userName).ifPresent(user -> { user.setActive(!user.isActive()); if (!user.isActive() && user.isConnected()) { - SessionManager.instance.disconnectUser(sessionId, user.getSessionId()); + managerFactory.sessionManager().disconnectUser(sessionId, user.getSessionId()); } })); } @Override public void endUserSession(final String sessionId, final String userSessionId) throws MageException { - execute("endUserSession", sessionId, () -> SessionManager.instance.endUserSession(sessionId, userSessionId)); + execute("endUserSession", sessionId, () -> managerFactory.sessionManager().endUserSession(sessionId, userSessionId)); } /** @@ -1100,9 +1081,9 @@ public class MageServerImpl implements MageServer { @Override public void removeTable(final String sessionId, final UUID tableId) throws MageException { execute("removeTable", sessionId, () -> { - SessionManager.instance.getSession(sessionId).ifPresent(session -> { + managerFactory.sessionManager().getSession(sessionId).ifPresent(session -> { UUID userId = session.getUserId(); - TableManager.instance.removeTable(userId, tableId); + managerFactory.tableManager().removeTable(userId, tableId); }); }); } @@ -1116,7 +1097,7 @@ public class MageServerImpl implements MageServer { public void sendFeedbackMessage(final String sessionId, final String username, final String title, final String type, final String message, final String email) throws MageException { if (title != null && message != null) { execute("sendFeedbackMessage", sessionId, () - -> SessionManager.instance.getSession(sessionId).ifPresent( + -> managerFactory.sessionManager().getSession(sessionId).ifPresent( session -> FeedbackServiceImpl.instance.feedback(username, title, type, message, email, session.getHost()) )); } @@ -1126,7 +1107,7 @@ public class MageServerImpl implements MageServer { public void sendBroadcastMessage(final String sessionId, final String message) throws MageException { if (message != null) { execute("sendBroadcastMessage", sessionId, () -> { - for (User user : UserManager.instance.getUsers()) { + for (User user : managerFactory.userManager().getUsers()) { if (message.toLowerCase(Locale.ENGLISH).startsWith("warn")) { user.fireCallback(new ClientCallback(ClientCallbackMethod.SERVER_MESSAGE, null, new ChatMessage("SERVER", message, null, null, MessageColor.RED))); } else { @@ -1138,12 +1119,12 @@ public class MageServerImpl implements MageServer { } private void sendErrorMessageToClient(final String sessionId, final String message) throws MageException { - execute("sendErrorMessageToClient", sessionId, () -> SessionManager.instance.sendErrorMessageToClient(sessionId, message)); + execute("sendErrorMessageToClient", sessionId, () -> managerFactory.sessionManager().sendErrorMessageToClient(sessionId, message)); } protected void execute(final String actionName, final String sessionId, final Action action, boolean checkAdminRights) throws MageException { if (checkAdminRights) { - if (!SessionManager.instance.isAdmin(sessionId)) { + if (!managerFactory.sessionManager().isAdmin(sessionId)) { return; } } @@ -1151,11 +1132,11 @@ public class MageServerImpl implements MageServer { } protected void execute(final String actionName, final String sessionId, final Action action) throws MageException { - if (SessionManager.instance.isValidSession(sessionId)) { + if (managerFactory.sessionManager().isValidSession(sessionId)) { try { callExecutor.execute( () -> { - if (SessionManager.instance.isValidSession(sessionId)) { + if (managerFactory.sessionManager().isValidSession(sessionId)) { try { action.execute(); } catch (MageException me) { @@ -1172,7 +1153,7 @@ public class MageServerImpl implements MageServer { protected T executeWithResult(String actionName, final String sessionId, final ActionWithResult action, boolean checkAdminRights) throws MageException { if (checkAdminRights) { - if (!SessionManager.instance.isAdmin(sessionId)) { + if (!managerFactory.sessionManager().isAdmin(sessionId)) { return action.negativeResult(); } } @@ -1181,7 +1162,7 @@ public class MageServerImpl implements MageServer { //TODO: also run in threads with future task protected T executeWithResult(String actionName, final String sessionId, final ActionWithResult action) throws MageException { - if (SessionManager.instance.isValidSession(sessionId)) { + if (managerFactory.sessionManager().isValidSession(sessionId)) { try { return action.execute(); } catch (Exception ex) { @@ -1218,15 +1199,15 @@ public class MageServerImpl implements MageServer { } } - private static class ListActionWithNullNegativeResult extends ActionWithNullNegativeResult> { + private class ListActionWithNullNegativeResult extends ActionWithNullNegativeResult> { @Override public List execute() throws MageException { - return UserManager.instance.getUserInfoList(); + return managerFactory.userManager().getUserInfoList(); } } - private static class GameViewActionWithNullNegativeResult extends ActionWithNullNegativeResult { + private class GameViewActionWithNullNegativeResult extends ActionWithNullNegativeResult { private final String sessionId; private final UUID gameId; @@ -1240,18 +1221,18 @@ public class MageServerImpl implements MageServer { @Override public GameView execute() throws MageException { - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (!session.isPresent()) { logger.error("Session not found : " + sessionId); return null; } else { //UUID userId = session.get().getUserId(); - return GameManager.instance.getGameView(gameId, playerId); + return managerFactory.gameManager().getGameView(gameId, playerId); } } } - private static class MyActionWithBooleanResult extends ActionWithBooleanResult { + private class MyActionWithBooleanResult extends ActionWithBooleanResult { private final String sessionId; private final UUID tableId; @@ -1263,17 +1244,17 @@ public class MageServerImpl implements MageServer { @Override public Boolean execute() throws MageException { - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (!session.isPresent()) { return false; } else { UUID userId = session.get().getUserId(); - return TableManager.instance.watchTable(userId, tableId); + return managerFactory.tableManager().watchTable(userId, tableId); } } } - private static class DraftPickViewActionWithNullNegativeResult extends ActionWithNullNegativeResult { + private class DraftPickViewActionWithNullNegativeResult extends ActionWithNullNegativeResult { private final String sessionId; private final UUID draftId; @@ -1289,9 +1270,9 @@ public class MageServerImpl implements MageServer { @Override public DraftPickView execute() { - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (session.isPresent()) { - return DraftManager.instance.sendCardPick(draftId, session.get().getUserId(), cardPick, hiddenCards); + return managerFactory.draftManager().sendCardPick(draftId, session.get().getUserId(), cardPick, hiddenCards); } else { logger.error("Session not found sessionId: " + sessionId + " draftId:" + draftId); } @@ -1299,7 +1280,7 @@ public class MageServerImpl implements MageServer { } } - private static class MyActionWithTableViewResult extends ActionWithTableViewResult { + private class MyActionWithTableViewResult extends ActionWithTableViewResult { private final String sessionId; private final MatchOptions options; @@ -1313,12 +1294,12 @@ public class MageServerImpl implements MageServer { @Override public TableView execute() throws MageException { - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (!session.isPresent()) { return null; } UUID userId = session.get().getUserId(); - Optional _user = UserManager.instance.getUser(userId); + Optional _user = managerFactory.userManager().getUser(userId); if (!_user.isPresent()) { logger.error("User for session not found. session = " + sessionId); return null; @@ -1349,13 +1330,13 @@ public class MageServerImpl implements MageServer { user.showUserMessage("Create table", message); throw new MageException("No message"); } - Optional room = GamesRoomManager.instance.getRoom(roomId); + Optional room = managerFactory.gamesRoomManager().getRoom(roomId); if (room.isPresent()) { TableView table = room.get().createTable(userId, options); if (logger.isDebugEnabled()) { logger.debug("TABLE created - tableId: " + table.getTableId() + ' ' + table.getTableName()); logger.debug("- " + user.getName() + " userId: " + user.getId()); - logger.debug("- chatId: " + TableManager.instance.getChatId(table.getTableId())); + logger.debug("- chatId: " + managerFactory.tableManager().getChatId(table.getTableId())); } return table; } else { diff --git a/Mage.Server/src/main/java/mage/server/MailClient.java b/Mage.Server/src/main/java/mage/server/MailClientImpl.java similarity index 80% rename from Mage.Server/src/main/java/mage/server/MailClient.java rename to Mage.Server/src/main/java/mage/server/MailClientImpl.java index 69e9518b774..6058fec1833 100644 --- a/Mage.Server/src/main/java/mage/server/MailClient.java +++ b/Mage.Server/src/main/java/mage/server/MailClientImpl.java @@ -1,25 +1,32 @@ package mage.server; -import java.util.Properties; +import mage.server.managers.ConfigSettings; +import mage.server.managers.MailClient; +import org.apache.log4j.Logger; + import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; -import mage.server.util.ConfigSettings; -import org.apache.log4j.Logger; +import java.util.Properties; -public final class MailClient { +public class MailClientImpl implements MailClient { private static final Logger logger = Logger.getLogger(Main.class); - public static boolean sendMessage(String email, String subject, String text) { + private final ConfigSettings config; + + public MailClientImpl(ConfigSettings config) { + this.config = config; + } + + public boolean sendMessage(String email, String subject, String text) { if (email.isEmpty()) { logger.info("Email is not sent because the address is empty"); return false; } - ConfigSettings config = ConfigSettings.instance; Properties properties = System.getProperties(); properties.setProperty("mail.smtps.host", config.getMailSmtpHost()); @@ -30,7 +37,7 @@ public final class MailClient { Session session = Session.getDefaultInstance(properties); - try{ + try { MimeMessage message = new MimeMessage(session); message.setFrom(new InternetAddress(config.getMailFromAddress())); message.addRecipient(Message.RecipientType.TO, new InternetAddress(email)); @@ -44,7 +51,7 @@ public final class MailClient { trnsport.close(); return true; - }catch (MessagingException ex) { + } catch (MessagingException ex) { logger.error("Error sending message to " + email, ex); } return false; diff --git a/Mage.Server/src/main/java/mage/server/MailgunClient.java b/Mage.Server/src/main/java/mage/server/MailgunClientImpl.java similarity index 65% rename from Mage.Server/src/main/java/mage/server/MailgunClient.java rename to Mage.Server/src/main/java/mage/server/MailgunClientImpl.java index 6cca110a0be..344a77fda29 100644 --- a/Mage.Server/src/main/java/mage/server/MailgunClient.java +++ b/Mage.Server/src/main/java/mage/server/MailgunClientImpl.java @@ -5,29 +5,37 @@ import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; import com.sun.jersey.core.util.MultivaluedMapImpl; -import javax.ws.rs.core.MediaType; -import mage.server.util.ConfigSettings; +import mage.server.managers.ConfigSettings; +import mage.server.managers.MailClient; import org.apache.log4j.Logger; -public final class MailgunClient { +import javax.ws.rs.core.MediaType; + +public class MailgunClientImpl implements MailClient { private static final Logger logger = Logger.getLogger(Main.class); - public static boolean sendMessage(String email, String subject, String text) { + private final ConfigSettings config; + + public MailgunClientImpl(ConfigSettings config) { + this.config = config; + } + + public boolean sendMessage(String email, String subject, String text) { if (email.isEmpty()) { logger.info("Email is not sent because the address is empty"); return false; } Client client = Client.create(); - client.addFilter(new HTTPBasicAuthFilter("api", ConfigSettings.instance.getMailgunApiKey())); - String domain = ConfigSettings.instance.getMailgunDomain(); + client.addFilter(new HTTPBasicAuthFilter("api", config.getMailgunApiKey())); + String domain = config.getMailgunDomain(); WebResource webResource = client.resource("https://api.mailgun.net/v3/" + domain + "/messages"); MultivaluedMapImpl formData = new MultivaluedMapImpl(); formData.add("from", "XMage '); formData.add("to", email); formData.add("subject", subject); formData.add("text", text); - ClientResponse response = webResource.type(MediaType.APPLICATION_FORM_URLENCODED).post(ClientResponse.class, formData); + ClientResponse response = webResource.type(MediaType.APPLICATION_FORM_URLENCODED).post(ClientResponse.class, formData); boolean succeeded = response.getStatus() == 200; if (!succeeded) { logger.error("Error sending message to " + email + ". Status code: " + response.getStatus()); diff --git a/Mage.Server/src/main/java/mage/server/Main.java b/Mage.Server/src/main/java/mage/server/Main.java index a9b617cd7cf..c8888660965 100644 --- a/Mage.Server/src/main/java/mage/server/Main.java +++ b/Mage.Server/src/main/java/mage/server/Main.java @@ -14,12 +14,11 @@ import mage.remote.Connection; import mage.server.draft.CubeFactory; import mage.server.game.GameFactory; import mage.server.game.PlayerFactory; +import mage.server.managers.ConfigSettings; +import mage.server.managers.ManagerFactory; import mage.server.record.UserStatsRepository; import mage.server.tournament.TournamentFactory; -import mage.server.util.ConfigSettings; -import mage.server.util.PluginClassLoader; -import mage.server.util.ServerMessagesUtil; -import mage.server.util.SystemUtil; +import mage.server.util.*; import mage.server.util.config.GamePlugin; import mage.server.util.config.Plugin; import mage.utils.MageVersion; @@ -40,6 +39,7 @@ import java.io.IOException; import java.net.InetAddress; import java.net.MalformedURLException; import java.nio.charset.Charset; +import java.nio.file.Paths; import java.util.*; /** @@ -53,9 +53,16 @@ public final class Main { private static final String testModeArg = "-testMode="; private static final String fastDBModeArg = "-fastDbMode="; private static final String adminPasswordArg = "-adminPassword="; + /** + * The property that holds the path to the configuration file. Defaults to "config/config.xml". + * + * To set up a different one, start the application with the java option "-Dxmage.config.path=<path>" + */ + private static final String configPathProp = "xmage.config.path"; private static final File pluginFolder = new File("plugins"); private static final File extensionFolder = new File("extensions"); + private static final String defaultConfigPath = Paths.get("config", "config.xml").toString(); public static final PluginClassLoader classLoader = new PluginClassLoader(); private static TransporterServer server; @@ -70,7 +77,6 @@ public final class Main { logger.info("Starting MAGE server version " + version); logger.info("Logging level: " + logger.getEffectiveLevel()); logger.info("Default charset: " + Charset.defaultCharset()); - String adminPassword = ""; for (String arg : args) { if (arg.startsWith(testModeArg)) { @@ -83,7 +89,14 @@ public final class Main { } } - if (ConfigSettings.instance.isAuthenticationActivated()) { + final String configPath = Optional.ofNullable(System.getProperty(configPathProp)) + .orElse(defaultConfigPath); + + logger.info(String.format("Reading configuration from path=%s", configPath)); + final ConfigWrapper config = new ConfigWrapper(ConfigFactory.loadFromFile(configPath)); + + + if (config.isAuthenticationActivated()) { logger.info("Check authorized user DB version ..."); if (!AuthorizedUserRepository.instance.checkAlterAndMigrateAuthorizedUser()) { logger.fatal("Failed to start server."); @@ -148,7 +161,6 @@ public final class Main { UserStatsRepository.instance.updateUserStats(); logger.info("Done."); deleteSavedGames(); - ConfigSettings config = ConfigSettings.instance; for (GamePlugin plugin : config.getGameTypes()) { GameFactory.instance.addGameType(plugin.getName(), loadGameType(plugin), loadPlugin(plugin)); } @@ -206,11 +218,12 @@ public final class Main { Connection connection = new Connection("&maxPoolSize=" + config.getMaxPoolSize()); connection.setHost(config.getServerAddress()); connection.setPort(config.getPort()); + final ManagerFactory managerFactory = new MainManagerFactory(config); try { // Parameter: serializationtype => jboss InvokerLocator serverLocator = new InvokerLocator(connection.getURI()); - if (!isAlreadyRunning(serverLocator)) { - server = new MageTransporterServer(serverLocator, new MageServerImpl(adminPassword, testMode), MageServer.class.getName(), new MageServerInvocationHandler()); + if (!isAlreadyRunning(config, serverLocator)) { + server = new MageTransporterServer(managerFactory, serverLocator, new MageServerImpl(managerFactory, adminPassword, testMode), MageServer.class.getName(), new MageServerInvocationHandler(managerFactory)); server.start(); logger.info("Started MAGE server - listening on " + connection.toString()); @@ -230,9 +243,9 @@ public final class Main { ServerMessagesUtil.instance.setStartDate(System.currentTimeMillis()); } - static boolean isAlreadyRunning(InvokerLocator serverLocator) { + static boolean isAlreadyRunning(ConfigSettings config, InvokerLocator serverLocator) { Map metadata = new HashMap<>(); - metadata.put(SocketWrapper.WRITE_TIMEOUT, String.valueOf(ConfigSettings.instance.getSocketWriteTimeout())); + metadata.put(SocketWrapper.WRITE_TIMEOUT, String.valueOf(config.getSocketWriteTimeout())); metadata.put("generalizeSocketException", "true"); try { MageServer testServer = (MageServer) TransporterClient.createTransporterClient(serverLocator.getLocatorURI(), MageServer.class, metadata); @@ -248,16 +261,22 @@ public final class Main { static class ClientConnectionListener implements ConnectionListener { + private final ManagerFactory managerFactory; + + public ClientConnectionListener(ManagerFactory managerFactory) { + this.managerFactory = managerFactory; + } + @Override public void handleConnectionException(Throwable throwable, Client client) { String sessionId = client.getSessionId(); - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (!session.isPresent()) { logger.trace("Session not found : " + sessionId); } else { UUID userId = session.get().getUserId(); StringBuilder sessionInfo = new StringBuilder(); - Optional user = UserManager.instance.getUser(userId); + Optional user = managerFactory.userManager().getUser(userId); if (user.isPresent()) { sessionInfo.append(user.get().getName()).append(" [").append(user.get().getGameInfo()).append(']'); } else { @@ -267,12 +286,12 @@ public final class Main { if (throwable instanceof ClientDisconnectedException) { // Seems like the random diconnects from public server land here and should not be handled as explicit disconnects // So it should be possible to reconnect to server and continue games if DisconnectReason is set to LostConnection - //SessionManager.instance.disconnect(client.getSessionId(), DisconnectReason.Disconnected); - SessionManager.instance.disconnect(client.getSessionId(), DisconnectReason.LostConnection); + //managerFactory.sessionManager().disconnect(client.getSessionId(), DisconnectReason.Disconnected); + managerFactory.sessionManager().disconnect(client.getSessionId(), DisconnectReason.LostConnection); logger.info("CLIENT DISCONNECTED - " + sessionInfo); logger.debug("Stack Trace", throwable); } else { - SessionManager.instance.disconnect(client.getSessionId(), DisconnectReason.LostConnection); + managerFactory.sessionManager().disconnect(client.getSessionId(), DisconnectReason.LostConnection); logger.info("LOST CONNECTION - " + sessionInfo); if (logger.isDebugEnabled()) { if (throwable == null) { @@ -292,11 +311,11 @@ public final class Main { protected Connector connector; - public MageTransporterServer(InvokerLocator locator, Object target, String subsystem, MageServerInvocationHandler serverInvocationHandler) throws Exception { + public MageTransporterServer(ManagerFactory managerFactory, InvokerLocator locator, Object target, String subsystem, MageServerInvocationHandler serverInvocationHandler) throws Exception { super(locator, target, subsystem); connector.addInvocationHandler("callback", serverInvocationHandler); - connector.setLeasePeriod(ConfigSettings.instance.getLeasePeriod()); - connector.addConnectionListener(new ClientConnectionListener()); + connector.setLeasePeriod(managerFactory.configSettings().getLeasePeriod()); + connector.addConnectionListener(new ClientConnectionListener(managerFactory)); } public Connector getConnector() throws Exception { @@ -313,6 +332,12 @@ public final class Main { static class MageServerInvocationHandler implements ServerInvocationHandler { + private final ManagerFactory managerFactory; + + public MageServerInvocationHandler(ManagerFactory managerFactory) { + this.managerFactory = managerFactory; + } + @Override public void setMBeanServer(MBeanServer server) { /** @@ -333,9 +358,9 @@ public final class Main { @Override public void setInvoker(ServerInvoker invoker) { - ((BisocketServerInvoker) invoker).setSecondaryBindPort(ConfigSettings.instance.getSecondaryBindPort()); - ((BisocketServerInvoker) invoker).setBacklog(ConfigSettings.instance.getBacklogSize()); - ((BisocketServerInvoker) invoker).setNumAcceptThreads(ConfigSettings.instance.getNumAcceptThreads()); + ((BisocketServerInvoker) invoker).setSecondaryBindPort(managerFactory.configSettings().getSecondaryBindPort()); + ((BisocketServerInvoker) invoker).setBacklog(managerFactory.configSettings().getBacklogSize()); + ((BisocketServerInvoker) invoker).setNumAcceptThreads(managerFactory.configSettings().getNumAcceptThreads()); } @Override @@ -344,7 +369,7 @@ public final class Main { ServerInvokerCallbackHandler handler = (ServerInvokerCallbackHandler) callbackHandler; try { String sessionId = handler.getClientSessionId(); - SessionManager.instance.createSession(sessionId, callbackHandler); + managerFactory.sessionManager().createSession(sessionId, callbackHandler); } catch (Throwable ex) { logger.fatal("", ex); } @@ -362,7 +387,7 @@ public final class Main { } else { host = "localhost"; } - Optional session = SessionManager.instance.getSession(sessionId); + Optional session = managerFactory.sessionManager().getSession(sessionId); if (!session.isPresent()) { logger.error("Session not found : " + sessionId); } else { @@ -375,7 +400,7 @@ public final class Main { public void removeListener(InvokerCallbackHandler callbackHandler) { ServerInvokerCallbackHandler handler = (ServerInvokerCallbackHandler) callbackHandler; String sessionId = handler.getClientSessionId(); - SessionManager.instance.disconnect(sessionId, DisconnectReason.Disconnected); + managerFactory.sessionManager().disconnect(sessionId, DisconnectReason.Disconnected); } } diff --git a/Mage.Server/src/main/java/mage/server/MainManagerFactory.java b/Mage.Server/src/main/java/mage/server/MainManagerFactory.java new file mode 100644 index 00000000000..753a2810f66 --- /dev/null +++ b/Mage.Server/src/main/java/mage/server/MainManagerFactory.java @@ -0,0 +1,124 @@ +package mage.server; + +import mage.server.draft.DraftManagerImpl; +import mage.server.game.GameManagerImpl; +import mage.server.game.GamesRoomManagerImpl; +import mage.server.game.ReplayManagerImpl; +import mage.server.managers.*; +import mage.server.tournament.TournamentManagerImpl; +import mage.server.util.ThreadExecutorImpl; + +public class MainManagerFactory implements ManagerFactory { + + private final ConfigSettings configSettings; + private final ThreadExecutor threadExecutor; + private final ChatManager chatManager; + private final DraftManager draftManager; + private final GameManager gameManager; + private final GamesRoomManager gamesRoomManager; + private final MailClient mailClient; + private final MailClient mailgunClient; + private final ReplayManager replayManager; + private final SessionManager sessionManager; + private final TableManager tableManager; + private final UserManager userManager; + private final TournamentManager tournamentManager; + + + public MainManagerFactory(ConfigSettings configSettings) { + this.configSettings = configSettings; + // ThreadExecutorImpl, MailClientImpl and MailGunClient depend only on the config, so they are initialised first + this.threadExecutor = new ThreadExecutorImpl(configSettings); + this.mailClient = new MailClientImpl(configSettings); + this.mailgunClient = new MailgunClientImpl(configSettings); + // Chat, Draft, Game, Replay, Session and Tournament managers only require access to the ManagerFactory + // but do not use them in initialisation + this.chatManager = new ChatManagerImpl(this); + this.draftManager = new DraftManagerImpl(this); + this.gameManager = new GameManagerImpl(this); + this.replayManager = new ReplayManagerImpl(this); + this.sessionManager = new SessionManagerImpl(this); + this.tournamentManager = new TournamentManagerImpl(this); + // GamesRoom, Table, User managers depend on the ManagerFactory and have an initialisation block which is delayed + // to the end of the construction + final GamesRoomManagerImpl gamesRoomManager = new GamesRoomManagerImpl(this); + final TableManagerImpl tableManager = new TableManagerImpl(this); + final UserManagerImpl userManager = new UserManagerImpl(this); + this.gamesRoomManager = gamesRoomManager; + this.tableManager = tableManager; + this.userManager = userManager; + // execute the initialisation block of the relevant manager (they start the executor services) + startThreads(gamesRoomManager, tableManager, userManager); + } + + private void startThreads(GamesRoomManagerImpl gamesRoomManager, TableManagerImpl tableManager, UserManagerImpl userManager) { + userManager.init(); + tableManager.init(); + gamesRoomManager.init(); + } + + @Override + public ChatManager chatManager() { + return chatManager; + } + + @Override + public DraftManager draftManager() { + return draftManager; + } + + @Override + public GameManager gameManager() { + return gameManager; + } + + @Override + public GamesRoomManager gamesRoomManager() { + return gamesRoomManager; + } + + @Override + public MailClient mailClient() { + return mailClient; + } + + @Override + public MailClient mailgunClient() { + return mailgunClient; + } + + @Override + public ReplayManager replayManager() { + return replayManager; + } + + @Override + public SessionManager sessionManager() { + return sessionManager; + } + + @Override + public TableManager tableManager() { + return tableManager; + } + + @Override + public UserManager userManager() { + return userManager; + } + + @Override + public ConfigSettings configSettings() { + return configSettings; + } + + @Override + public ThreadExecutor threadExecutor() { + return threadExecutor; + } + + @Override + public TournamentManager tournamentManager() { + return tournamentManager; + } +} diff --git a/Mage.Server/src/main/java/mage/server/RoomImpl.java b/Mage.Server/src/main/java/mage/server/RoomImpl.java index d92de7b9f01..9221ee0cd38 100644 --- a/Mage.Server/src/main/java/mage/server/RoomImpl.java +++ b/Mage.Server/src/main/java/mage/server/RoomImpl.java @@ -1,11 +1,10 @@ - - package mage.server; +import mage.server.managers.ChatManager; + import java.util.UUID; /** - * * @author BetaSteward_at_googlemail.com */ public abstract class RoomImpl implements Room { @@ -13,9 +12,9 @@ public abstract class RoomImpl implements Room { private final UUID chatId; private final UUID roomId; - public RoomImpl() { + public RoomImpl(ChatManager chatManager) { roomId = UUID.randomUUID(); - chatId = ChatManager.instance.createChatSession("Room " + roomId); + chatId = chatManager.createChatSession("Room " + roomId); } /** @@ -35,5 +34,4 @@ public abstract class RoomImpl implements Room { } - } diff --git a/Mage.Server/src/main/java/mage/server/Session.java b/Mage.Server/src/main/java/mage/server/Session.java index 10f0c798767..61d36241d52 100644 --- a/Mage.Server/src/main/java/mage/server/Session.java +++ b/Mage.Server/src/main/java/mage/server/Session.java @@ -7,8 +7,8 @@ import mage.interfaces.callback.ClientCallbackMethod; import mage.players.net.UserData; import mage.players.net.UserGroup; import mage.server.game.GamesRoom; -import mage.server.game.GamesRoomManager; -import mage.server.util.ConfigSettings; +import mage.server.managers.ConfigSettings; +import mage.server.managers.ManagerFactory; import mage.server.util.SystemUtil; import mage.util.RandomUtil; import org.apache.log4j.Logger; @@ -34,6 +34,7 @@ public class Session { private static final Pattern alphabetsPattern = Pattern.compile("[a-zA-Z]"); private static final Pattern digitsPattern = Pattern.compile("[0-9]"); + private final ManagerFactory managerFactory; private final String sessionId; private UUID userId; private String host; @@ -46,7 +47,8 @@ public class Session { private final ReentrantLock lock; private final ReentrantLock callBackLock; - public Session(String sessionId, InvokerCallbackHandler callbackHandler) { + public Session(ManagerFactory managerFactory, String sessionId, InvokerCallbackHandler callbackHandler) { + this.managerFactory = managerFactory; this.sessionId = sessionId; this.callbackHandler = (AsynchInvokerCallbackHandler) callbackHandler; this.isAdmin = false; @@ -56,7 +58,7 @@ public class Session { } public String registerUser(String userName, String password, String email) throws MageException { - if (!ConfigSettings.instance.isAuthenticationActivated()) { + if (!managerFactory.configSettings().isAuthenticationActivated()) { String returnMessage = "Registration is disabled by the server config"; sendErrorMessageToClient(returnMessage); return returnMessage; @@ -86,10 +88,10 @@ public class Session { boolean success; String subject = "XMage Registration Completed"; - if (!ConfigSettings.instance.getMailUser().isEmpty()) { - success = MailClient.sendMessage(email, subject, text); + if (!managerFactory.configSettings().getMailUser().isEmpty()) { + success = managerFactory.mailClient().sendMessage(email, subject, text); } else { - success = MailgunClient.sendMessage(email, subject, text); + success = managerFactory.mailgunClient().sendMessage(email, subject, text); } if (success) { String ok = "Sent a registration confirmation / initial password email to " + email + " for " + userName; @@ -109,18 +111,18 @@ public class Session { } } - private static String validateUserName(String userName) { + private String validateUserName(String userName) { if (userName.equals("Admin")) { return "User name Admin already in use"; } - ConfigSettings config = ConfigSettings.instance; + ConfigSettings config = managerFactory.configSettings(); if (userName.length() < config.getMinUserNameLength()) { return "User name may not be shorter than " + config.getMinUserNameLength() + " characters"; } if (userName.length() > config.getMaxUserNameLength()) { return "User name may not be longer than " + config.getMaxUserNameLength() + " characters"; } - Pattern invalidUserNamePattern = Pattern.compile(ConfigSettings.instance.getInvalidUserNamePattern(), Pattern.CASE_INSENSITIVE); + Pattern invalidUserNamePattern = Pattern.compile(managerFactory.configSettings().getInvalidUserNamePattern(), Pattern.CASE_INSENSITIVE); Matcher m = invalidUserNamePattern.matcher(userName); if (m.find()) { return "User name '" + userName + "' includes not allowed characters: use a-z, A-Z and 0-9"; @@ -132,8 +134,8 @@ public class Session { return null; } - private static String validatePassword(String password, String userName) { - ConfigSettings config = ConfigSettings.instance; + private String validatePassword(String password, String userName) { + ConfigSettings config = managerFactory.configSettings(); if (password.length() < config.getMinPasswordLength()) { return "Password may not be shorter than " + config.getMinPasswordLength() + " characters"; } @@ -178,7 +180,7 @@ public class Session { public String connectUserHandling(String userName, String password) throws MageException { this.isAdmin = false; AuthorizedUser authorizedUser = null; - if (ConfigSettings.instance.isAuthenticationActivated()) { + if (managerFactory.configSettings().isAuthenticationActivated()) { authorizedUser = AuthorizedUserRepository.instance.getByName(userName); String errorMsg = "Wrong username or password. In case you haven't, please register your account first."; if (authorizedUser == null) { @@ -196,7 +198,7 @@ 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 + managerFactory.userManager().createUser(userName, host, authorizedUser).ifPresent(user -> user.setLockedUntil(null) ); @@ -204,15 +206,15 @@ public class Session { } } - Optional selectUser = UserManager.instance.createUser(userName, host, authorizedUser); + Optional selectUser = managerFactory.userManager().createUser(userName, host, authorizedUser); boolean reconnect = false; if (!selectUser.isPresent()) { // user already connected - selectUser = UserManager.instance.getUserByName(userName); + selectUser = managerFactory.userManager().getUserByName(userName); if (selectUser.isPresent()) { User user = selectUser.get(); // If authentication is not activated, check the identity using IP address. - if (ConfigSettings.instance.isAuthenticationActivated() || user.getHost().equals(host)) { + if (managerFactory.configSettings().isAuthenticationActivated() || user.getHost().equals(host)) { user.updateLastActivity(null); // minimizes possible expiration this.userId = user.getId(); if (user.getSessionId().isEmpty()) { @@ -221,7 +223,7 @@ public class Session { } else { //disconnect previous session logger.info("Disconnecting another user instance: " + userName); - SessionManager.instance.disconnect(user.getSessionId(), DisconnectReason.ConnectingOtherInstance); + managerFactory.sessionManager().disconnect(user.getSessionId(), DisconnectReason.ConnectingOtherInstance); } } else { return "User name " + userName + " already in use (or your IP address changed)"; @@ -232,18 +234,18 @@ public class Session { } } User user = selectUser.get(); - if (!UserManager.instance.connectToSession(sessionId, user.getId())) { + if (!managerFactory.userManager().connectToSession(sessionId, user.getId())) { return "Error connecting " + userName; } this.userId = user.getId(); if (reconnect) { // must be connected to receive the message - Optional room = GamesRoomManager.instance.getRoom(GamesRoomManager.instance.getMainRoomId()); + Optional room = managerFactory.gamesRoomManager().getRoom(managerFactory.gamesRoomManager().getMainRoomId()); if (!room.isPresent()) { logger.warn("main room not found"); // after server restart users try to use old rooms on reconnect return null; } - ChatManager.instance.joinChat(room.get().getChatId(), userId); - ChatManager.instance.sendReconnectMessage(userId); + managerFactory.chatManager().joinChat(room.get().getChatId(), userId); + managerFactory.chatManager().sendReconnectMessage(userId); } return null; @@ -251,12 +253,12 @@ public class Session { public void connectAdmin() { this.isAdmin = true; - User user = UserManager.instance.createUser("Admin", host, null).orElse( - UserManager.instance.getUserByName("Admin").get()); + User user = managerFactory.userManager().createUser("Admin", host, null).orElse( + managerFactory.userManager().getUserByName("Admin").get()); UserData adminUserData = UserData.getDefaultUserDataView(); adminUserData.setGroupId(UserGroup.ADMIN.getGroupId()); user.setUserData(adminUserData); - if (!UserManager.instance.connectToSession(sessionId, user.getId())) { + if (!managerFactory.userManager().connectToSession(sessionId, user.getId())) { logger.info("Error connecting Admin!"); } else { user.setUserState(User.UserState.Connected); @@ -265,7 +267,7 @@ public class Session { } public boolean setUserData(String userName, UserData userData, String clientVersion, String userIdStr) { - Optional _user = UserManager.instance.getUserByName(userName); + Optional _user = managerFactory.userManager().getUserByName(userName); _user.ifPresent(user -> { if (clientVersion != null) { user.setClientVersion(clientVersion); @@ -313,7 +315,7 @@ public class Session { // because different threads can activate this public void userLostConnection() { - Optional _user = UserManager.instance.getUser(userId); + Optional _user = managerFactory.userManager().getUser(userId); if (!_user.isPresent()) { return; //user was already disconnected by other thread } @@ -338,7 +340,7 @@ public class Session { } else { logger.error("SESSION LOCK - kill: userId " + userId); } - UserManager.instance.removeUserFromAllTablesAndChat(userId, reason); + managerFactory.userManager().removeUserFromAllTablesAndChat(userId, reason); } catch (InterruptedException ex) { logger.error("SESSION LOCK - kill: userId " + userId, ex); } finally { @@ -372,11 +374,11 @@ public class Session { logger.warn("SESSION LOCK - fireCallback - userId: " + userId + " messageId: " + call.getMessageId(), ex); } catch (HandleCallbackException ex) { this.valid = false; - UserManager.instance.getUser(userId).ifPresent(user -> { + managerFactory.userManager().getUser(userId).ifPresent(user -> { user.setUserState(User.UserState.Disconnected); logger.warn("SESSION CALLBACK EXCEPTION - " + user.getName() + " userId " + userId + " messageId: " + call.getMessageId() + " - cause: " + getBasicCause(ex).toString()); logger.trace("Stack trace:", ex); - SessionManager.instance.disconnect(sessionId, LostConnection); + managerFactory.sessionManager().disconnect(sessionId, LostConnection); }); } catch (Exception ex) { logger.warn("Unspecific exception:", ex); diff --git a/Mage.Server/src/main/java/mage/server/SessionManager.java b/Mage.Server/src/main/java/mage/server/SessionManagerImpl.java similarity index 84% rename from Mage.Server/src/main/java/mage/server/SessionManager.java rename to Mage.Server/src/main/java/mage/server/SessionManagerImpl.java index b87dc49c9c4..896195a52d7 100644 --- a/Mage.Server/src/main/java/mage/server/SessionManager.java +++ b/Mage.Server/src/main/java/mage/server/SessionManagerImpl.java @@ -1,8 +1,9 @@ - package mage.server; import mage.MageException; import mage.players.net.UserData; +import mage.server.managers.SessionManager; +import mage.server.managers.ManagerFactory; import org.apache.log4j.Logger; import org.jboss.remoting.callback.InvokerCallbackHandler; @@ -13,21 +14,25 @@ import java.util.concurrent.ConcurrentHashMap; /** * @author BetaSteward_at_googlemail.com */ -public enum SessionManager { +public class SessionManagerImpl implements SessionManager { - instance; - - private static final Logger logger = Logger.getLogger(SessionManager.class); + private static final Logger logger = Logger.getLogger(SessionManagerImpl.class); + private final ManagerFactory managerFactory; private final ConcurrentHashMap sessions = new ConcurrentHashMap<>(); + public SessionManagerImpl(ManagerFactory managerFactory) { + this.managerFactory = managerFactory; + } + + @Override public Optional getSession(@Nonnull String sessionId) { Session session = sessions.get(sessionId); if (session == null) { logger.trace("Session with sessionId " + sessionId + " is not found"); return Optional.empty(); } - if (session.getUserId() != null && !UserManager.instance.getUser(session.getUserId()).isPresent()) { + if (session.getUserId() != null && !managerFactory.userManager().getUser(session.getUserId()).isPresent()) { logger.error("User for session " + sessionId + " with userId " + session.getUserId() + " is missing. Session removed."); // can happen if user from same host signs in multiple time with multiple clients, after they disconnect with one client disconnect(sessionId, DisconnectReason.ConnectingOtherInstance, session); // direct disconnect @@ -36,11 +41,13 @@ public enum SessionManager { return Optional.of(session); } + @Override public void createSession(String sessionId, InvokerCallbackHandler callbackHandler) { - Session session = new Session(sessionId, callbackHandler); + Session session = new Session(managerFactory, sessionId, callbackHandler); sessions.put(sessionId, session); } + @Override public boolean registerUser(String sessionId, String userName, String password, String email) throws MageException { Session session = sessions.get(sessionId); if (session == null) { @@ -59,6 +66,7 @@ public enum SessionManager { return true; } + @Override public boolean connectUser(String sessionId, String userName, String password, String userIdStr) throws MageException { Session session = sessions.get(sessionId); if (session != null) { @@ -78,6 +86,7 @@ public enum SessionManager { return false; } + @Override public boolean connectAdmin(String sessionId) { Session session = sessions.get(sessionId); if (session != null) { @@ -88,17 +97,20 @@ public enum SessionManager { return false; } + @Override public boolean setUserData(String userName, String sessionId, UserData userData, String clientVersion, String userIdStr) throws MageException { - return getSession(sessionId) - .map(session -> session.setUserData(userName,userData, clientVersion, userIdStr)) - .orElse(false); + return getSession(sessionId) + .map(session -> session.setUserData(userName, userData, clientVersion, userIdStr)) + .orElse(false); } + @Override public void disconnect(String sessionId, DisconnectReason reason) { disconnect(sessionId, reason, null); } + @Override public void disconnect(String sessionId, DisconnectReason reason, Session directSession) { if (directSession == null) { // find real session to disconnects @@ -115,13 +127,13 @@ public enum SessionManager { break; case ConnectingOtherInstance: case Disconnected: // regular session end or wrong client version - UserManager.instance.disconnect(session.getUserId(), reason); + managerFactory.userManager().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); + managerFactory.userManager().disconnect(session.getUserId(), reason); break; default: logger.trace("endSession: unexpected reason " + reason.toString() + " - sessionId: " + sessionId); @@ -141,6 +153,7 @@ public enum SessionManager { * @param sessionId * @param userSessionId */ + @Override public void disconnectUser(String sessionId, String userSessionId) { if (isAdmin(sessionId)) { getUserFromSession(sessionId).ifPresent(admin -> { @@ -159,40 +172,46 @@ public enum SessionManager { private Optional getUserFromSession(String sessionId) { return getSession(sessionId) - .flatMap(s -> UserManager.instance.getUser(s.getUserId())); + .flatMap(s -> managerFactory.userManager().getUser(s.getUserId())); } + @Override public void endUserSession(String sessionId, String userSessionId) { if (isAdmin(sessionId)) { disconnect(userSessionId, DisconnectReason.AdminDisconnect); } } + @Override public boolean isAdmin(String sessionId) { return getSession(sessionId).map(Session::isAdmin).orElse(false); } + @Override public boolean isValidSession(@Nonnull String sessionId) { return sessions.containsKey(sessionId); } + @Override public Optional getUser(@Nonnull String sessionId) { Session session = sessions.get(sessionId); if (session != null) { - return UserManager.instance.getUser(sessions.get(sessionId).getUserId()); + return managerFactory.userManager().getUser(sessions.get(sessionId).getUserId()); } logger.error(String.format("Session %s could not be found", sessionId)); return Optional.empty(); } + @Override public boolean extendUserSession(String sessionId, String pingInfo) { return getSession(sessionId) - .map(session -> UserManager.instance.extendUserSession(session.getUserId(), pingInfo)) + .map(session -> managerFactory.userManager().extendUserSession(session.getUserId(), pingInfo)) .orElse(false); } + @Override public void sendErrorMessageToClient(String sessionId, String message) { Session session = sessions.get(sessionId); if (session == null) { diff --git a/Mage.Server/src/main/java/mage/server/TableController.java b/Mage.Server/src/main/java/mage/server/TableController.java index 868fd5f4ee1..ca2ce8fe04c 100644 --- a/Mage.Server/src/main/java/mage/server/TableController.java +++ b/Mage.Server/src/main/java/mage/server/TableController.java @@ -20,16 +20,12 @@ import mage.game.tournament.TournamentOptions; import mage.game.tournament.TournamentPlayer; import mage.players.Player; import mage.players.PlayerType; -import mage.server.draft.DraftManager; import mage.server.game.GameFactory; -import mage.server.game.GameManager; import mage.server.game.PlayerFactory; +import mage.server.managers.ManagerFactory; import mage.server.record.TableRecorderImpl; import mage.server.tournament.TournamentFactory; -import mage.server.tournament.TournamentManager; -import mage.server.util.ConfigSettings; import mage.server.util.ServerMessagesUtil; -import mage.server.util.ThreadExecutor; import mage.view.ChatMessage; import org.apache.log4j.Logger; @@ -47,6 +43,7 @@ public class TableController { private static final Logger logger = Logger.getLogger(TableController.class); + private final ManagerFactory managerFactory; private final UUID userId; private final UUID chatId; private final String controllerName; @@ -58,32 +55,36 @@ public class TableController { private Tournament tournament; private ScheduledFuture futureTimeout; - protected static final ScheduledExecutorService timeoutExecutor = ThreadExecutor.instance.getTimeoutExecutor(); + protected final ScheduledExecutorService timeoutExecutor; - public TableController(UUID roomId, UUID userId, MatchOptions options) { + public TableController(ManagerFactory managerFactory, UUID roomId, UUID userId, MatchOptions options) { + this.managerFactory = managerFactory; + timeoutExecutor = managerFactory.threadExecutor().getTimeoutExecutor(); this.userId = userId; this.options = options; match = GameFactory.instance.createMatch(options.getGameType(), options); if (userId != null) { - Optional user = UserManager.instance.getUser(userId); + Optional user = managerFactory.userManager().getUser(userId); // TODO: Handle if user == null controllerName = user.map(User::getName).orElse("undefined"); } else { controllerName = "System"; } table = new Table(roomId, options.getGameType(), options.getName(), controllerName, DeckValidatorFactory.instance.createDeckValidator(options.getDeckType()), - options.getPlayerTypes(), TableRecorderImpl.instance, match, options.getBannedUsers(), options.isPlaneChase()); - chatId = ChatManager.instance.createChatSession("Match Table " + table.getId()); + options.getPlayerTypes(), new TableRecorderImpl(managerFactory.userManager()), match, options.getBannedUsers(), options.isPlaneChase()); + chatId = managerFactory.chatManager().createChatSession("Match Table " + table.getId()); init(); } - public TableController(UUID roomId, UUID userId, TournamentOptions options) { + public TableController(ManagerFactory managerFactory, UUID roomId, UUID userId, TournamentOptions options) { + this.managerFactory = managerFactory; + this.timeoutExecutor = managerFactory.threadExecutor().getTimeoutExecutor(); this.userId = userId; tournament = TournamentFactory.instance.createTournament(options.getTournamentType(), options); if (userId != null) { - Optional user = UserManager.instance.getUser(userId); + Optional user = managerFactory.userManager().getUser(userId); if (!user.isPresent()) { - logger.fatal(new StringBuilder("User for userId ").append(userId).append(" could not be retrieved from UserManager").toString()); + logger.fatal(new StringBuilder("User for userId ").append(userId).append(" could not be retrieved from UserManagerImpl").toString()); controllerName = "[unknown]"; } else { controllerName = user.get().getName(); @@ -92,8 +93,8 @@ public class TableController { controllerName = "System"; } table = new Table(roomId, options.getTournamentType(), options.getName(), controllerName, DeckValidatorFactory.instance.createDeckValidator(options.getMatchOptions().getDeckType()), - options.getPlayerTypes(), TableRecorderImpl.instance, tournament, options.getMatchOptions().getBannedUsers(), options.isPlaneChase()); - chatId = ChatManager.instance.createChatSession("Tourn. table " + table.getId()); + options.getPlayerTypes(), new TableRecorderImpl(managerFactory.userManager()), tournament, options.getMatchOptions().getBannedUsers(), options.isPlaneChase()); + chatId = managerFactory.chatManager().createChatSession("Tourn. table " + table.getId()); } private void init() { @@ -121,7 +122,7 @@ public class TableController { if (seat == null) { throw new GameException("No available seats."); } - Optional _user = UserManager.instance.getUser(userId); + Optional _user = managerFactory.userManager().getUser(userId); if (!_user.isPresent()) { logger.fatal("couldn't get user " + name + " for join tournament userId = " + userId); return false; @@ -156,7 +157,7 @@ public class TableController { user.showUserMessage("Join Table", sb.toString()); if (isOwner(userId)) { logger.debug("New table removed because owner submitted invalid deck tableId " + table.getId()); - TableManager.instance.removeTable(table.getId()); + managerFactory.tableManager().removeTable(table.getId()); } return false; } @@ -231,12 +232,12 @@ public class TableController { newTournamentPlayer.setState(oldTournamentPlayer.getState()); newTournamentPlayer.setReplacedTournamentPlayer(oldTournamentPlayer); - DraftManager.instance.getController(table.getId()).ifPresent(controller -> controller.replacePlayer(oldPlayer, newPlayer)); + managerFactory.draftManager().getController(table.getId()).ifPresent(controller -> controller.replacePlayer(oldPlayer, newPlayer)); return true; } public synchronized boolean joinTable(UUID userId, String name, PlayerType playerType, int skill, DeckCardLists deckList, String password) throws MageException { - Optional _user = UserManager.instance.getUser(userId); + Optional _user = managerFactory.userManager().getUser(userId); if (!_user.isPresent()) { logger.error("Join Table: can't find user to join " + name + " Id = " + userId); return false; @@ -274,7 +275,7 @@ public class TableController { user.showUserMessage("Join Table", sb.toString()); if (isOwner(userId)) { logger.debug("New table removed because owner submitted invalid deck tableId " + table.getId()); - TableManager.instance.removeTable(table.getId()); + managerFactory.tableManager().removeTable(table.getId()); } return false; } @@ -420,7 +421,7 @@ public class TableController { } } if (!Main.isTestMode() && !table.getValidator().validate(deck)) { - Optional _user = UserManager.instance.getUser(userId); + Optional _user = managerFactory.userManager().getUser(userId); if (!_user.isPresent()) { return false; } @@ -453,10 +454,10 @@ public class TableController { private void submitDeck(UUID userId, UUID playerId, Deck deck) { if (table.getState() == TableState.SIDEBOARDING) { match.submitDeck(playerId, deck); - UserManager.instance.getUser(userId).ifPresent(user -> user.removeSideboarding(table.getId())); + managerFactory.userManager().getUser(userId).ifPresent(user -> user.removeSideboarding(table.getId())); } else { - TournamentManager.instance.submitDeck(tournament.getId(), playerId, deck); - UserManager.instance.getUser(userId).ifPresent(user -> user.removeConstructing(playerId)); + managerFactory.tournamentManager().submitDeck(tournament.getId(), playerId, deck); + managerFactory.userManager().getUser(userId).ifPresent(user -> user.removeConstructing(playerId)); } } @@ -464,7 +465,7 @@ public class TableController { boolean validDeck = true; if (table.isTournament()) { if (tournament != null) { - validDeck = TournamentManager.instance.updateDeck(tournament.getId(), playerId, deck); + validDeck = managerFactory.tournamentManager().updateDeck(tournament.getId(), playerId, deck); } else { logger.fatal("Tournament == null table: " + table.getId() + " userId: " + userId); } @@ -478,7 +479,7 @@ public class TableController { public boolean watchTable(UUID userId) { if (table.isTournament()) { - UserManager.instance.getUser(userId).ifPresent(user -> user.ccShowTournament(table.getTournament().getId())); + managerFactory.userManager().getUser(userId).ifPresent(user -> user.ccShowTournament(table.getTournament().getId())); return true; } else { if (table.isTournamentSubTable() && !table.getTournament().getOptions().isWatchingAllowed()) { @@ -491,7 +492,7 @@ public class TableController { if (userPlayerMap.get(userId) != null) { return false; } - Optional _user = UserManager.instance.getUser(userId); + Optional _user = managerFactory.userManager().getUser(userId); if (!_user.isPresent()) { return false; } @@ -533,7 +534,7 @@ public class TableController { && (table.getState() == TableState.WAITING || table.getState() == TableState.READY_TO_START)) { // table not started yet and user is the owner, removeUserFromAllTablesAndChat the table - TableManager.instance.removeTable(table.getId()); + managerFactory.tableManager().removeTable(table.getId()); } else { UUID playerId = userPlayerMap.get(userId); if (playerId != null) { @@ -544,9 +545,9 @@ public class TableController { } else { match.quitMatch(playerId); } - Optional user = UserManager.instance.getUser(userId); + Optional user = managerFactory.userManager().getUser(userId); if (user.isPresent()) { - ChatManager.instance.broadcast(chatId, user.get().getName(), "has left the table", ChatMessage.MessageColor.BLUE, true, null, ChatMessage.MessageType.STATUS, ChatMessage.SoundToPlay.PlayerLeft); + managerFactory.chatManager().broadcast(chatId, user.get().getName(), "has left the table", ChatMessage.MessageColor.BLUE, true, null, ChatMessage.MessageType.STATUS, ChatMessage.SoundToPlay.PlayerLeft); if (!table.isTournamentSubTable()) { user.get().removeTable(playerId); } @@ -557,9 +558,9 @@ public class TableController { } else if (table.getState() != TableState.FINISHED) { if (table.isTournament()) { logger.debug("Quit tournament sub tables for userId: " + userId); - TableManager.instance.userQuitTournamentSubTables(tournament.getId(), userId); + managerFactory.tableManager().userQuitTournamentSubTables(tournament.getId(), userId); logger.debug("Quit tournament Id: " + table.getTournament().getId() + '(' + table.getTournament().getTournamentState() + ')'); - TournamentManager.instance.quit(tournament.getId(), userId); + managerFactory.tournamentManager().quit(tournament.getId(), userId); } else { MatchPlayer matchPlayer = match.getPlayer(playerId); if (matchPlayer != null && !match.hasEnded() && !matchPlayer.hasQuit()) { @@ -567,7 +568,7 @@ public class TableController { if (game != null && !game.hasEnded()) { Player player = match.getPlayer(playerId).getPlayer(); if (player != null && player.isInGame()) { - GameManager.instance.quitMatch(game.getId(), userId); + managerFactory.gameManager().quitMatch(game.getId(), userId); } match.quitMatch(playerId); } else { @@ -605,7 +606,7 @@ public class TableController { if (table.isTournamentSubTable()) { logger.info("Tourn. match started id:" + match.getId() + " tournId: " + table.getTournament().getId()); } else { - UserManager.instance.getUser(userId).ifPresent(user -> { + managerFactory.userManager().getUser(userId).ifPresent(user -> { logger.info("MATCH started [" + match.getName() + "] " + match.getId() + '(' + user.getName() + ')'); logger.debug("- " + match.getOptions().getGameType() + " - " + match.getOptions().getDeckType()); }); @@ -628,12 +629,12 @@ public class TableController { gameOptions.bannedUsers = match.getOptions().getBannedUsers(); gameOptions.planeChase = match.getOptions().isPlaneChase(); match.getGame().setGameOptions(gameOptions); - GameManager.instance.createGameSession(match.getGame(), userPlayerMap, table.getId(), choosingPlayerId, gameOptions); + managerFactory.gameManager().createGameSession(match.getGame(), userPlayerMap, table.getId(), choosingPlayerId, gameOptions); String creator = null; StringBuilder opponent = new StringBuilder(); for (Entry entry : userPlayerMap.entrySet()) { // do only for no AI players if (match.getPlayer(entry.getValue()) != null && !match.getPlayer(entry.getValue()).hasQuit()) { - Optional _user = UserManager.instance.getUser(entry.getKey()); + Optional _user = managerFactory.userManager().getUser(entry.getKey()); if (_user.isPresent()) { User user = _user.get(); user.ccGameStarted(match.getGame().getId(), entry.getValue()); @@ -670,17 +671,17 @@ public class TableController { logger.info("GAME started " + (match.getGame() != null ? match.getGame().getId() : "no Game") + " [" + match.getName() + "] " + creator + " - " + opponent.toString()); logger.debug("- matchId: " + match.getId() + " [" + match.getName() + ']'); if (match.getGame() != null) { - logger.debug("- chatId: " + GameManager.instance.getChatId(match.getGame().getId())); + logger.debug("- chatId: " + managerFactory.gameManager().getChatId(match.getGame().getId())); } } catch (Exception ex) { logger.fatal("Error starting game table: " + table.getId(), ex); if (table != null) { - TableManager.instance.removeTable(table.getId()); + managerFactory.tableManager().removeTable(table.getId()); } if (match != null) { Game game = match.getGame(); if (game != null) { - GameManager.instance.removeGame(game.getId()); + managerFactory.gameManager().removeGame(game.getId()); // game ended by error, so don't add it to ended stats } } @@ -691,9 +692,9 @@ public class TableController { try { if (userId.equals(this.userId) && table.getState() == TableState.STARTING) { tournament.setStartTime(); - TournamentManager.instance.createTournamentSession(tournament, userPlayerMap, table.getId()); + managerFactory.tournamentManager().createTournamentSession(tournament, userPlayerMap, table.getId()); for (Entry entry : userPlayerMap.entrySet()) { - UserManager.instance.getUser(entry.getKey()).ifPresent(user -> { + managerFactory.userManager().getUser(entry.getKey()).ifPresent(user -> { logger.info(new StringBuilder("User ").append(user.getName()).append(" tournament started: ").append(tournament.getId()).append(" userId: ").append(user.getId())); user.ccTournamentStarted(tournament.getId(), entry.getValue()); }); @@ -702,16 +703,16 @@ public class TableController { } } catch (Exception ex) { logger.fatal("Error starting tournament", ex); - TableManager.instance.removeTable(table.getId()); - TournamentManager.instance.quit(tournament.getId(), userId); + managerFactory.tableManager().removeTable(table.getId()); + managerFactory.tournamentManager().quit(tournament.getId(), userId); } } public void startDraft(Draft draft) { table.initDraft(); - DraftManager.instance.createDraftSession(draft, userPlayerMap, table.getId()); + managerFactory.draftManager().createDraftSession(draft, userPlayerMap, table.getId()); for (Entry entry : userPlayerMap.entrySet()) { - Optional user = UserManager.instance.getUser(entry.getKey()); + Optional user = managerFactory.userManager().getUser(entry.getKey()); if (user.isPresent()) { logger.info(new StringBuilder("User ").append(user.get().getName()).append(" draft started: ").append(draft.getId()).append(" userId: ").append(user.get().getId())); user.get().ccDraftStarted(draft.getId(), entry.getValue()); @@ -725,7 +726,7 @@ public class TableController { for (Entry entry : userPlayerMap.entrySet()) { if (entry.getValue().equals(playerId)) { - Optional user = UserManager.instance.getUser(entry.getKey()); + Optional user = managerFactory.userManager().getUser(entry.getKey()); int remaining = (int) futureTimeout.getDelay(TimeUnit.SECONDS); user.ifPresent(user1 -> user1.ccSideboard(deck, table.getId(), remaining, options.isLimited())); break; @@ -767,12 +768,12 @@ public class TableController { } UUID choosingPlayerId = match.getChooser(); match.endGame(); - if (ConfigSettings.instance.isSaveGameActivated() && !game.isSimulation()) { - if (GameManager.instance.saveGame(game.getId())) { + if (managerFactory.configSettings().isSaveGameActivated() && !game.isSimulation()) { + if (managerFactory.gameManager().saveGame(game.getId())) { match.setReplayAvailable(true); } } - GameManager.instance.removeGame(game.getId()); + managerFactory.gameManager().removeGame(game.getId()); ServerMessagesUtil.instance.incGamesEnded(); try { @@ -835,7 +836,7 @@ public class TableController { // opponent(s) left during sideboarding if (matchPlayer != null) { if (!matchPlayer.hasQuit()) { - UserManager.instance.getUser(entry.getKey()).ifPresent(user -> { + managerFactory.userManager().getUser(entry.getKey()).ifPresent(user -> { if (table.getState() == TableState.SIDEBOARDING) { StringBuilder sb = new StringBuilder(); if (table.isTournamentSubTable()) { @@ -860,7 +861,7 @@ public class TableController { } } // free resources no longer needed - match.cleanUpOnMatchEnd(ConfigSettings.instance.isSaveGameActivated(), table.isTournament()); + match.cleanUpOnMatchEnd(managerFactory.configSettings().isSaveGameActivated(), table.isTournament()); } } } @@ -932,13 +933,13 @@ public class TableController { public boolean isTournamentStillValid() { if (table.getTournament() != null) { if (table.getState() != TableState.WAITING && table.getState() != TableState.READY_TO_START && table.getState() != TableState.STARTING) { - return TournamentManager.instance.getTournamentController(table.getTournament().getId()) + return managerFactory.tournamentManager().getTournamentController(table.getTournament().getId()) .map(tc -> tc.isTournamentStillValid(table.getState())) .orElse(false); } else { // check if table creator is still a valid user, if not removeUserFromAllTablesAndChat table - return UserManager.instance.getUser(userId).isPresent(); + return managerFactory.userManager().getUser(userId).isPresent(); } } return false; @@ -1004,7 +1005,7 @@ public class TableController { || table.getState() == TableState.READY_TO_START) || !match.isDoneSideboarding() || (!matchPlayer.hasQuit() && match.getGame() != null && matchPlayer.getPlayer().isInGame())) { - Optional user = UserManager.instance.getUser(userPlayerEntry.getKey()); + Optional user = managerFactory.userManager().getUser(userPlayerEntry.getKey()); if (!user.isPresent() || !user.get().isActive()) { logger.warn("- Active user of match is missing: " + matchPlayer.getName()); logger.warn("-- matchId:" + match.getId()); @@ -1028,12 +1029,12 @@ public class TableController { void cleanUp() { if (!table.isTournamentSubTable()) { for (Map.Entry entry : userPlayerMap.entrySet()) { - UserManager.instance.getUser(entry.getKey()).ifPresent(user + managerFactory.userManager().getUser(entry.getKey()).ifPresent(user -> user.removeTable(entry.getValue())); } } - ChatManager.instance.destroyChatSession(chatId); + managerFactory.chatManager().destroyChatSession(chatId); } public synchronized TableState getTableState() { diff --git a/Mage.Server/src/main/java/mage/server/TableManager.java b/Mage.Server/src/main/java/mage/server/TableManagerImpl.java similarity index 85% rename from Mage.Server/src/main/java/mage/server/TableManager.java rename to Mage.Server/src/main/java/mage/server/TableManagerImpl.java index b898241ab22..268c4f20fef 100644 --- a/Mage.Server/src/main/java/mage/server/TableManager.java +++ b/Mage.Server/src/main/java/mage/server/TableManagerImpl.java @@ -14,9 +14,8 @@ import mage.game.tournament.TournamentOptions; import mage.game.tournament.TournamentPlayer; import mage.players.PlayerType; import mage.server.game.GameController; -import mage.server.game.GameManager; -import mage.server.game.GamesRoomManager; -import mage.server.util.ThreadExecutor; +import mage.server.managers.TableManager; +import mage.server.managers.ManagerFactory; import org.apache.log4j.Logger; import java.text.DateFormat; @@ -34,12 +33,12 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; /** * @author BetaSteward_at_googlemail.com */ -public enum TableManager { - instance; +public class TableManagerImpl implements TableManager { protected final ScheduledExecutorService expireExecutor = Executors.newSingleThreadScheduledExecutor(); - // protected static ScheduledExecutorService expireExecutor = ThreadExecutor.getInstance().getExpireExecutor(); - private final Logger logger = Logger.getLogger(TableManager.class); + // protected static ScheduledExecutorService expireExecutor = ThreadExecutorImpl.getInstance().getExpireExecutor(); + private final ManagerFactory managerFactory; + private final Logger logger = Logger.getLogger(TableManagerImpl.class); private final DateFormat formatter = new SimpleDateFormat("HH:mm:ss"); private final ConcurrentHashMap controllers = new ConcurrentHashMap<>(); @@ -55,10 +54,14 @@ public enum TableManager { */ private static final int EXPIRE_CHECK_PERIOD = 10; - TableManager() { + public TableManagerImpl(ManagerFactory managerFactory) { + this.managerFactory = managerFactory; + } + + public void init() { expireExecutor.scheduleAtFixedRate(() -> { try { - ChatManager.instance.clearUserMessageStorage(); + managerFactory.chatManager().clearUserMessageStorage(); checkTableHealthState(); } catch (Exception ex) { logger.fatal("Check table health state job error:", ex); @@ -66,22 +69,25 @@ public enum TableManager { }, EXPIRE_CHECK_PERIOD, EXPIRE_CHECK_PERIOD, TimeUnit.MINUTES); } + @Override public Table createTable(UUID roomId, UUID userId, MatchOptions options) { - TableController tableController = new TableController(roomId, userId, options); + TableController tableController = new TableController(managerFactory, roomId, userId, options); putControllers(tableController.getTable().getId(), tableController); putTables(tableController.getTable().getId(), tableController.getTable()); return tableController.getTable(); } + @Override public Table createTable(UUID roomId, MatchOptions options) { - TableController tableController = new TableController(roomId, null, options); + TableController tableController = new TableController(managerFactory, roomId, null, options); putControllers(tableController.getTable().getId(), tableController); putTables(tableController.getTable().getId(), tableController.getTable()); return tableController.getTable(); } + @Override public Table createTournamentTable(UUID roomId, UUID userId, TournamentOptions options) { - TableController tableController = new TableController(roomId, userId, options); + TableController tableController = new TableController(managerFactory, roomId, userId, options); putControllers(tableController.getTable().getId(), tableController); putTables(tableController.getTable().getId(), tableController.getTable()); return tableController.getTable(); @@ -107,10 +113,12 @@ public enum TableManager { } } + @Override public Table getTable(UUID tableId) { return tables.get(tableId); } + @Override public Optional getMatch(UUID tableId) { if (controllers.containsKey(tableId)) { return Optional.of(controllers.get(tableId).getMatch()); @@ -118,6 +126,7 @@ public enum TableManager { return Optional.empty(); } + @Override public Collection getTables() { Collection
newTables = new ArrayList<>(); final Lock r = tablesLock.readLock(); @@ -130,6 +139,7 @@ public enum TableManager { return newTables; } + @Override public Collection getControllers() { Collection newControllers = new ArrayList<>(); final Lock r = controllersLock.readLock(); @@ -142,6 +152,7 @@ public enum TableManager { return newControllers; } + @Override public Optional getController(UUID tableId) { if (controllers.containsKey(tableId)) { return Optional.of(controllers.get(tableId)); @@ -149,6 +160,7 @@ public enum TableManager { return Optional.empty(); } + @Override public boolean joinTable(UUID userId, UUID tableId, String name, PlayerType playerType, int skill, DeckCardLists deckList, String password) throws MageException { if (controllers.containsKey(tableId)) { return controllers.get(tableId).joinTable(userId, name, playerType, skill, deckList, password); @@ -156,6 +168,7 @@ public enum TableManager { return false; } + @Override public boolean joinTournament(UUID userId, UUID tableId, String name, PlayerType playerType, int skill, DeckCardLists deckList, String password) throws GameException { if (controllers.containsKey(tableId)) { return controllers.get(tableId).joinTournament(userId, name, playerType, skill, deckList, password); @@ -163,11 +176,12 @@ public enum TableManager { return false; } + @Override public boolean submitDeck(UUID userId, UUID tableId, DeckCardLists deckList) throws MageException { if (controllers.containsKey(tableId)) { return controllers.get(tableId).submitDeck(userId, deckList); } - UserManager.instance.getUser(userId).ifPresent(user -> { + managerFactory.userManager().getUser(userId).ifPresent(user -> { user.removeSideboarding(tableId); user.showUserMessage("Submit deck", "Table no longer active"); @@ -176,6 +190,7 @@ public enum TableManager { return true; } + @Override public void updateDeck(UUID userId, UUID tableId, DeckCardLists deckList) throws MageException { if (controllers.containsKey(tableId)) { controllers.get(tableId).updateDeck(userId, deckList); @@ -183,6 +198,7 @@ public enum TableManager { } // removeUserFromAllTablesAndChat user from all tournament sub tables + @Override public void userQuitTournamentSubTables(UUID userId) { for (TableController controller : getControllers()) { if (controller.getTable() != null) { @@ -190,12 +206,13 @@ public enum TableManager { controller.leaveTable(userId); } } else { - logger.error("TableManager.userQuitTournamentSubTables table == null - userId " + userId); + logger.error("TableManagerImpl.userQuitTournamentSubTables table == null - userId " + userId); } } } // removeUserFromAllTablesAndChat user from all sub tables of a tournament + @Override public void userQuitTournamentSubTables(UUID tournamentId, UUID userId) { for (TableController controller : getControllers()) { if (controller.getTable().isTournamentSubTable() && controller.getTable().getTournament().getId().equals(tournamentId)) { @@ -206,6 +223,7 @@ public enum TableManager { } } + @Override public boolean isTableOwner(UUID tableId, UUID userId) { if (controllers.containsKey(tableId)) { return controllers.get(tableId).isOwner(userId); @@ -213,13 +231,14 @@ public enum TableManager { return false; } + @Override public boolean removeTable(UUID userId, UUID tableId) { - if (isTableOwner(tableId, userId) || UserManager.instance.isAdmin(userId)) { + if (isTableOwner(tableId, userId) || managerFactory.userManager().isAdmin(userId)) { logger.debug("Table remove request - userId: " + userId + " tableId: " + tableId); TableController tableController = controllers.get(tableId); if (tableController != null) { tableController.leaveTableAll(); - ChatManager.instance.destroyChatSession(tableController.getChatId()); + managerFactory.chatManager().destroyChatSession(tableController.getChatId()); removeTable(tableId); } return true; @@ -227,6 +246,7 @@ public enum TableManager { return false; } + @Override public void leaveTable(UUID userId, UUID tableId) { TableController tableController = controllers.get(tableId); if (tableController != null) { @@ -234,6 +254,7 @@ public enum TableManager { } } + @Override public Optional getChatId(UUID tableId) { if (controllers.containsKey(tableId)) { return Optional.of(controllers.get(tableId).getChatId()); @@ -248,11 +269,12 @@ public enum TableManager { * @param roomId * @param tableId */ + @Override public void startMatch(UUID userId, UUID roomId, UUID tableId) { if (controllers.containsKey(tableId)) { controllers.get(tableId).startMatch(userId); // chat of start dialog can be killed - ChatManager.instance.destroyChatSession(controllers.get(tableId).getChatId()); + managerFactory.chatManager().destroyChatSession(controllers.get(tableId).getChatId()); } } @@ -262,25 +284,29 @@ public enum TableManager { * @param roomId * @param tableId */ + @Override public void startTournamentSubMatch(UUID roomId, UUID tableId) { if (controllers.containsKey(tableId)) { controllers.get(tableId).startMatch(); } } + @Override public void startTournament(UUID userId, UUID roomId, UUID tableId) { if (controllers.containsKey(tableId)) { controllers.get(tableId).startTournament(userId); - ChatManager.instance.destroyChatSession(controllers.get(tableId).getChatId()); + managerFactory.chatManager().destroyChatSession(controllers.get(tableId).getChatId()); } } + @Override public void startDraft(UUID tableId, Draft draft) { if (controllers.containsKey(tableId)) { controllers.get(tableId).startDraft(draft); } } + @Override public boolean watchTable(UUID userId, UUID tableId) { if (controllers.containsKey(tableId)) { return controllers.get(tableId).watchTable(userId); @@ -288,6 +314,7 @@ public enum TableManager { return false; } + @Override public void endGame(UUID tableId) { if (controllers.containsKey(tableId)) { if (controllers.get(tableId).endGameAndStartNextGame()) { @@ -296,42 +323,49 @@ public enum TableManager { } } + @Override public void endDraft(UUID tableId, Draft draft) { if (controllers.containsKey(tableId)) { controllers.get(tableId).endDraft(draft); } } + @Override public void endTournament(UUID tableId, Tournament tournament) { if (controllers.containsKey(tableId)) { controllers.get(tableId).endTournament(tournament); } } + @Override public void swapSeats(UUID tableId, UUID userId, int seatNum1, int seatNum2) { if (controllers.containsKey(tableId) && isTableOwner(tableId, userId)) { controllers.get(tableId).swapSeats(seatNum1, seatNum2); } } + @Override public void construct(UUID tableId) { if (controllers.containsKey(tableId)) { controllers.get(tableId).construct(); } } + @Override public void initTournament(UUID tableId) { if (controllers.containsKey(tableId)) { controllers.get(tableId).initTournament(); } } + @Override public void addPlayer(UUID userId, UUID tableId, TournamentPlayer player) throws GameException { if (controllers.containsKey(tableId)) { controllers.get(tableId).addPlayer(userId, player.getPlayer(), player.getPlayerType(), player.getDeck()); } } + @Override public void removeTable(UUID tableId) { TableController tableController = controllers.get(tableId); if (tableController != null) { @@ -365,21 +399,22 @@ public enum TableManager { // If table is not finished, the table has to be removed completly because it's not a normal state (if finished it will be removed in GamesRoomImpl.Update()) if (table.getState() != TableState.FINISHED) { if (game != null) { - GameManager.instance.removeGame(game.getId()); + managerFactory.gameManager().removeGame(game.getId()); // something goes wrong, so don't add it to ended stats } - GamesRoomManager.instance.removeTable(tableId); + managerFactory.gamesRoomManager().removeTable(tableId); } } } + @Override public void debugServerState() { logger.debug("--- Server state ----------------------------------------------"); - Collection users = UserManager.instance.getUsers(); + Collection users = managerFactory.userManager().getUsers(); logger.debug("--------User: " + users.size() + " [userId | since | lock | name -----------------------"); for (User user : users) { - Optional session = SessionManager.instance.getSession(user.getSessionId()); + Optional session = managerFactory.sessionManager().getSession(user.getSessionId()); String sessionState = "N"; if (session.isPresent()) { if (session.get().isLocked()) { @@ -393,14 +428,14 @@ public enum TableManager { + " | " + sessionState + " | " + user.getName() + " (" + user.getUserState().toString() + " - " + user.getPingInfo() + ')'); } - List chatSessions = ChatManager.instance.getChatSessions(); + List chatSessions = managerFactory.chatManager().getChatSessions(); logger.debug("------- ChatSessions: " + chatSessions.size() + " ----------------------------------"); for (ChatSession chatSession : chatSessions) { logger.debug(chatSession.getChatId() + " " + formatter.format(chatSession.getCreateTime()) + ' ' + chatSession.getInfo() + ' ' + chatSession.getClients().values().toString()); } - logger.debug("------- Games: " + GameManager.instance.getNumberActiveGames() + " --------------------------------------------"); - logger.debug(" Active Game Worker: " + ThreadExecutor.instance.getActiveThreads(ThreadExecutor.instance.getGameExecutor())); - for (Entry entry : GameManager.instance.getGameController().entrySet()) { + logger.debug("------- Games: " + managerFactory.gameManager().getNumberActiveGames() + " --------------------------------------------"); + logger.debug(" Active Game Worker: " + managerFactory.threadExecutor().getActiveThreads(managerFactory.threadExecutor().getGameExecutor())); + for (Entry entry : managerFactory.gameManager().getGameController().entrySet()) { logger.debug(entry.getKey() + entry.getValue().getPlayerNameList()); } logger.debug("--- Server state END ------------------------------------------"); diff --git a/Mage.Server/src/main/java/mage/server/User.java b/Mage.Server/src/main/java/mage/server/User.java index 7cde19283ff..cb0dba3047b 100644 --- a/Mage.Server/src/main/java/mage/server/User.java +++ b/Mage.Server/src/main/java/mage/server/User.java @@ -10,14 +10,13 @@ import mage.interfaces.callback.ClientCallback; import mage.interfaces.callback.ClientCallbackMethod; import mage.players.net.UserData; import mage.server.draft.DraftSession; -import mage.server.game.GameManager; import mage.server.game.GameSessionPlayer; +import mage.server.managers.ManagerFactory; import mage.server.rating.GlickoRating; import mage.server.rating.GlickoRatingSystem; import mage.server.record.UserStats; import mage.server.record.UserStatsRepository; import mage.server.tournament.TournamentController; -import mage.server.tournament.TournamentManager; import mage.server.tournament.TournamentSession; import mage.server.util.ServerMessagesUtil; import mage.server.util.SystemUtil; @@ -43,6 +42,7 @@ public class User { Offline // set if the user was disconnected and expired or regularly left XMage. Removed is the user later after some time } + private final ManagerFactory managerFactory; private final UUID userId; private final String userName; private final String host; @@ -68,7 +68,8 @@ public class User { private String clientVersion; private String userIdStr; - public User(String userName, String host, AuthorizedUser authorizedUser) { + public User(ManagerFactory managerFactory, String userName, String host, AuthorizedUser authorizedUser) { + this.managerFactory = managerFactory; this.userId = UUID.randomUUID(); this.userName = userName; this.host = host; @@ -181,7 +182,7 @@ public class User { // Because watched games don't get restored after reconnection call stop watching for (Iterator iterator = watchedGames.iterator(); iterator.hasNext(); ) { UUID gameId = iterator.next(); - GameManager.instance.stopWatching(gameId, userId); + managerFactory.gameManager().stopWatching(gameId, userId); iterator.remove(); } ServerMessagesUtil.instance.incLostConnection(); @@ -227,7 +228,7 @@ public class User { public void fireCallback(final ClientCallback call) { if (isConnected()) { - SessionManager.instance.getSession(sessionId).ifPresent(session + managerFactory.sessionManager().getSession(sessionId).ifPresent(session -> session.fireCallback(call) ); } @@ -288,27 +289,27 @@ public class User { public void sendPlayerUUID(final UUID gameId, final UUID data) { lastActivity = new Date(); - GameManager.instance.sendPlayerUUID(gameId, userId, data); + managerFactory.gameManager().sendPlayerUUID(gameId, userId, data); } public void sendPlayerString(final UUID gameId, final String data) { lastActivity = new Date(); - GameManager.instance.sendPlayerString(gameId, userId, data); + managerFactory.gameManager().sendPlayerString(gameId, userId, data); } public void sendPlayerManaType(final UUID gameId, final UUID playerId, final ManaType data) { lastActivity = new Date(); - GameManager.instance.sendPlayerManaType(gameId, playerId, userId, data); + managerFactory.gameManager().sendPlayerManaType(gameId, playerId, userId, data); } public void sendPlayerBoolean(final UUID gameId, final Boolean data) { lastActivity = new Date(); - GameManager.instance.sendPlayerBoolean(gameId, userId, data); + managerFactory.gameManager().sendPlayerBoolean(gameId, userId, data); } public void sendPlayerInteger(final UUID gameId, final Integer data) { lastActivity = new Date(); - GameManager.instance.sendPlayerInteger(gameId, userId, data); + managerFactory.gameManager().sendPlayerInteger(gameId, userId, data); } public void updateLastActivity(String pingInfo) { @@ -338,7 +339,7 @@ public class User { } for (Iterator> iterator = userTournaments.entrySet().iterator(); iterator.hasNext(); ) { Entry next = iterator.next(); - Optional tournamentController = TournamentManager.instance.getTournamentController(next.getValue()); + Optional tournamentController = managerFactory.tournamentManager().getTournamentController(next.getValue()); if (tournamentController.isPresent()) { ccTournamentStarted(next.getValue(), next.getKey()); tournamentController.get().rejoin(next.getKey()); @@ -349,7 +350,7 @@ public class User { for (Entry entry : gameSessions.entrySet()) { ccGameStarted(entry.getValue().getGameId(), entry.getKey()); entry.getValue().init(); - GameManager.instance.sendPlayerString(entry.getValue().getGameId(), userId, ""); + managerFactory.gameManager().sendPlayerString(entry.getValue().getGameId(), userId, ""); } for (Entry entry : draftSessions.entrySet()) { @@ -362,7 +363,7 @@ public class User { entry.getValue().construct(0); // TODO: Check if this is correct } for (Entry entry : sideboarding.entrySet()) { - Optional controller = TableManager.instance.getController(entry.getKey()); + Optional controller = managerFactory.tableManager().getController(entry.getKey()); if (controller.isPresent()) { ccSideboard(entry.getValue(), entry.getKey(), controller.get().getRemainingTime(), controller.get().getOptions().isLimited()); } else { @@ -427,32 +428,32 @@ public class User { draftSessions.clear(); logger.trace("REMOVE " + userName + " Tournament sessions " + userTournaments.size()); for (UUID tournamentId : userTournaments.values()) { - TournamentManager.instance.quit(tournamentId, userId); + managerFactory.tournamentManager().quit(tournamentId, userId); } userTournaments.clear(); constructing.clear(); logger.trace("REMOVE " + userName + " Tables " + tables.size()); for (Entry entry : tables.entrySet()) { logger.debug("-- leave tableId: " + entry.getValue().getId()); - TableManager.instance.leaveTable(userId, entry.getValue().getId()); + managerFactory.tableManager().leaveTable(userId, entry.getValue().getId()); } tables.clear(); sideboarding.clear(); logger.trace("REMOVE " + userName + " Game sessions: " + gameSessions.size()); for (GameSessionPlayer gameSessionPlayer : gameSessions.values()) { logger.debug("-- kill game session of gameId: " + gameSessionPlayer.getGameId()); - GameManager.instance.quitMatch(gameSessionPlayer.getGameId(), userId); + managerFactory.gameManager().quitMatch(gameSessionPlayer.getGameId(), userId); gameSessionPlayer.quitGame(); } gameSessions.clear(); logger.trace("REMOVE " + userName + " watched Games " + watchedGames.size()); for (Iterator it = watchedGames.iterator(); it.hasNext(); ) { // Iterator to prevent ConcurrentModificationException UUID gameId = it.next(); - GameManager.instance.stopWatching(gameId, userId); + managerFactory.gameManager().stopWatching(gameId, userId); } watchedGames.clear(); logger.trace("REMOVE " + userName + " Chats "); - ChatManager.instance.removeUser(userId, reason); + managerFactory.chatManager().removeUser(userId, reason); } public void setUserData(UserData userData) { @@ -788,7 +789,7 @@ public class User { if (table.getState() == TableState.FINISHED) { number++; } else { - Optional tableController = TableManager.instance.getController(table.getId()); + Optional tableController = managerFactory.tableManager().getController(table.getId()); if (!tableController.isPresent()) { logger.error("table not found : " + table.getId()); } else if (tableController.get().isUserStillActive(userId)) { diff --git a/Mage.Server/src/main/java/mage/server/UserManager.java b/Mage.Server/src/main/java/mage/server/UserManagerImpl.java similarity index 91% rename from Mage.Server/src/main/java/mage/server/UserManager.java rename to Mage.Server/src/main/java/mage/server/UserManagerImpl.java index 50c41e7fc05..8b1fa6ff34b 100644 --- a/Mage.Server/src/main/java/mage/server/UserManager.java +++ b/Mage.Server/src/main/java/mage/server/UserManagerImpl.java @@ -1,9 +1,10 @@ package mage.server; import mage.server.User.UserState; +import mage.server.managers.UserManager; +import mage.server.managers.ManagerFactory; import mage.server.record.UserStats; import mage.server.record.UserStatsRepository; -import mage.server.util.ThreadExecutor; import mage.view.UserView; import org.apache.log4j.Logger; @@ -19,37 +20,43 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; * * @author BetaSteward_at_googlemail.com */ -public enum UserManager { - instance; +public class UserManagerImpl implements UserManager { 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 - private static final Logger logger = Logger.getLogger(UserManager.class); + private static final Logger logger = Logger.getLogger(UserManagerImpl.class); protected final ScheduledExecutorService expireExecutor = Executors.newSingleThreadScheduledExecutor(); protected final ScheduledExecutorService userListExecutor = Executors.newSingleThreadScheduledExecutor(); private List userInfoList = new ArrayList<>(); + private final ManagerFactory managerFactory; private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final ConcurrentHashMap users = new ConcurrentHashMap<>(); - private static final ExecutorService USER_EXECUTOR = ThreadExecutor.instance.getCallExecutor(); + private ExecutorService USER_EXECUTOR; - UserManager() { + public UserManagerImpl(ManagerFactory managerFactory) { + this.managerFactory = managerFactory; + } + + public void init() { + USER_EXECUTOR = managerFactory.threadExecutor().getCallExecutor(); expireExecutor.scheduleAtFixedRate(this::checkExpired, 60, 60, TimeUnit.SECONDS); userListExecutor.scheduleAtFixedRate(this::updateUserInfoList, 4, 4, TimeUnit.SECONDS); } + @Override public Optional createUser(String userName, String host, AuthorizedUser authorizedUser) { if (getUserByName(userName).isPresent()) { return Optional.empty(); //user already exists } - User user = new User(userName, host, authorizedUser); + User user = new User(managerFactory, userName, host, authorizedUser); final Lock w = lock.writeLock(); w.lock(); try { @@ -60,6 +67,7 @@ public enum UserManager { return Optional.of(user); } + @Override public Optional getUser(UUID userId) { if (!users.containsKey(userId)) { //logger.warn(String.format("User with id %s could not be found", userId), new Throwable()); // TODO: remove after session freezes fixed @@ -69,6 +77,7 @@ public enum UserManager { } } + @Override public Optional getUserByName(String userName) { final Lock r = lock.readLock(); r.lock(); @@ -81,6 +90,7 @@ public enum UserManager { } + @Override public Collection getUsers() { List userList = new ArrayList<>(); final Lock r = lock.readLock(); @@ -93,6 +103,7 @@ public enum UserManager { return userList; } + @Override public boolean connectToSession(String sessionId, UUID userId) { if (userId != null) { User user = users.get(userId); @@ -104,19 +115,19 @@ public enum UserManager { return false; } + @Override public void disconnect(UUID userId, DisconnectReason reason) { - Optional user = UserManager.instance.getUser(userId); + Optional user = getUser(userId); if (user.isPresent()) { user.get().setSessionId(""); if (reason == DisconnectReason.Disconnected) { removeUserFromAllTablesAndChat(userId, reason); user.get().setUserState(UserState.Offline); - } else { -// ChatManager.instance.sendLostConnectionMessage(userId, reason); } } } + @Override public boolean isAdmin(UUID userId) { if (userId != null) { User user = users.get(userId); @@ -127,6 +138,7 @@ public enum UserManager { return false; } + @Override public void removeUserFromAllTablesAndChat(final UUID userId, final DisconnectReason reason) { if (userId != null) { getUser(userId).ifPresent(user @@ -135,7 +147,7 @@ public enum UserManager { try { logger.info("USER REMOVE - " + user.getName() + " (" + reason.toString() + ") userId: " + userId + " [" + user.getGameInfo() + ']'); user.removeUserFromAllTables(reason); - ChatManager.instance.removeUser(user.getId(), reason); + managerFactory.chatManager().removeUser(user.getId(), reason); logger.debug("USER REMOVE END - " + user.getName()); } catch (Exception ex) { handleException(ex); @@ -146,13 +158,14 @@ public enum UserManager { } } + @Override public void informUserOpponents(final UUID userId, final String message) { if (userId != null) { getUser(userId).ifPresent(user -> USER_EXECUTOR.execute( () -> { try { - ChatManager.instance.sendMessageToUserChats(user.getId(), message); + managerFactory.chatManager().sendMessageToUserChats(user.getId(), message); } catch (Exception ex) { handleException(ex); } @@ -161,6 +174,7 @@ public enum UserManager { } } + @Override public boolean extendUserSession(UUID userId, String pingInfo) { if (userId != null) { User user = users.get(userId); @@ -245,7 +259,7 @@ public enum UserManager { private void updateUserInfoList() { try { List newUserInfoList = new ArrayList<>(); - for (User user : UserManager.instance.getUsers()) { + for (User user : getUsers()) { newUserInfoList.add(new UserView( user.getName(), user.getHost(), @@ -266,10 +280,12 @@ public enum UserManager { } } + @Override public List getUserInfoList() { return userInfoList; } + @Override public void handleException(Exception ex) { if (ex != null) { logger.fatal("User manager exception ", ex); @@ -281,6 +297,7 @@ public enum UserManager { } } + @Override public String getUserHistory(String userName) { Optional user = getUserByName(userName); if (user.isPresent()) { @@ -295,6 +312,7 @@ public enum UserManager { return "User " + userName + " not found"; } + @Override public void updateUserHistory() { USER_EXECUTOR.execute(() -> { for (String updatedUser : UserStatsRepository.instance.updateUserStats()) { diff --git a/Mage.Server/src/main/java/mage/server/draft/DraftController.java b/Mage.Server/src/main/java/mage/server/draft/DraftController.java index 24ef713bf62..2bead233f72 100644 --- a/Mage.Server/src/main/java/mage/server/draft/DraftController.java +++ b/Mage.Server/src/main/java/mage/server/draft/DraftController.java @@ -1,5 +1,3 @@ - - package mage.server.draft; import mage.MageException; @@ -9,10 +7,8 @@ import mage.game.events.Listener; import mage.game.events.PlayerQueryEvent; import mage.game.events.TableEvent; import mage.players.Player; -import mage.server.TableManager; -import mage.server.UserManager; import mage.server.game.GameController; -import mage.server.util.ThreadExecutor; +import mage.server.managers.ManagerFactory; import mage.view.DraftPickView; import org.apache.log4j.Logger; @@ -25,7 +21,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** - * * @author BetaSteward_at_googlemail.com */ public class DraftController { @@ -33,6 +28,7 @@ public class DraftController { private static final Logger logger = Logger.getLogger(GameController.class); public static final String INIT_FILE_PATH = "config" + File.separator + "init.txt"; + private final ManagerFactory managerFactory; private final ConcurrentMap draftSessions = new ConcurrentHashMap<>(); private final ConcurrentMap userPlayerMap; private final UUID draftSessionId; @@ -40,7 +36,8 @@ public class DraftController { private final UUID tableId; private final UUID markedCard; - public DraftController(Draft draft, ConcurrentHashMap userPlayerMap, UUID tableId) { + public DraftController(ManagerFactory managerFactory, Draft draft, ConcurrentHashMap userPlayerMap, UUID tableId) { + this.managerFactory = managerFactory; draftSessionId = UUID.randomUUID(); this.userPlayerMap = userPlayerMap; this.draft = draft; @@ -61,8 +58,7 @@ public class DraftController { endDraft(); break; } - } - catch (MageException ex) { + } catch (MageException ex) { logger.fatal("Table event listener error", ex); } } @@ -75,13 +71,12 @@ public class DraftController { pickCard(event.getPlayerId(), event.getMax()); break; } - } - catch (MageException ex) { + } catch (MageException ex) { logger.fatal("Table event listener error", ex); } } ); - for (DraftPlayer player: draft.getPlayers()) { + for (DraftPlayer player : draft.getPlayers()) { if (!player.getPlayer().isHuman()) { player.setJoined(); logger.debug("player " + player.getPlayer().getId() + " has joined draft " + draft.getId()); @@ -96,13 +91,13 @@ public class DraftController { public void join(UUID userId) { UUID playerId = userPlayerMap.get(userId); - DraftSession draftSession = new DraftSession(draft, userId, playerId); + DraftSession draftSession = new DraftSession(managerFactory, draft, userId, playerId); draftSessions.put(playerId, draftSession); - UserManager.instance.getUser(userId).ifPresent(user-> { - user.addDraft(playerId, draftSession); - logger.debug("User " + user.getName() + " has joined draft " + draft.getId()); - draft.getPlayer(playerId).setJoined(); - }); + managerFactory.userManager().getUser(userId).ifPresent(user -> { + user.addDraft(playerId, draftSession); + logger.debug("User " + user.getName() + " has joined draft " + draft.getId()); + draft.getPlayer(playerId).setJoined(); + }); checkStart(); } @@ -114,7 +109,7 @@ public class DraftController { } public boolean replacePlayer(Player oldPlayer, Player newPlayer) { - if (draft.replacePlayer(oldPlayer, newPlayer)) { + if (draft.replacePlayer(oldPlayer, newPlayer)) { DraftSession draftSession = draftSessions.get(oldPlayer.getId()); if (draftSession != null) { draftSession.draftOver(); // closes the draft panel of the replaced player @@ -128,12 +123,12 @@ public class DraftController { private synchronized void checkStart() { if (!draft.isStarted() && allJoined()) { draft.setStarted(); - ThreadExecutor.instance.getCallExecutor().execute(this::startDraft); + managerFactory.threadExecutor().getCallExecutor().execute(this::startDraft); } } private void startDraft() { - for (final Entry entry: draftSessions.entrySet()) { + for (final Entry entry : draftSessions.entrySet()) { if (!entry.getValue().init()) { logger.fatal("Unable to initialize client for playerId " + entry.getKey()); //TODO: generate client error message @@ -147,7 +142,7 @@ public class DraftController { if (!draft.allJoined()) { return false; } - for (DraftPlayer player: draft.getPlayers()) { + for (DraftPlayer player : draft.getPlayers()) { if (player.getPlayer().isHuman() && !draftSessions.containsKey(player.getPlayer().getId())) { return false; } @@ -160,12 +155,12 @@ public class DraftController { } private void endDraft() throws MageException { - for (final DraftSession draftSession: draftSessions.values()) { + for (final DraftSession draftSession : draftSessions.values()) { draftSession.draftOver(); draftSession.removeDraft(); } - TableManager.instance.endDraft(tableId, draft); - DraftManager.instance.removeDraft(draft.getId()); + managerFactory.tableManager().endDraft(tableId, draft); + managerFactory.draftManager().removeDraft(draft.getId()); } public void kill(UUID userId) { @@ -210,7 +205,7 @@ public class DraftController { } private synchronized void updateDraft() throws MageException { - for (final Entry entry: draftSessions.entrySet()) { + for (final Entry entry : draftSessions.entrySet()) { entry.getValue().update(); } } @@ -229,7 +224,7 @@ public class DraftController { draft.setAbort(true); try { endDraft(); - } catch(MageException ex) { + } catch (MageException ex) { } } diff --git a/Mage.Server/src/main/java/mage/server/draft/DraftManager.java b/Mage.Server/src/main/java/mage/server/draft/DraftManagerImpl.java similarity index 75% rename from Mage.Server/src/main/java/mage/server/draft/DraftManager.java rename to Mage.Server/src/main/java/mage/server/draft/DraftManagerImpl.java index 3d49b6762da..69127d4479e 100644 --- a/Mage.Server/src/main/java/mage/server/draft/DraftManager.java +++ b/Mage.Server/src/main/java/mage/server/draft/DraftManagerImpl.java @@ -1,69 +1,83 @@ - - package mage.server.draft; +import mage.game.draft.Draft; +import mage.server.managers.DraftManager; +import mage.server.managers.ManagerFactory; +import mage.view.DraftPickView; + import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import mage.game.draft.Draft; -import mage.view.DraftPickView; - /** - * * @author BetaSteward_at_googlemail.com */ -public enum DraftManager { - instance; +public class DraftManagerImpl implements DraftManager { + private final ManagerFactory managerFactory; private final ConcurrentMap draftControllers = new ConcurrentHashMap<>(); + public DraftManagerImpl(ManagerFactory managerFactory) { + this.managerFactory = managerFactory; + } + + @Override public UUID createDraftSession(Draft draft, ConcurrentHashMap userPlayerMap, UUID tableId) { - DraftController draftController = new DraftController(draft, userPlayerMap, tableId); + DraftController draftController = new DraftController(managerFactory, draft, userPlayerMap, tableId); draftControllers.put(draft.getId(), draftController); return draftController.getSessionId(); } + @Override public void joinDraft(UUID draftId, UUID userId) { draftControllers.get(draftId).join(userId); } + @Override public void destroyChatSession(UUID gameId) { draftControllers.remove(gameId); } + @Override public DraftPickView sendCardPick(UUID draftId, UUID userId, UUID cardId, Set hiddenCards) { return draftControllers.get(draftId).sendCardPick(userId, cardId, hiddenCards); } + @Override public void sendCardMark(UUID draftId, UUID userId, UUID cardId) { draftControllers.get(draftId).sendCardMark(userId, cardId); } + @Override public void removeSession(UUID userId) { - for (DraftController controller: draftControllers.values()) { + for (DraftController controller : draftControllers.values()) { controller.kill(userId); } } + @Override public void kill(UUID draftId, UUID userId) { draftControllers.get(draftId).kill(userId); } + @Override public void timeout(UUID gameId, UUID userId) { draftControllers.get(gameId).timeout(userId); } + @Override public void removeDraft(UUID draftId) { draftControllers.remove(draftId); } + @Override public DraftController getControllerByDraftId(UUID draftId) { return draftControllers.get(draftId); } + @Override public Optional getController(UUID tableId) { return draftControllers.values().stream().filter(controller -> controller.getTableId().equals(tableId)).findFirst(); } diff --git a/Mage.Server/src/main/java/mage/server/draft/DraftSession.java b/Mage.Server/src/main/java/mage/server/draft/DraftSession.java index d34848fdb10..bea9c5b612e 100644 --- a/Mage.Server/src/main/java/mage/server/draft/DraftSession.java +++ b/Mage.Server/src/main/java/mage/server/draft/DraftSession.java @@ -1,6 +1,15 @@ - package mage.server.draft; +import mage.game.draft.Draft; +import mage.interfaces.callback.ClientCallback; +import mage.interfaces.callback.ClientCallbackMethod; +import mage.server.User; +import mage.server.managers.ManagerFactory; +import mage.view.DraftClientMessage; +import mage.view.DraftPickView; +import mage.view.DraftView; +import org.apache.log4j.Logger; + import java.rmi.RemoteException; import java.util.Optional; import java.util.Set; @@ -8,16 +17,6 @@ import java.util.UUID; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import mage.game.draft.Draft; -import mage.interfaces.callback.ClientCallback; -import mage.interfaces.callback.ClientCallbackMethod; -import mage.server.User; -import mage.server.UserManager; -import mage.server.util.ThreadExecutor; -import mage.view.DraftClientMessage; -import mage.view.DraftPickView; -import mage.view.DraftView; -import org.apache.log4j.Logger; /** * @author BetaSteward_at_googlemail.com @@ -26,6 +25,7 @@ public class DraftSession { protected static final Logger logger = Logger.getLogger(DraftSession.class); + private final ManagerFactory managerFactory; protected final UUID userId; protected final UUID playerId; protected final Draft draft; @@ -33,9 +33,11 @@ public class DraftSession { protected UUID markedCard; private ScheduledFuture futureTimeout; - protected static final ScheduledExecutorService timeoutExecutor = ThreadExecutor.instance.getTimeoutExecutor(); + protected final ScheduledExecutorService timeoutExecutor; - public DraftSession(Draft draft, UUID userId, UUID playerId) { + public DraftSession(ManagerFactory managerFactory, Draft draft, UUID userId, UUID playerId) { + this.managerFactory = managerFactory; + this.timeoutExecutor = managerFactory.threadExecutor().getTimeoutExecutor(); this.userId = userId; this.draft = draft; this.playerId = playerId; @@ -44,12 +46,12 @@ public class DraftSession { public boolean init() { if (!killed) { - Optional user = UserManager.instance.getUser(userId); + Optional user = managerFactory.userManager().getUser(userId); if (user.isPresent()) { if (futureTimeout != null && !futureTimeout.isDone()) { int remaining = (int) futureTimeout.getDelay(TimeUnit.SECONDS); user.get().fireCallback(new ClientCallback(ClientCallbackMethod.DRAFT_INIT, draft.getId(), - new DraftClientMessage(getDraftView(), getDraftPickView(remaining)))); + new DraftClientMessage(getDraftView(), getDraftPickView(remaining)))); } return true; } @@ -59,16 +61,16 @@ public class DraftSession { public void update() { if (!killed) { - UserManager.instance + managerFactory.userManager() .getUser(userId). ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.DRAFT_UPDATE, draft.getId(), - new DraftClientMessage(getDraftView(), null)))); + new DraftClientMessage(getDraftView(), null)))); } } public void draftOver() { if (!killed) { - UserManager.instance + managerFactory.userManager() .getUser(userId) .ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.DRAFT_OVER, draft.getId()))); } @@ -77,10 +79,10 @@ public class DraftSession { public void pickCard(int timeout) { if (!killed) { setupTimeout(timeout); - UserManager.instance + managerFactory.userManager() .getUser(userId) .ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.DRAFT_PICK, draft.getId(), - new DraftClientMessage(getDraftView(), getDraftPickView(timeout))))); + new DraftClientMessage(getDraftView(), getDraftPickView(timeout))))); } } @@ -89,7 +91,7 @@ public class DraftSession { cancelTimeout(); if (seconds > 0) { futureTimeout = timeoutExecutor.schedule( - () -> DraftManager.instance.timeout(draft.getId(), userId), + () -> managerFactory.draftManager().timeout(draft.getId(), userId), seconds, TimeUnit.SECONDS ); } @@ -103,7 +105,7 @@ public class DraftSession { protected void handleRemoteException(RemoteException ex) { logger.fatal("DraftSession error ", ex); - DraftManager.instance.kill(draft.getId(), userId); + managerFactory.draftManager().kill(draft.getId(), userId); } public void setKilled() { @@ -119,7 +121,7 @@ public class DraftSession { } public void removeDraft() { - UserManager.instance.getUser(userId).ifPresent(user -> user.removeDraft(playerId)); + managerFactory.userManager().getUser(userId).ifPresent(user -> user.removeDraft(playerId)); } diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java index ab3b8fcb8d8..f6ea6a032ab 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -23,11 +23,11 @@ import mage.game.permanent.Permanent; import mage.game.turn.Phase; import mage.interfaces.Action; import mage.players.Player; -import mage.server.*; -import mage.server.util.ConfigSettings; +import mage.server.Main; +import mage.server.User; +import mage.server.managers.ManagerFactory; import mage.server.util.Splitter; import mage.server.util.SystemUtil; -import mage.server.util.ThreadExecutor; import mage.utils.StreamUtils; import mage.utils.timer.PriorityTimer; import mage.view.*; @@ -53,13 +53,14 @@ public class GameController implements GameCallback { private static final int GAME_TIMEOUTS_CHECK_JOINING_STATUS_EVERY_SECS = 10; // checks and inform players about joining status 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 final ExecutorService gameExecutor; private static final Logger logger = Logger.getLogger(GameController.class); protected final ScheduledExecutorService joinWaitingExecutor = Executors.newSingleThreadScheduledExecutor(); private ScheduledFuture futureTimeout; - protected static final ScheduledExecutorService timeoutIdleExecutor = ThreadExecutor.instance.getTimeoutIdleExecutor(); + private final ManagerFactory managerFactory; + protected final ScheduledExecutorService timeoutIdleExecutor; private final ConcurrentMap gameSessions = new ConcurrentHashMap<>(); private final ReadWriteLock gameSessionsLock = new ReentrantReadWriteLock(); @@ -83,13 +84,16 @@ public class GameController implements GameCallback { private int turnsToRollback; private int requestsOpen; - public GameController(Game game, ConcurrentMap userPlayerMap, UUID tableId, UUID choosingPlayerId, GameOptions gameOptions) { + public GameController(ManagerFactory managerFactory, Game game, ConcurrentMap userPlayerMap, UUID tableId, UUID choosingPlayerId, GameOptions gameOptions) { + this.managerFactory = managerFactory; + gameExecutor = managerFactory.threadExecutor().getGameExecutor(); + timeoutIdleExecutor = managerFactory.threadExecutor().getTimeoutIdleExecutor(); gameSessionId = UUID.randomUUID(); this.userPlayerMap = userPlayerMap; - chatId = ChatManager.instance.createChatSession("Game " + game.getId()); + chatId = managerFactory.chatManager().createChatSession("Game " + game.getId()); this.userReqestingRollback = null; this.game = game; - this.game.setSaveGame(ConfigSettings.instance.isSaveGameActivated()); + this.game.setSaveGame(managerFactory.configSettings().isSaveGameActivated()); this.tableId = tableId; this.choosingPlayerId = choosingPlayerId; this.gameOptions = gameOptions; @@ -103,7 +107,7 @@ public class GameController implements GameCallback { for (GameSessionPlayer gameSessionPlayer : getGameSessions()) { gameSessionPlayer.cleanUp(); } - ChatManager.instance.destroyChatSession(chatId); + managerFactory.chatManager().destroyChatSession(chatId); for (PriorityTimer priorityTimer : timers.values()) { priorityTimer.cancel(); } @@ -120,11 +124,11 @@ public class GameController implements GameCallback { updateGame(); break; case INFO: - ChatManager.instance.broadcast(chatId, "", event.getMessage(), MessageColor.BLACK, true, event.getGame(), MessageType.GAME, null); + managerFactory.chatManager().broadcast(chatId, "", event.getMessage(), MessageColor.BLACK, true, event.getGame(), MessageType.GAME, null); logger.trace(game.getId() + " " + event.getMessage()); break; case STATUS: - ChatManager.instance.broadcast(chatId, "", event.getMessage(), MessageColor.ORANGE, event.getWithTime(), event.getWithTurnInfo() ? event.getGame() : null, MessageType.GAME, null); + managerFactory.chatManager().broadcast(chatId, "", event.getMessage(), MessageColor.ORANGE, event.getWithTime(), event.getWithTurnInfo() ? event.getGame() : null, MessageType.GAME, null); logger.trace(game.getId() + " " + event.getMessage()); break; case ERROR: @@ -273,7 +277,7 @@ public class GameController implements GameCallback { logger.fatal("- userId: " + userId); return; } - Optional user = UserManager.instance.getUser(userId); + Optional user = managerFactory.userManager().getUser(userId); if (!user.isPresent()) { logger.fatal("User not found : " + userId); return; @@ -286,7 +290,7 @@ public class GameController implements GameCallback { GameSessionPlayer gameSession = gameSessions.get(playerId); String joinType; if (gameSession == null) { - gameSession = new GameSessionPlayer(game, userId, playerId); + gameSession = new GameSessionPlayer(managerFactory, game, userId, playerId); final Lock w = gameSessionsLock.writeLock(); w.lock(); try { @@ -300,7 +304,7 @@ public class GameController implements GameCallback { } user.get().addGame(playerId, gameSession); logger.debug("Player " + player.getName() + ' ' + playerId + " has " + joinType + " gameId: " + game.getId()); - ChatManager.instance.broadcast(chatId, "", game.getPlayer(playerId).getLogName() + " has " + joinType + " the game", MessageColor.ORANGE, true, game, MessageType.GAME, null); + managerFactory.chatManager().broadcast(chatId, "", game.getPlayer(playerId).getLogName() + " has " + joinType + " the game", MessageColor.ORANGE, true, game, MessageType.GAME, null); checkStart(); } @@ -335,7 +339,7 @@ public class GameController implements GameCallback { // join the game because player has not joined or was removed because of disconnect String problemPlayerFixes; user.removeConstructing(player.getId()); - GameManager.instance.joinGame(game.getId(), user.getId()); + managerFactory.gameManager().joinGame(game.getId(), user.getId()); logger.warn("Forced join of player " + player.getName() + " (" + user.getUserState() + ") to gameId: " + game.getId()); if (user.isConnected()) { // init game session, see reconnect() @@ -345,7 +349,7 @@ public class GameController implements GameCallback { logger.warn("Send forced game start event for player " + player.getName() + " in gameId: " + game.getId()); user.ccGameStarted(session.getGameId(), player.getId()); session.init(); - GameManager.instance.sendPlayerString(session.getGameId(), user.getId(), ""); + managerFactory.gameManager().sendPlayerString(session.getGameId(), user.getId(), ""); } else { problemPlayerFixes = "leave on broken game session"; logger.error("Can't find game session for forced join, leave it: player " + player.getName() + " in gameId: " + game.getId()); @@ -357,7 +361,7 @@ public class GameController implements GameCallback { player.leave(); } - ChatManager.instance.broadcast(chatId, player.getName(), user.getPingInfo() + managerFactory.chatManager().broadcast(chatId, player.getName(), user.getPingInfo() + " is forced to join the game (waiting ends after " + GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS + " secs, applied fixes: " + problemPlayerFixes + ")", @@ -382,7 +386,7 @@ public class GameController implements GameCallback { private Optional getUserByPlayerId(UUID playerId) { for (Map.Entry entry : userPlayerMap.entrySet()) { if (entry.getValue().equals(playerId)) { - return UserManager.instance.getUser(entry.getKey()); + return managerFactory.userManager().getUser(entry.getKey()); } } return Optional.empty(); @@ -391,7 +395,7 @@ public class GameController implements GameCallback { private void checkStart() { if (allJoined()) { joinWaitingExecutor.shutdownNow(); - ThreadExecutor.instance.getCallExecutor().execute(this::startGame); + managerFactory.threadExecutor().getCallExecutor().execute(this::startGame); } } @@ -423,14 +427,14 @@ public class GameController implements GameCallback { } if (!isAllowedToWatch(userId)) { // Dont want people on our ignore list to stalk us - UserManager.instance.getUser(userId).ifPresent(user -> { + managerFactory.userManager().getUser(userId).ifPresent(user -> { user.showUserMessage("Not allowed", "You are banned from watching this game"); - ChatManager.instance.broadcast(chatId, user.getName(), " tried to join, but is banned", MessageColor.BLUE, true, game, ChatMessage.MessageType.STATUS, null); + managerFactory.chatManager().broadcast(chatId, user.getName(), " tried to join, but is banned", MessageColor.BLUE, true, game, ChatMessage.MessageType.STATUS, null); }); return false; } - UserManager.instance.getUser(userId).ifPresent(user -> { - GameSessionWatcher gameWatcher = new GameSessionWatcher(userId, game, false); + managerFactory.userManager().getUser(userId).ifPresent(user -> { + GameSessionWatcher gameWatcher = new GameSessionWatcher(managerFactory.userManager(), userId, game, false); final Lock w = gameWatchersLock.writeLock(); w.lock(); try { @@ -440,7 +444,7 @@ public class GameController implements GameCallback { } gameWatcher.init(); user.addGameWatchInfo(game.getId()); - ChatManager.instance.broadcast(chatId, user.getName(), " has started watching", MessageColor.BLUE, true, game, ChatMessage.MessageType.STATUS, null); + managerFactory.chatManager().broadcast(chatId, user.getName(), " has started watching", MessageColor.BLUE, true, game, ChatMessage.MessageType.STATUS, null); }); return true; } @@ -453,8 +457,8 @@ public class GameController implements GameCallback { } finally { w.unlock(); } - UserManager.instance.getUser(userId).ifPresent(user -> { - ChatManager.instance.broadcast(chatId, user.getName(), " has stopped watching", MessageColor.BLUE, true, game, ChatMessage.MessageType.STATUS, null); + managerFactory.userManager().getUser(userId).ifPresent(user -> { + managerFactory.chatManager().broadcast(chatId, user.getName(), " has stopped watching", MessageColor.BLUE, true, game, ChatMessage.MessageType.STATUS, null); }); } @@ -628,7 +632,7 @@ public class GameController implements GameCallback { gameSession.requestPermissionToSeeHandCards(userIdRequester); } else { // player does not allow the request - UserManager.instance.getUser(userIdRequester).ifPresent(requester -> { + managerFactory.userManager().getUser(userIdRequester).ifPresent(requester -> { requester.showUserMessage("Request to show hand cards", "Player " + grantingPlayer.getName() + " does not allow to request to show hand cards!"); }); } @@ -640,7 +644,7 @@ public class GameController implements GameCallback { } } else { // user can already see the cards - UserManager.instance.getUser(userIdRequester).ifPresent(requester -> { + managerFactory.userManager().getUser(userIdRequester).ifPresent(requester -> { requester.showUserMessage("Request to show hand cards", "You can see already the hand cards of player " + grantingPlayer.getName() + '!'); }); @@ -653,9 +657,9 @@ public class GameController implements GameCallback { Player viewLimitedDeckPlayer = game.getPlayer(userIdRequester); if (viewLimitedDeckPlayer != null) { if (viewLimitedDeckPlayer.isHuman()) { - for (MatchPlayer p : TableManager.instance.getTable(tableId).getMatch().getPlayers()) { + for (MatchPlayer p : managerFactory.tableManager().getTable(tableId).getMatch().getPlayers()) { if (p.getPlayer().getId().equals(userIdRequester)) { - Optional u = UserManager.instance.getUser(origId); + Optional u = managerFactory.userManager().getUser(origId); if (u.isPresent() && p.getDeck() != null) { u.get().ccViewLimitedDeck(p.getDeck(), tableId, requestsOpen, true); } @@ -698,8 +702,8 @@ public class GameController implements GameCallback { if (player != null) { String sb = player.getLogName() + " has timed out (player had priority and was not active for " - + ConfigSettings.instance.getMaxSecondsIdle() + " seconds ) - Auto concede."; - ChatManager.instance.broadcast(chatId, "", sb, MessageColor.BLACK, true, game, MessageType.STATUS, null); + + managerFactory.configSettings().getMaxSecondsIdle() + " seconds ) - Auto concede."; + managerFactory.chatManager().broadcast(chatId, "", sb, MessageColor.BLACK, true, game, MessageType.STATUS, null); game.idleTimeout(playerId); } } @@ -712,7 +716,7 @@ public class GameController implements GameCallback { for (final GameSessionWatcher gameWatcher : getGameSessionWatchers()) { gameWatcher.gameOver(message); } - TableManager.instance.endGame(tableId); + managerFactory.tableManager().endGame(tableId); } public UUID getSessionId() { @@ -763,7 +767,7 @@ public class GameController implements GameCallback { } private synchronized void endGameInfo() { - Table table = TableManager.instance.getTable(tableId); + Table table = managerFactory.tableManager().getTable(tableId); if (table != null) { if (table.getMatch() != null) { for (final GameSessionPlayer gameSession : getGameSessions()) { @@ -784,7 +788,7 @@ public class GameController implements GameCallback { } private synchronized void choosePile(UUID playerId, final String message, final List pile1, final List pile2) throws MageException { - perform(playerId, playerId1 -> getGameSession(playerId1).choosePile(message, new CardsView(pile1), new CardsView(pile2))); + perform(playerId, playerId1 -> getGameSession(playerId1).choosePile(message, new CardsView(game, pile1), new CardsView(game, pile2))); } private synchronized void chooseMode(UUID playerId, final Map modes, final String message) throws MageException { @@ -1012,7 +1016,7 @@ public class GameController implements GameCallback { cancelTimeout(); futureTimeout = timeoutIdleExecutor.schedule( () -> idleTimeout(playerId), - Main.isTestMode() ? 3600 : ConfigSettings.instance.getMaxSecondsIdle(), + Main.isTestMode() ? 3600 : managerFactory.configSettings().getMaxSecondsIdle(), TimeUnit.SECONDS ); } @@ -1094,7 +1098,7 @@ public class GameController implements GameCallback { } public boolean isAllowedToWatch(UUID userId) { - Optional user = UserManager.instance.getUser(userId); + Optional user = managerFactory.userManager().getUser(userId); if (user.isPresent()) { return !gameOptions.bannedUsers.contains(user.get().getName()); } @@ -1208,7 +1212,7 @@ public class GameController implements GameCallback { public String getPingsInfo() { List usersInfo = new ArrayList<>(); for (Map.Entry entry : userPlayerMap.entrySet()) { - Optional user = UserManager.instance.getUser(entry.getKey()); + Optional user = managerFactory.userManager().getUser(entry.getKey()); user.ifPresent(u -> usersInfo.add("* " + u.getName() + ": " + u.getPingInfo())); } Collections.sort(usersInfo); @@ -1216,7 +1220,7 @@ public class GameController implements GameCallback { List watchersinfo = new ArrayList<>(); for (Map.Entry entry : watchers.entrySet()) { - Optional user = UserManager.instance.getUser(entry.getValue().userId); + Optional user = managerFactory.userManager().getUser(entry.getValue().userId); user.ifPresent(u -> watchersinfo.add("* " + u.getName() + ": " + u.getPingInfo())); } Collections.sort(watchersinfo); diff --git a/Mage.Server/src/main/java/mage/server/game/GameManager.java b/Mage.Server/src/main/java/mage/server/game/GameManagerImpl.java similarity index 89% rename from Mage.Server/src/main/java/mage/server/game/GameManager.java rename to Mage.Server/src/main/java/mage/server/game/GameManagerImpl.java index f2650901492..3121bfdbdb6 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameManager.java +++ b/Mage.Server/src/main/java/mage/server/game/GameManagerImpl.java @@ -5,6 +5,8 @@ import mage.constants.ManaType; import mage.constants.PlayerAction; import mage.game.Game; import mage.game.GameOptions; +import mage.server.managers.GameManager; +import mage.server.managers.ManagerFactory; import mage.view.GameView; import java.util.HashMap; @@ -20,14 +22,19 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; /** * @author BetaSteward_at_googlemail.com */ -public enum GameManager { - instance; +public class GameManagerImpl implements GameManager { + private final ManagerFactory managerFactory; private final ConcurrentMap gameControllers = new ConcurrentHashMap<>(); private final ReadWriteLock gameControllersLock = new ReentrantReadWriteLock(); + public GameManagerImpl(ManagerFactory managerFactory) { + this.managerFactory = managerFactory; + } + + @Override public UUID createGameSession(Game game, ConcurrentHashMap userPlayerMap, UUID tableId, UUID choosingPlayerId, GameOptions gameOptions) { - GameController gameController = new GameController(game, userPlayerMap, tableId, choosingPlayerId, gameOptions); + GameController gameController = new GameController(managerFactory, game, userPlayerMap, tableId, choosingPlayerId, gameOptions); final Lock w = gameControllersLock.writeLock(); w.lock(); try { @@ -48,6 +55,7 @@ public enum GameManager { } } + @Override public void joinGame(UUID gameId, UUID userId) { GameController gameController = getGameControllerSafe(gameId); if (gameController != null) { @@ -55,6 +63,7 @@ public enum GameManager { } } + @Override public Optional getChatId(UUID gameId) { GameController gameController = getGameControllerSafe(gameId); if (gameController != null) { @@ -63,6 +72,7 @@ public enum GameManager { return Optional.empty(); } + @Override public void sendPlayerUUID(UUID gameId, UUID userId, UUID data) { GameController gameController = getGameControllerSafe(gameId); if (gameController != null) { @@ -70,6 +80,7 @@ public enum GameManager { } } + @Override public void sendPlayerString(UUID gameId, UUID userId, String data) { GameController gameController = getGameControllerSafe(gameId); if (gameController != null) { @@ -77,6 +88,7 @@ public enum GameManager { } } + @Override public void sendPlayerManaType(UUID gameId, UUID playerId, UUID userId, ManaType data) { GameController gameController = getGameControllerSafe(gameId); if (gameController != null) { @@ -84,6 +96,7 @@ public enum GameManager { } } + @Override public void sendPlayerBoolean(UUID gameId, UUID userId, Boolean data) { GameController gameController = getGameControllerSafe(gameId); if (gameController != null) { @@ -91,6 +104,7 @@ public enum GameManager { } } + @Override public void sendPlayerInteger(UUID gameId, UUID userId, Integer data) { GameController gameController = getGameControllerSafe(gameId); if (gameController != null) { @@ -98,6 +112,7 @@ public enum GameManager { } } + @Override public void quitMatch(UUID gameId, UUID userId) { GameController gameController = getGameControllerSafe(gameId); if (gameController != null) { @@ -105,6 +120,7 @@ public enum GameManager { } } + @Override public void sendPlayerAction(PlayerAction playerAction, UUID gameId, UUID userId, Object data) { GameController gameController = getGameControllerSafe(gameId); if (gameController != null) { @@ -112,6 +128,7 @@ public enum GameManager { } } + @Override public boolean watchGame(UUID gameId, UUID userId) { GameController gameController = getGameControllerSafe(gameId); if (gameController != null) { @@ -120,6 +137,7 @@ public enum GameManager { return false; } + @Override public void stopWatching(UUID gameId, UUID userId) { GameController gameController = getGameControllerSafe(gameId); if (gameController != null) { @@ -127,6 +145,7 @@ public enum GameManager { } } + @Override public void cheat(UUID gameId, UUID userId, UUID playerId, DeckCardLists deckList) { GameController gameController = getGameControllerSafe(gameId); if (gameController != null) { @@ -134,6 +153,7 @@ public enum GameManager { } } + @Override public boolean cheat(UUID gameId, UUID userId, UUID playerId, String cardName) { GameController gameController = getGameControllerSafe(gameId); if (gameController != null) { @@ -142,6 +162,7 @@ public enum GameManager { return false; } + @Override public void removeGame(UUID gameId) { GameController gameController = getGameControllerSafe(gameId); if (gameController != null) { @@ -156,6 +177,7 @@ public enum GameManager { } } + @Override public boolean saveGame(UUID gameId) { GameController gameController = getGameControllerSafe(gameId); if (gameController != null) { @@ -164,6 +186,7 @@ public enum GameManager { return false; } + @Override public GameView getGameView(UUID gameId, UUID playerId) { GameController gameController = getGameControllerSafe(gameId); if (gameController != null) { @@ -172,10 +195,12 @@ public enum GameManager { return null; } + @Override public int getNumberActiveGames() { return getGameController().size(); } + @Override public Map getGameController() { Map newControllers = new HashMap<>(); final Lock r = gameControllersLock.readLock(); diff --git a/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java b/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java index 38e0ea9647d..b7f3c9aae9a 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java +++ b/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java @@ -11,8 +11,8 @@ import mage.interfaces.callback.ClientCallback; import mage.interfaces.callback.ClientCallbackMethod; import mage.players.Player; import mage.server.User; -import mage.server.UserManager; -import mage.server.util.ThreadExecutor; +import mage.server.managers.UserManager; +import mage.server.managers.ManagerFactory; import mage.view.*; import org.apache.log4j.Logger; @@ -28,12 +28,15 @@ public class GameSessionPlayer extends GameSessionWatcher { private static final Logger logger = Logger.getLogger(GameSessionPlayer.class); + private final UserManager userManager; private final UUID playerId; - private static final ExecutorService callExecutor = ThreadExecutor.instance.getCallExecutor(); + private final ExecutorService callExecutor; - public GameSessionPlayer(Game game, UUID userId, UUID playerId) { - super(userId, game, true); + public GameSessionPlayer(ManagerFactory managerFactory, Game game, UUID userId, UUID playerId) { + super(managerFactory.userManager(), userId, game, true); + this.userManager = managerFactory.userManager(); + callExecutor = managerFactory.threadExecutor().getCallExecutor(); this.playerId = playerId; } @@ -44,14 +47,14 @@ public class GameSessionPlayer extends GameSessionWatcher { public void ask(final String question, final Map options) { if (!killed) { - UserManager.instance.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_ASK, game.getId(), new GameClientMessage(getGameView(), question, options))) + userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_ASK, game.getId(), new GameClientMessage(getGameView(), question, options))) ); } } public void target(final String question, final CardsView cardView, final Set targets, final boolean required, final Map options) { if (!killed) { - UserManager.instance.getUser(userId).ifPresent(user -> { + userManager.getUser(userId).ifPresent(user -> { user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_TARGET, game.getId(), new GameClientMessage(getGameView(), question, cardView, targets, required, options))); }); @@ -60,13 +63,13 @@ public class GameSessionPlayer extends GameSessionWatcher { public void select(final String message, final Map options) { if (!killed) { - UserManager.instance.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_SELECT, game.getId(), new GameClientMessage(getGameView(), message, options)))); + userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_SELECT, game.getId(), new GameClientMessage(getGameView(), message, options)))); } } public void chooseAbility(final AbilityPickerView abilities) { if (!killed) { - UserManager.instance.getUser(userId).ifPresent(user + userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_CHOOSE_ABILITY, game.getId(), abilities))); } @@ -74,7 +77,7 @@ public class GameSessionPlayer extends GameSessionWatcher { public void choosePile(final String message, final CardsView pile1, final CardsView pile2) { if (!killed) { - UserManager.instance.getUser(userId).ifPresent(user + userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_CHOOSE_PILE, game.getId(), new GameClientMessage(message, pile1, pile2)))); } @@ -82,7 +85,7 @@ public class GameSessionPlayer extends GameSessionWatcher { public void chooseChoice(final Choice choice) { if (!killed) { - UserManager.instance.getUser(userId).ifPresent(user + userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_CHOOSE_CHOICE, game.getId(), new GameClientMessage(choice)))); } @@ -90,14 +93,14 @@ public class GameSessionPlayer extends GameSessionWatcher { public void playMana(final String message, final Map options) { if (!killed) { - UserManager.instance.getUser(userId).ifPresent(user + userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_PLAY_MANA, game.getId(), new GameClientMessage(getGameView(), message, options)))); } } public void playXMana(final String message) { if (!killed) { - UserManager.instance.getUser(userId).ifPresent(user + userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_PLAY_XMANA, game.getId(), new GameClientMessage(getGameView(), message)))); } @@ -105,7 +108,7 @@ public class GameSessionPlayer extends GameSessionWatcher { public void getAmount(final String message, final int min, final int max) { if (!killed) { - UserManager.instance.getUser(userId).ifPresent(user -> { + userManager.getUser(userId).ifPresent(user -> { user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_GET_AMOUNT, game.getId(), new GameClientMessage(message, min, max))); }); } @@ -113,15 +116,15 @@ public class GameSessionPlayer extends GameSessionWatcher { public void endGameInfo(Table table) { if (!killed) { - UserManager.instance.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.END_GAME_INFO, game.getId(), getGameEndView(playerId, table)))); + userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.END_GAME_INFO, game.getId(), getGameEndView(playerId, table)))); } } public void requestPermissionToRollbackTurn(UUID requestingUserId, int numberTurns) { if (!killed) { - Optional requestingUser = UserManager.instance.getUser(requestingUserId); - Optional requestedUser = UserManager.instance.getUser(userId); + Optional requestingUser = userManager.getUser(requestingUserId); + Optional requestedUser = userManager.getUser(userId); if (requestedUser.isPresent() && requestingUser.isPresent()) { String message; switch (numberTurns) { @@ -147,8 +150,8 @@ public class GameSessionPlayer extends GameSessionWatcher { public void requestPermissionToSeeHandCards(UUID watcherId) { if (!killed) { - Optional watcher = UserManager.instance.getUser(watcherId); - Optional user = UserManager.instance.getUser(userId); + Optional watcher = userManager.getUser(watcherId); + Optional user = userManager.getUser(userId); if (user.isPresent() && watcher.isPresent()) { UserRequestMessage userRequestMessage = new UserRequestMessage( "User request", @@ -217,7 +220,7 @@ public class GameSessionPlayer extends GameSessionWatcher { } public void removeGame() { - UserManager.instance.getUser(userId).ifPresent(user -> user.removeGame(playerId)); + userManager.getUser(userId).ifPresent(user -> user.removeGame(playerId)); } diff --git a/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java b/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java index ebe711cfcf0..6aafddbf672 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java +++ b/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java @@ -6,7 +6,7 @@ import mage.interfaces.callback.ClientCallback; import mage.interfaces.callback.ClientCallbackMethod; import mage.players.Player; import mage.server.User; -import mage.server.UserManager; +import mage.server.managers.UserManager; import mage.view.GameClientMessage; import mage.view.GameEndView; import mage.view.GameView; @@ -25,12 +25,14 @@ public class GameSessionWatcher { protected static final Logger logger = Logger.getLogger(GameSessionWatcher.class); + private final UserManager userManager; protected final UUID userId; protected final Game game; protected boolean killed = false; protected final boolean isPlayer; - public GameSessionWatcher(UUID userId, Game game, boolean isPlayer) { + public GameSessionWatcher(UserManager userManager, UUID userId, Game game, boolean isPlayer) { + this.userManager = userManager; this.userId = userId; this.game = game; this.isPlayer = isPlayer; @@ -38,7 +40,7 @@ public class GameSessionWatcher { public boolean init() { if (!killed) { - Optional user = UserManager.instance.getUser(userId); + Optional user = userManager.getUser(userId); if (user.isPresent()) { user.get().fireCallback(new ClientCallback(ClientCallbackMethod.GAME_INIT, game.getId(), getGameView())); return true; @@ -49,28 +51,28 @@ public class GameSessionWatcher { public void update() { if (!killed) { - UserManager.instance.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_UPDATE, game.getId(), getGameView()))); + userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_UPDATE, game.getId(), getGameView()))); } } public void inform(final String message) { if (!killed) { - UserManager.instance.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_INFORM, game.getId(), new GameClientMessage(getGameView(), message)))); + userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_INFORM, game.getId(), new GameClientMessage(getGameView(), message)))); } } public void informPersonal(final String message) { if (!killed) { - UserManager.instance.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_INFORM_PERSONAL, game.getId(), new GameClientMessage(getGameView(), message)))); + userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_INFORM_PERSONAL, game.getId(), new GameClientMessage(getGameView(), message)))); } } public void gameOver(final String message) { if (!killed) { - UserManager.instance.getUser(userId).ifPresent(user -> { + userManager.getUser(userId).ifPresent(user -> { user.removeGameWatchInfo(game.getId()); user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_OVER, game.getId(), message)); }); @@ -86,7 +88,7 @@ public class GameSessionWatcher { public void gameError(final String message) { if (!killed) { - UserManager.instance.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_ERROR, game.getId(), message))); + userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_ERROR, game.getId(), message))); } } 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 f597cdbaf5e..c69f29f69d7 100644 --- a/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java +++ b/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java @@ -1,12 +1,5 @@ - 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; @@ -16,18 +9,21 @@ import mage.game.match.MatchOptions; import mage.game.tournament.TournamentOptions; import mage.players.PlayerType; import mage.server.RoomImpl; -import mage.server.TableManager; import mage.server.User; -import mage.server.UserManager; -import mage.server.tournament.TournamentManager; -import mage.server.util.ConfigSettings; -import mage.server.util.ThreadExecutor; +import mage.server.managers.ManagerFactory; import mage.view.MatchView; import mage.view.RoomUsersView; 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 */ @@ -40,9 +36,12 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { private static List matchView = new ArrayList<>(); private static List roomUsersView = new ArrayList<>(); + private final ManagerFactory managerFactory; private final ConcurrentHashMap tables = new ConcurrentHashMap<>(); - public GamesRoomImpl() { + public GamesRoomImpl(ManagerFactory managerFactory) { + super(managerFactory.chatManager()); + this.managerFactory = managerFactory; UPDATE_EXECUTOR.scheduleAtFixedRate(() -> { try { update(); @@ -71,7 +70,7 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { } else { // more since 50 matches finished since this match so removeUserFromAllTablesAndChat it if (table.isTournament()) { - TournamentManager.instance.removeTournament(table.getTournament().getId()); + managerFactory.tournamentManager().removeTournament(table.getTournament().getId()); } this.removeTable(table.getId()); } @@ -79,7 +78,7 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { tableView = tableList; matchView = matchList; List users = new ArrayList<>(); - for (User user : UserManager.instance.getUsers()) { + for (User user : managerFactory.userManager().getUsers()) { if (user.getUserState() != User.UserState.Offline && !user.getName().equals("Admin")) { try { users.add(new UsersView(user.getUserData().getFlagName(), user.getName(), @@ -108,9 +107,9 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { users.sort((one, two) -> one.getUserName().compareToIgnoreCase(two.getUserName())); List roomUserInfo = new ArrayList<>(); roomUserInfo.add(new RoomUsersView(users, - GameManager.instance.getNumberActiveGames(), - ThreadExecutor.instance.getActiveThreads(ThreadExecutor.instance.getGameExecutor()), - ConfigSettings.instance.getMaxGameThreads() + managerFactory.gameManager().getNumberActiveGames(), + managerFactory.threadExecutor().getActiveThreads(managerFactory.threadExecutor().getGameExecutor()), + managerFactory.configSettings().getMaxGameThreads() )); roomUsersView = roomUserInfo; } @@ -123,7 +122,7 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { @Override public boolean joinTable(UUID userId, UUID tableId, String name, PlayerType playerType, int skill, DeckCardLists deckList, String password) throws MageException { if (tables.containsKey(tableId)) { - return TableManager.instance.joinTable(userId, tableId, name, playerType, skill, deckList, password); + return managerFactory.tableManager().joinTable(userId, tableId, name, playerType, skill, deckList, password); } else { return false; } @@ -131,7 +130,7 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { @Override public TableView createTable(UUID userId, MatchOptions options) { - Table table = TableManager.instance.createTable(this.getRoomId(), userId, options); + Table table = managerFactory.tableManager().createTable(this.getRoomId(), userId, options); tables.put(table.getId(), table); return new TableView(table); } @@ -139,7 +138,7 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { @Override public boolean joinTournamentTable(UUID userId, UUID tableId, String name, PlayerType playerType, int skill, DeckCardLists deckList, String password) throws GameException { if (tables.containsKey(tableId)) { - return TableManager.instance.joinTournament(userId, tableId, name, playerType, skill, deckList, password); + return managerFactory.tableManager().joinTournament(userId, tableId, name, playerType, skill, deckList, password); } else { return false; } @@ -147,7 +146,7 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { @Override public TableView createTournamentTable(UUID userId, TournamentOptions options) { - Table table = TableManager.instance.createTournamentTable(this.getRoomId(), userId, options); + Table table = managerFactory.tableManager().createTournamentTable(this.getRoomId(), userId, options); tables.put(table.getId(), table); return new TableView(table); } @@ -179,12 +178,12 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { @Override public void leaveTable(UUID userId, UUID tableId) { - TableManager.instance.leaveTable(userId, tableId); + managerFactory.tableManager().leaveTable(userId, tableId); } @Override public boolean watchTable(UUID userId, UUID tableId) throws MageException { - return TableManager.instance.watchTable(userId, tableId); + return managerFactory.tableManager().watchTable(userId, tableId); } @Override diff --git a/Mage.Server/src/main/java/mage/server/game/GamesRoomManager.java b/Mage.Server/src/main/java/mage/server/game/GamesRoomManagerImpl.java similarity index 66% rename from Mage.Server/src/main/java/mage/server/game/GamesRoomManager.java rename to Mage.Server/src/main/java/mage/server/game/GamesRoomManagerImpl.java index c67c834fc21..77ea11bc72b 100644 --- a/Mage.Server/src/main/java/mage/server/game/GamesRoomManager.java +++ b/Mage.Server/src/main/java/mage/server/game/GamesRoomManagerImpl.java @@ -1,5 +1,7 @@ package mage.server.game; +import mage.server.managers.GamesRoomManager; +import mage.server.managers.ManagerFactory; import org.apache.log4j.Logger; import java.util.Optional; @@ -9,36 +11,44 @@ import java.util.concurrent.ConcurrentHashMap; /** * @author BetaSteward_at_googlemail.com */ -public enum GamesRoomManager { - instance; +public class GamesRoomManagerImpl implements GamesRoomManager { + private final ManagerFactory managerFactory; private final ConcurrentHashMap rooms = new ConcurrentHashMap<>(); - private final UUID mainRoomId; - private final UUID mainChatId; - private static final Logger logger = Logger.getLogger(GamesRoomManager.class); + private UUID mainRoomId; + private UUID mainChatId; + private static final Logger logger = Logger.getLogger(GamesRoomManagerImpl.class); - GamesRoomManager() { - GamesRoom mainRoom = new GamesRoomImpl(); + public GamesRoomManagerImpl(ManagerFactory managerFactory) { + this.managerFactory = managerFactory; + } + + public void init() { + GamesRoom mainRoom = new GamesRoomImpl(managerFactory); mainRoomId = mainRoom.getRoomId(); mainChatId = mainRoom.getChatId(); rooms.put(mainRoomId, mainRoom); } + @Override public UUID createRoom() { - GamesRoom room = new GamesRoomImpl(); + GamesRoom room = new GamesRoomImpl(managerFactory); rooms.put(room.getRoomId(), room); return room.getRoomId(); } + @Override public UUID getMainRoomId() { return mainRoomId; } + @Override public UUID getMainChatId() { return mainChatId; } + @Override public Optional getRoom(UUID roomId) { if (rooms.containsKey(roomId)) { return Optional.of(rooms.get(roomId)); @@ -48,6 +58,7 @@ public enum GamesRoomManager { } + @Override public void removeTable(UUID tableId) { for (GamesRoom room : rooms.values()) { room.removeTable(tableId); diff --git a/Mage.Server/src/main/java/mage/server/game/ReplayManager.java b/Mage.Server/src/main/java/mage/server/game/ReplayManagerImpl.java similarity index 67% rename from Mage.Server/src/main/java/mage/server/game/ReplayManager.java rename to Mage.Server/src/main/java/mage/server/game/ReplayManagerImpl.java index beb554780d7..c41c554067e 100644 --- a/Mage.Server/src/main/java/mage/server/game/ReplayManager.java +++ b/Mage.Server/src/main/java/mage/server/game/ReplayManagerImpl.java @@ -1,46 +1,56 @@ - - package mage.server.game; +import mage.server.managers.ReplayManager; +import mage.server.managers.ManagerFactory; + import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import mage.server.UserManager; /** - * * @author BetaSteward_at_googlemail.com */ -public enum ReplayManager { - instance; +public class ReplayManagerImpl implements ReplayManager { private final ConcurrentHashMap replaySessions = new ConcurrentHashMap<>(); + private final ManagerFactory managerFactory; - public void replayGame(UUID gameId, UUID userId) { - ReplaySession replaySession = new ReplaySession(gameId, userId); - replaySessions.put(gameId.toString() + userId.toString(), replaySession); - UserManager.instance.getUser(userId).ifPresent(user->user.ccReplayGame(gameId)); + public ReplayManagerImpl(ManagerFactory managerFactory) { + this.managerFactory = managerFactory; } + @Override + public void replayGame(UUID gameId, UUID userId) { + ReplaySession replaySession = new ReplaySession(managerFactory, gameId, userId); + replaySessions.put(gameId.toString() + userId.toString(), replaySession); + managerFactory.userManager().getUser(userId).ifPresent(user -> user.ccReplayGame(gameId)); + } + + @Override public void startReplay(UUID gameId, UUID userId) { replaySessions.get(gameId.toString() + userId.toString()).replay(); } + @Override public void stopReplay(UUID gameId, UUID userId) { replaySessions.get(gameId.toString() + userId.toString()).stop(); } + @Override public void nextPlay(UUID gameId, UUID userId) { replaySessions.get(gameId.toString() + userId.toString()).next(); } + @Override public void previousPlay(UUID gameId, UUID userId) { replaySessions.get(gameId.toString() + userId.toString()).previous(); } + @Override public void skipForward(UUID gameId, UUID userId, int moves) { replaySessions.get(gameId.toString() + userId.toString()).next(moves); } + @Override public void endReplay(UUID gameId, UUID userId) { replaySessions.remove(gameId.toString() + userId.toString()); } diff --git a/Mage.Server/src/main/java/mage/server/game/ReplaySession.java b/Mage.Server/src/main/java/mage/server/game/ReplaySession.java index bb27a7a666e..ac6592cd7ab 100644 --- a/Mage.Server/src/main/java/mage/server/game/ReplaySession.java +++ b/Mage.Server/src/main/java/mage/server/game/ReplaySession.java @@ -1,32 +1,32 @@ - - package mage.server.game; -import java.util.UUID; - import mage.game.Game; import mage.game.GameState; import mage.interfaces.callback.ClientCallback; import mage.interfaces.callback.ClientCallbackMethod; -import mage.server.UserManager; +import mage.server.managers.ManagerFactory; import mage.view.GameView; +import java.util.UUID; + /** * @author BetaSteward_at_googlemail.com */ public class ReplaySession implements GameCallback { + private final ManagerFactory managerFactory; private final GameReplay replay; protected final UUID userId; - ReplaySession(UUID gameId, UUID userId) { + ReplaySession(ManagerFactory managerFactory, UUID gameId, UUID userId) { + this.managerFactory = managerFactory; this.replay = new GameReplay(gameId); this.userId = userId; } public void replay() { replay.start(); - UserManager.instance.getUser(userId).ifPresent(user -> + managerFactory.userManager().getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.REPLAY_INIT, replay.getGame().getId(), new GameView(replay.next(), replay.getGame(), null, null)))); } @@ -52,17 +52,17 @@ public class ReplaySession implements GameCallback { @Override public void gameResult(final String result) { - UserManager.instance.getUser(userId).ifPresent(user -> + managerFactory.userManager().getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.REPLAY_DONE, replay.getGame().getId(), result))); - ReplayManager.instance.endReplay(replay.getGame().getId(), userId); + managerFactory.replayManager().endReplay(replay.getGame().getId(), userId); } private void updateGame(final GameState state, Game game) { if (state == null) { gameResult("game ended"); } else { - UserManager.instance.getUser(userId).ifPresent(user -> + managerFactory.userManager().getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.REPLAY_UPDATE, replay.getGame().getId(), new GameView(state, game, null, null)))); } diff --git a/Mage.Server/src/main/java/mage/server/managers/ChatManager.java b/Mage.Server/src/main/java/mage/server/managers/ChatManager.java new file mode 100644 index 00000000000..504911f7b28 --- /dev/null +++ b/Mage.Server/src/main/java/mage/server/managers/ChatManager.java @@ -0,0 +1,36 @@ +package mage.server.managers; + +import mage.game.Game; +import mage.server.ChatSession; +import mage.server.DisconnectReason; +import mage.server.exceptions.UserNotFoundException; +import mage.view.ChatMessage; + +import java.util.List; +import java.util.UUID; + +public interface ChatManager { + UUID createChatSession(String info); + + void joinChat(UUID chatId, UUID userId); + + void clearUserMessageStorage(); + + void leaveChat(UUID chatId, UUID userId); + + void destroyChatSession(UUID chatId); + + void broadcast(UUID chatId, String userName, String message, ChatMessage.MessageColor color, boolean withTime, Game game, ChatMessage.MessageType messageType, ChatMessage.SoundToPlay soundToPlay); + + void broadcast(UUID userId, String message, ChatMessage.MessageColor color) throws UserNotFoundException; + + void sendReconnectMessage(UUID userId); + + void sendLostConnectionMessage(UUID userId, DisconnectReason reason); + + void sendMessageToUserChats(UUID userId, String message); + + void removeUser(UUID userId, DisconnectReason reason); + + List getChatSessions(); +} diff --git a/Mage.Server/src/main/java/mage/server/managers/ConfigSettings.java b/Mage.Server/src/main/java/mage/server/managers/ConfigSettings.java new file mode 100644 index 00000000000..be6d6c6c23d --- /dev/null +++ b/Mage.Server/src/main/java/mage/server/managers/ConfigSettings.java @@ -0,0 +1,72 @@ +package mage.server.managers; + +import mage.server.util.config.GamePlugin; +import mage.server.util.config.Plugin; + +import java.util.List; + +public interface ConfigSettings { + String getServerAddress(); + + String getServerName(); + + int getPort(); + + int getSecondaryBindPort(); + + int getLeasePeriod(); + + int getSocketWriteTimeout(); + + int getMaxPoolSize(); + + int getNumAcceptThreads(); + + int getBacklogSize(); + + int getMaxGameThreads(); + + int getMaxSecondsIdle(); + + int getMinUserNameLength(); + + int getMaxUserNameLength(); + + String getInvalidUserNamePattern(); + + int getMinPasswordLength(); + + int getMaxPasswordLength(); + + String getMaxAiOpponents(); + + Boolean isSaveGameActivated(); + + Boolean isAuthenticationActivated(); + + String getGoogleAccount(); + + String getMailgunApiKey(); + + String getMailgunDomain(); + + String getMailSmtpHost(); + + String getMailSmtpPort(); + + String getMailUser(); + + String getMailPassword(); + + String getMailFromAddress(); + + List getPlayerTypes(); + + List getGameTypes(); + + List getTournamentTypes(); + + List getDraftCubes(); + + List getDeckTypes(); +} diff --git a/Mage.Server/src/main/java/mage/server/managers/DraftManager.java b/Mage.Server/src/main/java/mage/server/managers/DraftManager.java new file mode 100644 index 00000000000..d9d17a9900d --- /dev/null +++ b/Mage.Server/src/main/java/mage/server/managers/DraftManager.java @@ -0,0 +1,34 @@ +package mage.server.managers; + +import mage.game.draft.Draft; +import mage.server.draft.DraftController; +import mage.view.DraftPickView; + +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public interface DraftManager { + UUID createDraftSession(Draft draft, ConcurrentHashMap userPlayerMap, UUID tableId); + + void joinDraft(UUID draftId, UUID userId); + + void destroyChatSession(UUID gameId); + + DraftPickView sendCardPick(UUID draftId, UUID userId, UUID cardId, Set hiddenCards); + + void sendCardMark(UUID draftId, UUID userId, UUID cardId); + + void removeSession(UUID userId); + + void kill(UUID draftId, UUID userId); + + void timeout(UUID gameId, UUID userId); + + void removeDraft(UUID draftId); + + DraftController getControllerByDraftId(UUID draftId); + + Optional getController(UUID tableId); +} diff --git a/Mage.Server/src/main/java/mage/server/managers/GameManager.java b/Mage.Server/src/main/java/mage/server/managers/GameManager.java new file mode 100644 index 00000000000..4531a537428 --- /dev/null +++ b/Mage.Server/src/main/java/mage/server/managers/GameManager.java @@ -0,0 +1,54 @@ +package mage.server.managers; + +import mage.cards.decks.DeckCardLists; +import mage.constants.ManaType; +import mage.constants.PlayerAction; +import mage.game.Game; +import mage.game.GameOptions; +import mage.server.game.GameController; +import mage.view.GameView; + +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public interface GameManager { + UUID createGameSession(Game game, ConcurrentHashMap userPlayerMap, UUID tableId, UUID choosingPlayerId, GameOptions gameOptions); + + void joinGame(UUID gameId, UUID userId); + + Optional getChatId(UUID gameId); + + void sendPlayerUUID(UUID gameId, UUID userId, UUID data); + + void sendPlayerString(UUID gameId, UUID userId, String data); + + void sendPlayerManaType(UUID gameId, UUID playerId, UUID userId, ManaType data); + + void sendPlayerBoolean(UUID gameId, UUID userId, Boolean data); + + void sendPlayerInteger(UUID gameId, UUID userId, Integer data); + + void quitMatch(UUID gameId, UUID userId); + + void sendPlayerAction(PlayerAction playerAction, UUID gameId, UUID userId, Object data); + + boolean watchGame(UUID gameId, UUID userId); + + void stopWatching(UUID gameId, UUID userId); + + void cheat(UUID gameId, UUID userId, UUID playerId, DeckCardLists deckList); + + boolean cheat(UUID gameId, UUID userId, UUID playerId, String cardName); + + void removeGame(UUID gameId); + + boolean saveGame(UUID gameId); + + GameView getGameView(UUID gameId, UUID playerId); + + int getNumberActiveGames(); + + Map getGameController(); +} diff --git a/Mage.Server/src/main/java/mage/server/managers/GamesRoomManager.java b/Mage.Server/src/main/java/mage/server/managers/GamesRoomManager.java new file mode 100644 index 00000000000..e2a877358a5 --- /dev/null +++ b/Mage.Server/src/main/java/mage/server/managers/GamesRoomManager.java @@ -0,0 +1,18 @@ +package mage.server.managers; + +import mage.server.game.GamesRoom; + +import java.util.Optional; +import java.util.UUID; + +public interface GamesRoomManager { + UUID createRoom(); + + UUID getMainRoomId(); + + UUID getMainChatId(); + + Optional getRoom(UUID roomId); + + void removeTable(UUID tableId); +} diff --git a/Mage.Server/src/main/java/mage/server/managers/MailClient.java b/Mage.Server/src/main/java/mage/server/managers/MailClient.java new file mode 100644 index 00000000000..bfb125a6c91 --- /dev/null +++ b/Mage.Server/src/main/java/mage/server/managers/MailClient.java @@ -0,0 +1,7 @@ +package mage.server.managers; + +public interface MailClient { + + boolean sendMessage(String email, String subject, String text); + +} diff --git a/Mage.Server/src/main/java/mage/server/managers/ManagerFactory.java b/Mage.Server/src/main/java/mage/server/managers/ManagerFactory.java new file mode 100644 index 00000000000..917c681f928 --- /dev/null +++ b/Mage.Server/src/main/java/mage/server/managers/ManagerFactory.java @@ -0,0 +1,29 @@ +package mage.server.managers; + +public interface ManagerFactory { + ChatManager chatManager(); + + DraftManager draftManager(); + + GameManager gameManager(); + + GamesRoomManager gamesRoomManager(); + + MailClient mailClient(); + + MailClient mailgunClient(); + + ReplayManager replayManager(); + + SessionManager sessionManager(); + + TableManager tableManager(); + + UserManager userManager(); + + ConfigSettings configSettings(); + + ThreadExecutor threadExecutor(); + + TournamentManager tournamentManager(); +} diff --git a/Mage.Server/src/main/java/mage/server/managers/ReplayManager.java b/Mage.Server/src/main/java/mage/server/managers/ReplayManager.java new file mode 100644 index 00000000000..df6148cee98 --- /dev/null +++ b/Mage.Server/src/main/java/mage/server/managers/ReplayManager.java @@ -0,0 +1,19 @@ +package mage.server.managers; + +import java.util.UUID; + +public interface ReplayManager { + void replayGame(UUID gameId, UUID userId); + + void startReplay(UUID gameId, UUID userId); + + void stopReplay(UUID gameId, UUID userId); + + void nextPlay(UUID gameId, UUID userId); + + void previousPlay(UUID gameId, UUID userId); + + void skipForward(UUID gameId, UUID userId, int moves); + + void endReplay(UUID gameId, UUID userId); +} diff --git a/Mage.Server/src/main/java/mage/server/managers/SessionManager.java b/Mage.Server/src/main/java/mage/server/managers/SessionManager.java new file mode 100644 index 00000000000..b5dc0d109b2 --- /dev/null +++ b/Mage.Server/src/main/java/mage/server/managers/SessionManager.java @@ -0,0 +1,43 @@ +package mage.server.managers; + +import mage.MageException; +import mage.players.net.UserData; +import mage.server.DisconnectReason; +import mage.server.Session; +import mage.server.User; +import org.jboss.remoting.callback.InvokerCallbackHandler; + +import javax.annotation.Nonnull; +import java.util.Optional; + +public interface SessionManager { + Optional getSession(@Nonnull String sessionId); + + void createSession(String sessionId, InvokerCallbackHandler callbackHandler); + + boolean registerUser(String sessionId, String userName, String password, String email) throws MageException; + + boolean connectUser(String sessionId, String userName, String password, String userIdStr) throws MageException; + + boolean connectAdmin(String sessionId); + + boolean setUserData(String userName, String sessionId, UserData userData, String clientVersion, String userIdStr) throws MageException; + + void disconnect(String sessionId, DisconnectReason reason); + + void disconnect(String sessionId, DisconnectReason reason, Session directSession); + + void disconnectUser(String sessionId, String userSessionId); + + void endUserSession(String sessionId, String userSessionId); + + boolean isAdmin(String sessionId); + + boolean isValidSession(@Nonnull String sessionId); + + Optional getUser(@Nonnull String sessionId); + + boolean extendUserSession(String sessionId, String pingInfo); + + void sendErrorMessageToClient(String sessionId, String message); +} diff --git a/Mage.Server/src/main/java/mage/server/managers/TableManager.java b/Mage.Server/src/main/java/mage/server/managers/TableManager.java new file mode 100644 index 00000000000..7ba5d81c757 --- /dev/null +++ b/Mage.Server/src/main/java/mage/server/managers/TableManager.java @@ -0,0 +1,86 @@ +package mage.server.managers; + +import mage.MageException; +import mage.cards.decks.DeckCardLists; +import mage.game.GameException; +import mage.game.Table; +import mage.game.draft.Draft; +import mage.game.match.Match; +import mage.game.match.MatchOptions; +import mage.game.tournament.Tournament; +import mage.game.tournament.TournamentOptions; +import mage.game.tournament.TournamentPlayer; +import mage.players.PlayerType; +import mage.server.TableController; + +import java.util.Collection; +import java.util.Optional; +import java.util.UUID; + +public interface TableManager { + Table createTable(UUID roomId, UUID userId, MatchOptions options); + + Table createTable(UUID roomId, MatchOptions options); + + Table createTournamentTable(UUID roomId, UUID userId, TournamentOptions options); + + Table getTable(UUID tableId); + + Optional getMatch(UUID tableId); + + Collection
getTables(); + + Collection getControllers(); + + Optional getController(UUID tableId); + + boolean joinTable(UUID userId, UUID tableId, String name, PlayerType playerType, int skill, DeckCardLists deckList, String password) throws MageException; + + boolean joinTournament(UUID userId, UUID tableId, String name, PlayerType playerType, int skill, DeckCardLists deckList, String password) throws GameException; + + boolean submitDeck(UUID userId, UUID tableId, DeckCardLists deckList) throws MageException; + + void updateDeck(UUID userId, UUID tableId, DeckCardLists deckList) throws MageException; + + // removeUserFromAllTablesAndChat user from all tournament sub tables + void userQuitTournamentSubTables(UUID userId); + + // removeUserFromAllTablesAndChat user from all sub tables of a tournament + void userQuitTournamentSubTables(UUID tournamentId, UUID userId); + + boolean isTableOwner(UUID tableId, UUID userId); + + boolean removeTable(UUID userId, UUID tableId); + + void leaveTable(UUID userId, UUID tableId); + + Optional getChatId(UUID tableId); + + void startMatch(UUID userId, UUID roomId, UUID tableId); + + void startTournamentSubMatch(UUID roomId, UUID tableId); + + void startTournament(UUID userId, UUID roomId, UUID tableId); + + void startDraft(UUID tableId, Draft draft); + + boolean watchTable(UUID userId, UUID tableId); + + void endGame(UUID tableId); + + void endDraft(UUID tableId, Draft draft); + + void endTournament(UUID tableId, Tournament tournament); + + void swapSeats(UUID tableId, UUID userId, int seatNum1, int seatNum2); + + void construct(UUID tableId); + + void initTournament(UUID tableId); + + void addPlayer(UUID userId, UUID tableId, TournamentPlayer player) throws GameException; + + void removeTable(UUID tableId); + + void debugServerState(); +} diff --git a/Mage.Server/src/main/java/mage/server/managers/ThreadExecutor.java b/Mage.Server/src/main/java/mage/server/managers/ThreadExecutor.java new file mode 100644 index 00000000000..46f59b53435 --- /dev/null +++ b/Mage.Server/src/main/java/mage/server/managers/ThreadExecutor.java @@ -0,0 +1,16 @@ +package mage.server.managers; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledExecutorService; + +public interface ThreadExecutor { + int getActiveThreads(ExecutorService executerService); + + ExecutorService getCallExecutor(); + + ExecutorService getGameExecutor(); + + ScheduledExecutorService getTimeoutExecutor(); + + ScheduledExecutorService getTimeoutIdleExecutor(); +} diff --git a/Mage.Server/src/main/java/mage/server/managers/TournamentManager.java b/Mage.Server/src/main/java/mage/server/managers/TournamentManager.java new file mode 100644 index 00000000000..852588c2643 --- /dev/null +++ b/Mage.Server/src/main/java/mage/server/managers/TournamentManager.java @@ -0,0 +1,32 @@ +package mage.server.managers; + +import mage.cards.decks.Deck; +import mage.game.tournament.Tournament; +import mage.server.tournament.TournamentController; +import mage.view.TournamentView; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public interface TournamentManager { + Optional getTournamentController(UUID tournamentId); + + void createTournamentSession(Tournament tournament, ConcurrentHashMap userPlayerMap, UUID tableId); + + void joinTournament(UUID tournamentId, UUID userId); + + void quit(UUID tournamentId, UUID userId); + + void timeout(UUID tournamentId, UUID userId); + + void submitDeck(UUID tournamentId, UUID playerId, Deck deck); + + boolean updateDeck(UUID tournamentId, UUID playerId, Deck deck); + + TournamentView getTournamentView(UUID tournamentId); + + Optional getChatId(UUID tournamentId); + + void removeTournament(UUID tournamentId); +} diff --git a/Mage.Server/src/main/java/mage/server/managers/UserManager.java b/Mage.Server/src/main/java/mage/server/managers/UserManager.java new file mode 100644 index 00000000000..626609ee251 --- /dev/null +++ b/Mage.Server/src/main/java/mage/server/managers/UserManager.java @@ -0,0 +1,41 @@ +package mage.server.managers; + +import mage.server.AuthorizedUser; +import mage.server.DisconnectReason; +import mage.server.User; +import mage.view.UserView; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface UserManager { + Optional createUser(String userName, String host, AuthorizedUser authorizedUser); + + Optional getUser(UUID userId); + + Optional getUserByName(String userName); + + Collection getUsers(); + + boolean connectToSession(String sessionId, UUID userId); + + void disconnect(UUID userId, DisconnectReason reason); + + boolean isAdmin(UUID userId); + + void removeUserFromAllTablesAndChat(UUID userId, DisconnectReason reason); + + void informUserOpponents(UUID userId, String message); + + boolean extendUserSession(UUID userId, String pingInfo); + + List getUserInfoList(); + + void handleException(Exception ex); + + String getUserHistory(String userName); + + void updateUserHistory(); +} diff --git a/Mage.Server/src/main/java/mage/server/record/TableRecorderImpl.java b/Mage.Server/src/main/java/mage/server/record/TableRecorderImpl.java index cfcc360ed34..de1eaf025e1 100644 --- a/Mage.Server/src/main/java/mage/server/record/TableRecorderImpl.java +++ b/Mage.Server/src/main/java/mage/server/record/TableRecorderImpl.java @@ -3,16 +3,20 @@ package mage.server.record; import mage.game.Table; import mage.game.Table.TableRecorder; import mage.game.result.ResultProtos.TableProto; -import mage.server.UserManager; +import mage.server.managers.UserManager; -public enum TableRecorderImpl implements TableRecorder { +public class TableRecorderImpl implements TableRecorder { - instance; + private final UserManager userManager; + + public TableRecorderImpl(UserManager userManager) { + this.userManager = userManager; + } @Override public void record(Table table) { TableProto proto = table.toProto(); TableRecordRepository.instance.add(new TableRecord(proto, proto.getEndTimeMs())); - UserManager.instance.updateUserHistory(); + userManager.updateUserHistory(); } } diff --git a/Mage.Server/src/main/java/mage/server/tournament/TournamentController.java b/Mage.Server/src/main/java/mage/server/tournament/TournamentController.java index ef630681399..094e1420b90 100644 --- a/Mage.Server/src/main/java/mage/server/tournament/TournamentController.java +++ b/Mage.Server/src/main/java/mage/server/tournament/TournamentController.java @@ -17,14 +17,10 @@ import mage.game.tournament.Tournament; import mage.game.tournament.TournamentPairing; import mage.game.tournament.TournamentPlayer; import mage.players.PlayerType; -import mage.server.ChatManager; -import mage.server.TableManager; import mage.server.User; -import mage.server.UserManager; import mage.server.draft.DraftController; -import mage.server.draft.DraftManager; -import mage.server.game.GamesRoomManager; -import mage.server.util.ThreadExecutor; +import mage.server.managers.TableManager; +import mage.server.managers.ManagerFactory; import mage.view.ChatMessage.MessageColor; import mage.view.ChatMessage.MessageType; import mage.view.ChatMessage.SoundToPlay; @@ -44,6 +40,7 @@ public class TournamentController { private static final Logger logger = Logger.getLogger(TournamentController.class); + private final ManagerFactory managerFactory; private final UUID chatId; private final UUID tableId; private boolean started = false; @@ -51,9 +48,10 @@ public class TournamentController { private ConcurrentMap userPlayerMap = new ConcurrentHashMap<>(); private final ConcurrentMap tournamentSessions = new ConcurrentHashMap<>(); - public TournamentController(Tournament tournament, ConcurrentMap userPlayerMap, UUID tableId) { + public TournamentController(ManagerFactory managerFactory, Tournament tournament, ConcurrentMap userPlayerMap, UUID tableId) { + this.managerFactory = managerFactory; this.userPlayerMap = userPlayerMap; - chatId = ChatManager.instance.createChatSession("Tournament " + tournament.getId()); + chatId = managerFactory.chatManager().createChatSession("Tournament " + tournament.getId()); this.tournament = tournament; this.tableId = tableId; init(); @@ -67,7 +65,7 @@ public class TournamentController { checkPlayersState(); break; case INFO: - ChatManager.instance.broadcast(chatId, "", event.getMessage(), MessageColor.BLACK, true, null, MessageType.STATUS, null); + managerFactory.chatManager().broadcast(chatId, "", event.getMessage(), MessageColor.BLACK, true, null, MessageType.STATUS, null); logger.debug(tournament.getId() + " " + event.getMessage()); break; case START_DRAFT: @@ -122,7 +120,7 @@ public class TournamentController { if (!player.getPlayer().isHuman()) { player.setJoined(); logger.debug("player " + player.getPlayer().getId() + " has joined tournament " + tournament.getId()); - ChatManager.instance.broadcast(chatId, "", player.getPlayer().getLogName() + " has joined the tournament", MessageColor.BLACK, true, null, MessageType.STATUS, null); + managerFactory.chatManager().broadcast(chatId, "", player.getPlayer().getLogName() + " has joined the tournament", MessageColor.BLACK, true, null, MessageType.STATUS, null); } } checkStart(); @@ -132,7 +130,7 @@ public class TournamentController { UUID playerId = userPlayerMap.get(userId); if (playerId == null) { if (logger.isDebugEnabled()) { - UserManager.instance.getUser(userId).ifPresent(user + managerFactory.userManager().getUser(userId).ifPresent(user -> logger.debug(user.getName() + " shows tournament panel tournamentId: " + tournament.getId())); } @@ -143,16 +141,16 @@ public class TournamentController { return; } // first join of player - TournamentSession tournamentSession = new TournamentSession(tournament, userId, tableId, playerId); + TournamentSession tournamentSession = new TournamentSession(managerFactory, tournament, userId, tableId, playerId); tournamentSessions.put(playerId, tournamentSession); - Optional _user = UserManager.instance.getUser(userId); + Optional _user = managerFactory.userManager().getUser(userId); if (_user.isPresent()) { User user = _user.get(); user.addTournament(playerId, tournament.getId()); TournamentPlayer player = tournament.getPlayer(playerId); player.setJoined(); logger.debug("player " + player.getPlayer().getName() + " - client has joined tournament " + tournament.getId()); - ChatManager.instance.broadcast(chatId, "", player.getPlayer().getLogName() + " has joined the tournament", MessageColor.BLACK, true, null, MessageType.STATUS, null); + managerFactory.chatManager().broadcast(chatId, "", player.getPlayer().getLogName() + " has joined the tournament", MessageColor.BLACK, true, null, MessageType.STATUS, null); checkStart(); } else { logger.error("User not found userId: " + userId + " tournamentId: " + tournament.getId()); @@ -174,7 +172,7 @@ public class TournamentController { private void checkStart() { if (!started && allJoined()) { - ThreadExecutor.instance.getCallExecutor().execute(this::startTournament); + managerFactory.threadExecutor().getCallExecutor().execute(this::startTournament); } } @@ -211,15 +209,15 @@ public class TournamentController { tournamentSession.tournamentOver(); } this.tournamentSessions.clear(); - TableManager.instance.endTournament(tableId, tournament); + managerFactory.tableManager().endTournament(tableId, tournament); tournament.cleanUpOnTournamentEnd(); } private void startMatch(TournamentPairing pair, MatchOptions matchOptions) { try { - TableManager tableManager = TableManager.instance; - Table table = tableManager.createTable(GamesRoomManager.instance.getMainRoomId(), matchOptions); + TableManager tableManager = managerFactory.tableManager(); + Table table = tableManager.createTable(managerFactory.gamesRoomManager().getMainRoomId(), matchOptions); table.setTournamentSubTable(true); table.setTournament(tournament); table.setState(TableState.WAITING); @@ -261,8 +259,8 @@ public class TournamentController { private void startMultiplayerMatch(MultiplayerRound round, MatchOptions matchOptions) { try { - TableManager tableManager = TableManager.instance; - Table table = tableManager.createTable(GamesRoomManager.instance.getMainRoomId(), matchOptions); + TableManager tableManager = managerFactory.tableManager(); + Table table = tableManager.createTable(managerFactory.gamesRoomManager().getMainRoomId(), matchOptions); table.setTournamentSubTable(true); table.setTournament(tournament); table.setState(TableState.WAITING); @@ -287,16 +285,16 @@ public class TournamentController { } private void startDraft(Draft draft) { - TableManager.instance.startDraft(tableId, draft); + managerFactory.tableManager().startDraft(tableId, draft); } private void construct() { - TableManager.instance.construct(tableId); + managerFactory.tableManager().construct(tableId); } private void initTournament() { - if (TableManager.instance.getTable(tableId).getState() != TableState.DUELING) { - TableManager.instance.initTournament(tableId); + if (managerFactory.tableManager().getTable(tableId).getState() != TableState.DUELING) { + managerFactory.tableManager().initTournament(tableId); } } @@ -305,7 +303,7 @@ public class TournamentController { TournamentSession tournamentSession = tournamentSessions.get(playerId); tournamentSession.construct(timeout); getPlayerUserId(playerId).ifPresent(userId -> { - UserManager.instance.getUser(userId).ifPresent(user -> { + managerFactory.userManager().getUser(userId).ifPresent(user -> { user.addConstructing(playerId, tournamentSession); TournamentPlayer player = tournament.getPlayer(playerId); player.setState(TournamentPlayerState.CONSTRUCTING); @@ -319,7 +317,7 @@ public class TournamentController { TournamentPlayer player = tournament.getPlayer(playerId); if (player != null && !player.hasQuit()) { tournamentSessions.get(playerId).submitDeck(deck); - ChatManager.instance.broadcast(chatId, "", player.getPlayer().getLogName() + " has submitted their tournament deck", MessageColor.BLACK, true, null, MessageType.STATUS, SoundToPlay.PlayerSubmittedDeck); + managerFactory.chatManager().broadcast(chatId, "", player.getPlayer().getLogName() + " has submitted their tournament deck", MessageColor.BLACK, true, null, MessageType.STATUS, SoundToPlay.PlayerSubmittedDeck); } } } @@ -338,7 +336,7 @@ public class TournamentController { tournament.autoSubmit(userPlayerMap.get(userId), tournamentPlayer.generateDeck()); } else { StringBuilder sb = new StringBuilder(); - UserManager.instance.getUser(userId).ifPresent(user + managerFactory.userManager().getUser(userId).ifPresent(user -> sb.append(user.getName())); sb.append(" - no deck found for auto submit"); @@ -380,16 +378,16 @@ public class TournamentController { if (tournament.isDoneConstructing()) { info = new StringBuilder("during round ").append(tournament.getRounds().size()).toString(); // quit active matches of that tournament - TableManager.instance.userQuitTournamentSubTables(tournament.getId(), userId); + managerFactory.tableManager().userQuitTournamentSubTables(tournament.getId(), userId); status = TourneyQuitStatus.DURING_ROUND; } else if (tournamentPlayer.getState() == TournamentPlayerState.DRAFTING) { info = "during Draft phase"; if (!checkToReplaceDraftPlayerByAi(userId, tournamentPlayer)) { this.abortDraftTournament(); } else { - DraftManager.instance.getController(tableId).ifPresent(draftController -> { + managerFactory.draftManager().getController(tableId).ifPresent(draftController -> { draftController.getDraftSession(playerId).ifPresent(draftSession - -> DraftManager.instance.kill(draftSession.getDraftId(), userId)); + -> managerFactory.draftManager().kill(draftSession.getDraftId(), userId)); }); } @@ -404,7 +402,7 @@ public class TournamentController { tournamentPlayer.setQuit(info, status); tournament.quit(playerId); tournamentSession.quit(); - ChatManager.instance.broadcast(chatId, "", tournamentPlayer.getPlayer().getLogName() + " has quit the tournament", MessageColor.BLACK, true, null, MessageType.STATUS, SoundToPlay.PlayerQuitTournament); + managerFactory.chatManager().broadcast(chatId, "", tournamentPlayer.getPlayer().getLogName() + " has quit the tournament", MessageColor.BLACK, true, null, MessageType.STATUS, SoundToPlay.PlayerQuitTournament); } } @@ -417,8 +415,8 @@ public class TournamentController { } // replace player that quits with draft bot if (humans > 1) { - Optional user = UserManager.instance.getUser(userId); - TableManager.instance.getController(tableId).ifPresent(tableController -> { + Optional user = managerFactory.userManager().getUser(userId); + managerFactory.tableManager().getController(tableId).ifPresent(tableController -> { String replacePlayerName = "Draftbot"; if (user.isPresent()) { @@ -430,7 +428,7 @@ public class TournamentController { user.get().removeTable(leavingPlayer.getPlayer().getId()); user.get().removeTournament(leavingPlayer.getPlayer().getId()); } - ChatManager.instance.broadcast(chatId, "", leavingPlayer.getPlayer().getLogName() + " was replaced by draftbot", MessageColor.BLACK, true, null, MessageType.STATUS, null); + managerFactory.chatManager().broadcast(chatId, "", leavingPlayer.getPlayer().getLogName() + " was replaced by draftbot", MessageColor.BLACK, true, null, MessageType.STATUS, null); }); return true; } @@ -447,7 +445,7 @@ public class TournamentController { private void abortDraftTournament() { tournament.setAbort(true); - DraftManager.instance.getController(tableId).ifPresent(DraftController::abortDraft); + managerFactory.draftManager().getController(tableId).ifPresent(DraftController::abortDraft); } public boolean isAbort() { @@ -472,7 +470,7 @@ public class TournamentController { } public void cleanUpOnRemoveTournament() { - ChatManager.instance.destroyChatSession(chatId); + managerFactory.chatManager().destroyChatSession(chatId); } /** @@ -490,7 +488,7 @@ public class TournamentController { if (tournamentPlayer != null) { if (!tournamentPlayer.hasQuit()) { if (tournamentPlayer.getPlayer().isHuman()) { - Optional user = UserManager.instance.getUser(entry.getKey()); + Optional user = managerFactory.userManager().getUser(entry.getKey()); if (!user.isPresent()) { logger.debug("Tournament user is missing but player active -> start quit - tournamentId: " + tournament.getId() + " state: " + tableState.toString()); // active tournament player but the user is no longer online diff --git a/Mage.Server/src/main/java/mage/server/tournament/TournamentManager.java b/Mage.Server/src/main/java/mage/server/tournament/TournamentManagerImpl.java similarity index 78% rename from Mage.Server/src/main/java/mage/server/tournament/TournamentManager.java rename to Mage.Server/src/main/java/mage/server/tournament/TournamentManagerImpl.java index a499842e29e..f84ec3fcd5e 100644 --- a/Mage.Server/src/main/java/mage/server/tournament/TournamentManager.java +++ b/Mage.Server/src/main/java/mage/server/tournament/TournamentManagerImpl.java @@ -1,57 +1,71 @@ - package mage.server.tournament; +import mage.cards.decks.Deck; +import mage.game.tournament.Tournament; +import mage.server.managers.TournamentManager; +import mage.server.managers.ManagerFactory; +import mage.view.TournamentView; +import org.apache.log4j.Logger; + import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import mage.cards.decks.Deck; -import mage.game.tournament.Tournament; -import mage.view.TournamentView; -import org.apache.log4j.Logger; - /** * @author BetaSteward_at_googlemail.com */ -public enum TournamentManager { - instance; +public class TournamentManagerImpl implements TournamentManager { + + private final ManagerFactory managerFactory; private final ConcurrentMap controllers = new ConcurrentHashMap<>(); + public TournamentManagerImpl(ManagerFactory managerFactory) { + this.managerFactory = managerFactory; + } + + @Override public Optional getTournamentController(UUID tournamentId) { return Optional.ofNullable(controllers.get(tournamentId)); } + @Override public void createTournamentSession(Tournament tournament, ConcurrentHashMap userPlayerMap, UUID tableId) { - TournamentController tournamentController = new TournamentController(tournament, userPlayerMap, tableId); + TournamentController tournamentController = new TournamentController(managerFactory, tournament, userPlayerMap, tableId); controllers.put(tournament.getId(), tournamentController); } + @Override public void joinTournament(UUID tournamentId, UUID userId) { controllers.get(tournamentId).join(userId); } + @Override public void quit(UUID tournamentId, UUID userId) { TournamentController tournamentController = controllers.get(tournamentId); if (tournamentController != null) { tournamentController.quit(userId); } else { - Logger.getLogger(TournamentManager.class).error("Tournament controller missing tournamentid: " + tournamentId + " userId: " + userId); + Logger.getLogger(TournamentManagerImpl.class).error("Tournament controller missing tournamentid: " + tournamentId + " userId: " + userId); } } + @Override public void timeout(UUID tournamentId, UUID userId) { controllers.get(tournamentId).timeout(userId); } + @Override public void submitDeck(UUID tournamentId, UUID playerId, Deck deck) { controllers.get(tournamentId).submitDeck(playerId, deck); } + @Override public boolean updateDeck(UUID tournamentId, UUID playerId, Deck deck) { return controllers.get(tournamentId).updateDeck(playerId, deck); } + @Override public TournamentView getTournamentView(UUID tournamentId) { TournamentController tournamentController = controllers.get(tournamentId); if (tournamentController != null) { @@ -60,6 +74,7 @@ public enum TournamentManager { return null; } + @Override public Optional getChatId(UUID tournamentId) { if (controllers.containsKey(tournamentId)) { return Optional.of(controllers.get(tournamentId).getChatId()); @@ -67,6 +82,7 @@ public enum TournamentManager { return Optional.empty(); } + @Override public void removeTournament(UUID tournamentId) { TournamentController tournamentController = controllers.get(tournamentId); if (tournamentController != null) { diff --git a/Mage.Server/src/main/java/mage/server/tournament/TournamentSession.java b/Mage.Server/src/main/java/mage/server/tournament/TournamentSession.java index 33171659910..e04c9062e2f 100644 --- a/Mage.Server/src/main/java/mage/server/tournament/TournamentSession.java +++ b/Mage.Server/src/main/java/mage/server/tournament/TournamentSession.java @@ -1,20 +1,19 @@ - package mage.server.tournament; +import mage.cards.decks.Deck; +import mage.game.tournament.Tournament; +import mage.interfaces.callback.ClientCallback; +import mage.interfaces.callback.ClientCallbackMethod; +import mage.server.User; +import mage.server.managers.ManagerFactory; +import mage.view.TournamentView; +import org.apache.log4j.Logger; + import java.util.Optional; import java.util.UUID; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import mage.cards.decks.Deck; -import mage.game.tournament.Tournament; -import mage.interfaces.callback.ClientCallback; -import mage.interfaces.callback.ClientCallbackMethod; -import mage.server.User; -import mage.server.UserManager; -import mage.server.util.ThreadExecutor; -import mage.view.TournamentView; -import org.apache.log4j.Logger; /** * @author BetaSteward_at_googlemail.com @@ -23,6 +22,7 @@ public class TournamentSession { protected static final Logger logger = Logger.getLogger(TournamentSession.class); + private final ManagerFactory managerFactory; protected final UUID userId; protected final UUID playerId; protected final UUID tableId; @@ -30,9 +30,11 @@ public class TournamentSession { protected boolean killed = false; private ScheduledFuture futureTimeout; - protected static final ScheduledExecutorService timeoutExecutor = ThreadExecutor.instance.getTimeoutExecutor(); + protected final ScheduledExecutorService timeoutExecutor; - public TournamentSession(Tournament tournament, UUID userId, UUID tableId, UUID playerId) { + public TournamentSession(ManagerFactory managerFactory, Tournament tournament, UUID userId, UUID tableId, UUID playerId) { + this.managerFactory = managerFactory; + this.timeoutExecutor = managerFactory.threadExecutor().getTimeoutExecutor(); this.userId = userId; this.tournament = tournament; this.playerId = playerId; @@ -41,7 +43,7 @@ public class TournamentSession { public boolean init() { if (!killed) { - Optional user = UserManager.instance.getUser(userId); + Optional user = managerFactory.userManager().getUser(userId); if (user.isPresent()) { user.get().fireCallback(new ClientCallback(ClientCallbackMethod.TOURNAMENT_INIT, tournament.getId(), getTournamentView())); return true; @@ -52,7 +54,7 @@ public class TournamentSession { public void update() { if (!killed) { - UserManager.instance.getUser(userId).ifPresent(user + managerFactory.userManager().getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.TOURNAMENT_UPDATE, tournament.getId(), getTournamentView()))); } @@ -60,7 +62,7 @@ public class TournamentSession { public void gameOver(final String message) { if (!killed) { - UserManager.instance.getUser(userId).ifPresent(user + managerFactory.userManager().getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.TOURNAMENT_OVER, tournament.getId(), message))); } @@ -69,7 +71,7 @@ public class TournamentSession { public void construct(int timeout) { if (!killed) { setupTimeout(timeout); - UserManager.instance.getUser(userId).ifPresent(user -> { + managerFactory.userManager().getUser(userId).ifPresent(user -> { int remaining = (int) futureTimeout.getDelay(TimeUnit.SECONDS); user.ccConstruct(tournament.getPlayer(playerId).getDeck(), tableId, remaining); }); @@ -102,7 +104,7 @@ public class TournamentSession { futureTimeout = timeoutExecutor.schedule( () -> { try { - TournamentManager.instance.timeout(tournament.getId(), userId); + managerFactory.tournamentManager().timeout(tournament.getId(), userId); } catch (Exception e) { logger.fatal("TournamentSession error - userId " + userId + " tId " + tournament.getId(), e); } @@ -144,7 +146,7 @@ public class TournamentSession { } private void removeTournamentForUser() { - Optional user = UserManager.instance.getUser(userId); + Optional user = managerFactory.userManager().getUser(userId); if (user.isPresent()) { user.get().removeTable(playerId); user.get().removeTournament(playerId); diff --git a/Mage.Server/src/main/java/mage/server/util/ConfigFactory.java b/Mage.Server/src/main/java/mage/server/util/ConfigFactory.java new file mode 100644 index 00000000000..793419d06ef --- /dev/null +++ b/Mage.Server/src/main/java/mage/server/util/ConfigFactory.java @@ -0,0 +1,20 @@ +package mage.server.util; + +import mage.server.util.config.Config; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Unmarshaller; +import java.io.File; + +public class ConfigFactory { + + public static Config loadFromFile(final String filePath) { + try { + final JAXBContext jaxbContext = JAXBContext.newInstance("mage.server.util.config"); + final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + return (Config) unmarshaller.unmarshal(new File(filePath)); + } catch (Exception e) { + throw new ConfigurationException(e); + } + } +} diff --git a/Mage.Server/src/main/java/mage/server/util/ConfigSettings.java b/Mage.Server/src/main/java/mage/server/util/ConfigWrapper.java similarity index 82% rename from Mage.Server/src/main/java/mage/server/util/ConfigSettings.java rename to Mage.Server/src/main/java/mage/server/util/ConfigWrapper.java index ac65aae03bc..20be1a64256 100644 --- a/Mage.Server/src/main/java/mage/server/util/ConfigSettings.java +++ b/Mage.Server/src/main/java/mage/server/util/ConfigWrapper.java @@ -1,33 +1,18 @@ - package mage.server.util; -import java.io.File; -import java.util.List; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Unmarshaller; +import mage.server.managers.ConfigSettings; import mage.server.util.config.Config; import mage.server.util.config.GamePlugin; import mage.server.util.config.Plugin; -import org.apache.log4j.Logger; -/** - * @author BetaSteward_at_googlemail.com - */ -public enum ConfigSettings { - instance; - private final Logger logger = Logger.getLogger(ConfigSettings.class); +import java.util.List; - private Config config; +public class ConfigWrapper implements ConfigSettings { - ConfigSettings() { - try { - JAXBContext jaxbContext = JAXBContext.newInstance("mage.server.util.config"); - Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); - config = (Config) unmarshaller.unmarshal(new File("config/config.xml")); - } catch (JAXBException ex) { - logger.fatal("ConfigSettings error", ex); - } + private final Config config; + + public ConfigWrapper(final Config config) { + this.config = config; } public String getServerAddress() { diff --git a/Mage.Server/src/main/java/mage/server/util/ConfigurationException.java b/Mage.Server/src/main/java/mage/server/util/ConfigurationException.java new file mode 100644 index 00000000000..e1c1e5d0f06 --- /dev/null +++ b/Mage.Server/src/main/java/mage/server/util/ConfigurationException.java @@ -0,0 +1,7 @@ +package mage.server.util; + +public class ConfigurationException extends RuntimeException { + public ConfigurationException(Throwable cause) { + super(cause); + } +} diff --git a/Mage.Server/src/main/java/mage/server/util/ThreadExecutor.java b/Mage.Server/src/main/java/mage/server/util/ThreadExecutorImpl.java similarity index 70% rename from Mage.Server/src/main/java/mage/server/util/ThreadExecutor.java rename to Mage.Server/src/main/java/mage/server/util/ThreadExecutorImpl.java index a5014ecf73b..85ca2f8b1e6 100644 --- a/Mage.Server/src/main/java/mage/server/util/ThreadExecutor.java +++ b/Mage.Server/src/main/java/mage/server/util/ThreadExecutorImpl.java @@ -1,24 +1,18 @@ - package mage.server.util; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import mage.server.managers.ConfigSettings; +import mage.server.managers.ThreadExecutor; + +import java.util.concurrent.*; /** - * * @author BetaSteward_at_googlemail.com */ -public enum ThreadExecutor { -instance; - private static final ExecutorService callExecutor = Executors.newCachedThreadPool(); - private static final ExecutorService userExecutor = Executors.newCachedThreadPool(); - private static final ExecutorService gameExecutor = Executors.newFixedThreadPool(ConfigSettings.instance.getMaxGameThreads()); - private static final ScheduledExecutorService timeoutExecutor = Executors.newScheduledThreadPool(4); - private static final ScheduledExecutorService timeoutIdleExecutor = Executors.newScheduledThreadPool(4); +public class ThreadExecutorImpl implements ThreadExecutor { + private final ExecutorService callExecutor; + private final ExecutorService gameExecutor; + private final ScheduledExecutorService timeoutExecutor; + private final ScheduledExecutorService timeoutIdleExecutor; /** * noxx: what the settings below do is setting the ability to keep OS @@ -26,17 +20,20 @@ instance; * within this time period, the thread may be discarded. But anyway if new * game is created later, new OS/java thread will be created for it taking * MaxGameThreads limit into account. - * + *

* This all is done for performance reasons as creating new OS threads is * resource consuming process. */ - static { + + public ThreadExecutorImpl(ConfigSettings config) { + callExecutor = Executors.newCachedThreadPool(); + gameExecutor = Executors.newFixedThreadPool(config.getMaxGameThreads()); + timeoutExecutor = Executors.newScheduledThreadPool(4); + timeoutIdleExecutor = Executors.newScheduledThreadPool(4); + ((ThreadPoolExecutor) callExecutor).setKeepAliveTime(60, TimeUnit.SECONDS); ((ThreadPoolExecutor) callExecutor).allowCoreThreadTimeOut(true); ((ThreadPoolExecutor) callExecutor).setThreadFactory(new XMageThreadFactory("CALL")); - ((ThreadPoolExecutor) userExecutor).setKeepAliveTime(60, TimeUnit.SECONDS); - ((ThreadPoolExecutor) userExecutor).allowCoreThreadTimeOut(true); - ((ThreadPoolExecutor) userExecutor).setThreadFactory(new XMageThreadFactory("USER")); ((ThreadPoolExecutor) gameExecutor).setKeepAliveTime(60, TimeUnit.SECONDS); ((ThreadPoolExecutor) gameExecutor).allowCoreThreadTimeOut(true); ((ThreadPoolExecutor) gameExecutor).setThreadFactory(new XMageThreadFactory("GAME")); @@ -49,6 +46,7 @@ instance; } + @Override public int getActiveThreads(ExecutorService executerService) { if (executerService instanceof ThreadPoolExecutor) { return ((ThreadPoolExecutor) executerService).getActiveCount(); @@ -56,18 +54,22 @@ instance; return -1; } + @Override public ExecutorService getCallExecutor() { return callExecutor; } + @Override public ExecutorService getGameExecutor() { return gameExecutor; } + @Override public ScheduledExecutorService getTimeoutExecutor() { return timeoutExecutor; } + @Override public ScheduledExecutorService getTimeoutIdleExecutor() { return timeoutIdleExecutor; } diff --git a/Mage.Server/src/test/java/mage/server/util/ConfigFactoryTest.java b/Mage.Server/src/test/java/mage/server/util/ConfigFactoryTest.java new file mode 100644 index 00000000000..3563287fad1 --- /dev/null +++ b/Mage.Server/src/test/java/mage/server/util/ConfigFactoryTest.java @@ -0,0 +1,36 @@ +package mage.server.util; + +import mage.server.util.config.Config; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.nio.file.Paths; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +public class ConfigFactoryTest { + + @Test + @DisplayName("should unmarshal configuration from file") + void loadConfig() { + final Config config = ConfigFactory.loadFromFile("config/config.xml"); + + assertThat(config.getServer().getServerName()).isEqualTo("mage-server"); + assertThat(config.getServer().getPort()).isEqualTo(17171); + } + + @Test + @DisplayName("should fail if config is malformed") + void failOnMalformed() { + assertThatExceptionOfType(ConfigurationException.class) + .isThrownBy(() -> ConfigFactory.loadFromFile(Paths.get("src", "test", "resources", "config_error.xml").toString())); + } + + @Test + @DisplayName("should fail if file does not exist") + void failOnNotFound() { + assertThatExceptionOfType(ConfigurationException.class) + .isThrownBy(() -> ConfigFactory.loadFromFile("does not exist")); + } +} diff --git a/Mage.Server/src/test/java/mage/server/util/ConfigWrapperTest.java b/Mage.Server/src/test/java/mage/server/util/ConfigWrapperTest.java new file mode 100644 index 00000000000..c8a80c34c4c --- /dev/null +++ b/Mage.Server/src/test/java/mage/server/util/ConfigWrapperTest.java @@ -0,0 +1,294 @@ +package mage.server.util; + +import mage.server.util.config.*; +import mage.utils.FluentBuilder; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.RandomUtils; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; + +import java.math.BigInteger; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ConfigWrapperTest { + + static class ConfigBuilder extends FluentBuilder { + + public String serverAddress; + public String serverName; + public int port; + public int secondaryBindPort; + public int leasePeriod; + public int socketWriteTimeout; + public int maxPoolSize; + public int numAcceptThreads; + public int backlogSize; + public int maxGameThreads; + public int maxSecondsIdle; + public int minUsernameLength; + public int maxUsernameLength; + public String invalidUsernamePattern; + public int minPasswordLength; + public int maxPasswordLength; + public String maxAiOpponents; + public boolean saveGameActivated; + public boolean authenticationActivated; + public String googleAccount; + public String mailgunApiKey; + public String mailgunDomain; + public String mailSmtpHost; + public String mailSmtpPort; + public String mailUser; + public String mailPassword; + public String mailFromAddress; + public List playerTypes = Collections.emptyList(); + public List gameTypes = Collections.emptyList(); + public List tournamentTypes = Collections.emptyList(); + public List draftCubes = Collections.emptyList(); + public List deckTypes = Collections.emptyList(); + + private ConfigBuilder() { + super(ConfigBuilder::new); + } + + @Override + protected Config makeValue() { + final Config result = new Config(); + result.setServer(makeServer()); + result.setPlayerTypes(makePlayerTypes()); + result.setGameTypes(makeGameTypes()); + result.setTournamentTypes(makeTournamentTypes()); + result.setDraftCubes(makeDraftCubes()); + result.setDeckTypes(makeDeckTypes()); + return result; + } + + private Server makeServer() { + final Server server = new Server(); + server.setServerAddress(serverAddress); + server.setServerName(serverName); + server.setPort(BigInteger.valueOf(port)); + server.setSecondaryBindPort(bi(secondaryBindPort)); + server.setLeasePeriod(bi(leasePeriod)); + server.setSocketWriteTimeout(bi(socketWriteTimeout)); + server.setMaxPoolSize(bi(maxPoolSize)); + server.setNumAcceptThreads(bi(numAcceptThreads)); + server.setBacklogSize(bi(backlogSize)); + server.setMaxGameThreads(bi(maxGameThreads)); + server.setMaxSecondsIdle(bi(maxSecondsIdle)); + server.setMinUserNameLength(bi(minUsernameLength)); + server.setMaxUserNameLength(bi(maxUsernameLength)); + server.setInvalidUserNamePattern(invalidUsernamePattern); + server.setMinPasswordLength(bi(minPasswordLength)); + server.setMaxPasswordLength(bi(maxPasswordLength)); + server.setMaxAiOpponents(maxAiOpponents); + server.setSaveGameActivated(saveGameActivated); + server.setAuthenticationActivated(authenticationActivated); + server.setGoogleAccount(googleAccount); + server.setMailgunApiKey(mailgunApiKey); + server.setMailgunDomain(mailgunDomain); + server.setMailSmtpHost(mailSmtpHost); + server.setMailSmtpPort(mailSmtpPort); + server.setMailUser(mailUser); + server.setMailPassword(mailPassword); + server.setMailFromAddress(mailFromAddress); + return server; + } + + private PlayerTypes makePlayerTypes() { + final PlayerTypes playerTypes = new PlayerTypes(); + this.playerTypes.forEach(p -> playerTypes.getPlayerType().add(p)); + return playerTypes; + } + + private GameTypes makeGameTypes() { + final GameTypes gameTypes = new GameTypes(); + this.gameTypes.forEach(g -> gameTypes.getGameType().add(g)); + return gameTypes; + } + + private TournamentTypes makeTournamentTypes() { + final TournamentTypes tournamentTypes = new TournamentTypes(); + this.tournamentTypes.forEach(t -> tournamentTypes.getTournamentType().add(t)); + return tournamentTypes; + } + + private DraftCubes makeDraftCubes() { + final DraftCubes draftCubes = new DraftCubes(); + this.draftCubes.forEach(d -> draftCubes.getDraftCube().add(d)); + return draftCubes; + } + + private DeckTypes makeDeckTypes() { + final DeckTypes deckTypes = new DeckTypes(); + this.deckTypes.forEach(d -> deckTypes.getDeckType().add(d)); + return deckTypes; + } + + private BigInteger bi(int value) { + return BigInteger.valueOf(value); + } + } + + private ConfigBuilder baseConfigBuilder() { + return new ConfigBuilder(); + } + + private final String expectedString = RandomStringUtils.randomAlphanumeric(15); + private final int expectedPositiveInt = RandomUtils.nextInt(0, Integer.MAX_VALUE); + + + @TestFactory + @DisplayName("should return from server") + Stream assignmentFromServer() { + return Stream.of( + testString("server address", c -> c.serverAddress = expectedString, ConfigWrapper::getServerAddress), + testString("server name", c -> c.serverName = expectedString, ConfigWrapper::getServerName), + testInt("port", c -> c.port = expectedPositiveInt, ConfigWrapper::getPort), + testInt("secondary bind port", c -> c.secondaryBindPort = expectedPositiveInt, ConfigWrapper::getSecondaryBindPort), + testInt("lease period", c -> c.leasePeriod = expectedPositiveInt, ConfigWrapper::getLeasePeriod), + testInt("socket write timeout", c -> c.socketWriteTimeout = expectedPositiveInt, ConfigWrapper::getSocketWriteTimeout), + testInt("max pool size", c -> c.maxPoolSize = expectedPositiveInt, ConfigWrapper::getMaxPoolSize), + testInt("number of accept threads", c -> c.numAcceptThreads = expectedPositiveInt, ConfigWrapper::getNumAcceptThreads), + testInt("backlog size", c -> c.backlogSize = expectedPositiveInt, ConfigWrapper::getBacklogSize), + testInt("max game threads", c -> c.maxGameThreads = expectedPositiveInt, ConfigWrapper::getMaxGameThreads), + testInt("max seconds idle", c -> c.maxSecondsIdle = expectedPositiveInt, ConfigWrapper::getMaxSecondsIdle), + testInt("min username length", c -> c.minUsernameLength = expectedPositiveInt, ConfigWrapper::getMinUserNameLength), + testInt("max username length", c -> c.maxUsernameLength = expectedPositiveInt, ConfigWrapper::getMaxUserNameLength), + testString("invalid username pattern", c -> c.invalidUsernamePattern = expectedString, ConfigWrapper::getInvalidUserNamePattern), + testInt("min password length", c -> c.minPasswordLength = expectedPositiveInt, ConfigWrapper::getMinPasswordLength), + testInt("max password length", c -> c.maxPasswordLength = expectedPositiveInt, ConfigWrapper::getMaxPasswordLength), + testString("max AI opponents", c -> c.maxAiOpponents = expectedString, ConfigWrapper::getMaxAiOpponents), + testTrue("save game activated", c -> c.saveGameActivated = true, ConfigWrapper::isSaveGameActivated), + testTrue("authentication activated", c -> c.authenticationActivated = true, ConfigWrapper::isAuthenticationActivated), + testString("google account", c -> c.googleAccount = expectedString, ConfigWrapper::getGoogleAccount), + testString("mailgun api key", c -> c.mailgunApiKey = expectedString, ConfigWrapper::getMailgunApiKey), + testString("mailgun domain", c -> c.mailgunDomain = expectedString, ConfigWrapper::getMailgunDomain), + testString("mail smtp host", c -> c.mailSmtpHost = expectedString, ConfigWrapper::getMailSmtpHost), + testString("mail smtp port", c -> c.mailSmtpPort = expectedString, ConfigWrapper::getMailSmtpPort), + testString("mail from address", c -> c.mailFromAddress = expectedString, ConfigWrapper::getMailFromAddress), + testString("mail user", c -> c.mailUser = expectedString, ConfigWrapper::getMailUser), + testString("mail password", c -> c.mailPassword = expectedString, ConfigWrapper::getMailPassword) + ); + } + + private DynamicTest testString(String description, Consumer builderSetter, Function valueExtractor) { + return testTemplate(description, builderSetter, valueExtractor, expectedString); + } + + private DynamicTest testTemplate(String description, Consumer builderSetter, Function valueExtractor, Object expectedValue) { + return DynamicTest.dynamicTest(description, () -> assertThat(valueExtractor.apply(makeTestee(baseConfigBuilder().with(builderSetter)))).isEqualTo(expectedValue)); + } + + private ConfigWrapper makeTestee(ConfigBuilder builder) { + return new ConfigWrapper(builder.build()); + } + + private DynamicTest testInt(String description, Consumer builderSetter, Function valueExtractor) { + return testTemplate(description, builderSetter, valueExtractor, expectedPositiveInt); + } + + private DynamicTest testTrue(String description, Consumer builderSetter, Function valueExtractor) { + return testTemplate(description, builderSetter, valueExtractor, true); + } + + + private final Comparator pluginComparator = (p1, p2) -> { + if (Objects.equals(p1.getName(), p2.getName()) && + Objects.equals(p1.getJar(), p2.getJar()) && + Objects.equals(p1.getClassName(), p2.getClassName())) { + return 0; + } else { + return -1; + } + }; + + private final Comparator gamePluginComparator = (p1, p2) -> { + if (Objects.equals(p1.getName(), p2.getName()) && + Objects.equals(p1.getJar(), p2.getJar()) && + Objects.equals(p1.getClassName(), p2.getClassName()) && + Objects.equals(p1.getTypeName(), p2.getTypeName())) { + return 0; + } else { + return -1; + } + }; + + private final List randomPlugins = IntStream.range(0, RandomUtils.nextInt(1, 10)) + .mapToObj(i -> makePlugin( + RandomStringUtils.randomAlphanumeric(15), + RandomStringUtils.randomAlphanumeric(16), + RandomStringUtils.randomAlphanumeric(17)) + ).collect(Collectors.toList()); + private final List randomGamePlugins = IntStream.range(0, RandomUtils.nextInt(1, 10)) + .mapToObj(i -> makeGamePlugin( + RandomStringUtils.randomAlphanumeric(15), + RandomStringUtils.randomAlphanumeric(16), + RandomStringUtils.randomAlphanumeric(17), + RandomStringUtils.randomAlphanumeric(18)) + ).collect(Collectors.toList()); + + private Plugin makePlugin(String name, String jar, String className) { + final Plugin plugin = new Plugin(); + plugin.setName(name); + plugin.setJar(jar); + plugin.setClassName(className); + return plugin; + } + + private GamePlugin makeGamePlugin(String name, String jar, String className, String typeName) { + final GamePlugin plugin = new GamePlugin(); + plugin.setName(name); + plugin.setJar(jar); + plugin.setClassName(className); + plugin.setTypeName(typeName); + return plugin; + } + + @TestFactory + @DisplayName("should extract") + Stream pluginsExtraction() { + return Stream.of( + pluginTest("playerTypes from playerTypes", c -> c.playerTypes = randomPlugins, ConfigWrapper::getPlayerTypes), + gamePluginTest("gameTypes from gameTypes", c -> c.gameTypes = randomGamePlugins, ConfigWrapper::getGameTypes), + gamePluginTest("tournamentTypes from tournamentTypes", c -> c.tournamentTypes = randomGamePlugins, ConfigWrapper::getTournamentTypes), + pluginTest("draftCubes from draftCubes", c -> c.draftCubes = randomPlugins, ConfigWrapper::getDraftCubes), + pluginTest("deckTypes from deckTypes", c -> c.deckTypes = randomPlugins, ConfigWrapper::getDeckTypes) + ); + } + + private DynamicTest pluginTest(String description, + Consumer builderSetter, + Function> listExtractor) { + return testTemplateForLists(description, builderSetter, listExtractor, randomPlugins, pluginComparator); + } + + private DynamicTest gamePluginTest(String description, + Consumer builderSetter, + Function> listExtractor) { + return testTemplateForLists(description, builderSetter, listExtractor, randomGamePlugins, gamePluginComparator); + } + + private DynamicTest testTemplateForLists(String description, + Consumer builderSetter, + Function> listExtractor, + List expectedValue, + Comparator comparator) { + return DynamicTest.dynamicTest(description, () -> + assertThat(listExtractor.apply(makeTestee(baseConfigBuilder().with(builderSetter)))) + .usingElementComparator(comparator) + .containsExactlyElementsOf(expectedValue) + ); + } +} diff --git a/Mage.Server/src/test/resources/config_error.xml b/Mage.Server/src/test/resources/config_error.xml new file mode 100644 index 00000000000..51a73dee44a --- /dev/null +++ b/Mage.Server/src/test/resources/config_error.xml @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mage.Sets/src/mage/cards/a/AgonyWarp.java b/Mage.Sets/src/mage/cards/a/AgonyWarp.java index 2624507a6f8..674bf50f5fb 100644 --- a/Mage.Sets/src/mage/cards/a/AgonyWarp.java +++ b/Mage.Sets/src/mage/cards/a/AgonyWarp.java @@ -32,7 +32,7 @@ public final class AgonyWarp extends CardImpl { // Target creature gets -0/-3 until end of turn. Effect effect2 = new BoostTargetEffect(-0,-3, Duration.EndOfTurn); - effect2.setText("

Target creature gets -0/-3 until end of turn"); + effect2.setText("

Target creature gets -0/-3 until end of turn"); effect2.setTargetPointer(SecondTargetPointer.getInstance()); this.getSpellAbility().addEffect(effect2); target = new TargetCreaturePermanent(new FilterCreaturePermanent("second creature (can be the same as the first)")); diff --git a/Mage.Sets/src/mage/cards/a/ArchelosLagoonMystic.java b/Mage.Sets/src/mage/cards/a/ArchelosLagoonMystic.java index baed9ca6fca..ab2bee4eeee 100644 --- a/Mage.Sets/src/mage/cards/a/ArchelosLagoonMystic.java +++ b/Mage.Sets/src/mage/cards/a/ArchelosLagoonMystic.java @@ -78,7 +78,18 @@ class ArchelosLagoonMysticEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - return !source.getSourceId().equals(((EntersTheBattlefieldEvent) event).getTarget().getId()); + Permanent sourceObject = game.getPermanent(source.getSourceId()); + if (sourceObject == null) { + return false; + } + + Permanent targetObject = ((EntersTheBattlefieldEvent) event).getTarget(); + if (targetObject == null) { + return false; + } + + return !sourceObject.getId().equals(targetObject.getId()) + && sourceObject.isTapped() == this.tapped; } @Override diff --git a/Mage.Sets/src/mage/cards/a/ArcticFoxes.java b/Mage.Sets/src/mage/cards/a/ArcticFoxes.java index b658e56e0e6..d6b86a7d425 100644 --- a/Mage.Sets/src/mage/cards/a/ArcticFoxes.java +++ b/Mage.Sets/src/mage/cards/a/ArcticFoxes.java @@ -68,6 +68,6 @@ enum ArcticFoxesCondition implements Condition { if (defenderId == null) { return false; } - return game.getBattlefield().contains(filter, defenderId, 1, game); + return game.getBattlefield().contains(filter, source.getSourceId(), defenderId, game, 1); } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/a/AzoriusPloy.java b/Mage.Sets/src/mage/cards/a/AzoriusPloy.java index 0a2586584a1..9b066888ae4 100644 --- a/Mage.Sets/src/mage/cards/a/AzoriusPloy.java +++ b/Mage.Sets/src/mage/cards/a/AzoriusPloy.java @@ -33,7 +33,7 @@ public final class AzoriusPloy extends CardImpl { // Prevent all combat damage that would be dealt to target creature this turn. Effect effect2 = new PreventDamageToTargetEffect(Duration.EndOfTurn, true); - effect2.setText("

Prevent all combat damage that would be dealt to target creature this turn."); + effect2.setText("

Prevent all combat damage that would be dealt to target creature this turn."); effect2.setTargetPointer(SecondTargetPointer.getInstance()); this.getSpellAbility().addEffect(effect2); target = new TargetCreaturePermanent(new FilterCreaturePermanent("second creature (can be the same as the first)")); diff --git a/Mage.Sets/src/mage/cards/b/Backlash.java b/Mage.Sets/src/mage/cards/b/Backlash.java index ddb75a6ee5a..0693f19c382 100644 --- a/Mage.Sets/src/mage/cards/b/Backlash.java +++ b/Mage.Sets/src/mage/cards/b/Backlash.java @@ -63,16 +63,15 @@ class BacklashEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - boolean applied = false; Permanent targetCreature = game.getPermanent(targetPointer.getFirst(game, source)); if (targetCreature != null) { - applied = targetCreature.tap(source, game); + targetCreature.tap(source, game); Player controller = game.getPlayer(targetCreature.getControllerId()); if (controller != null) { - controller.damage(targetCreature.getPower().getValue(), source.getSourceId(), source, game); - applied = true; + controller.damage(targetCreature.getPower().getValue(), targetCreature.getId(), source, game); + return true; } } - return applied; + return false; } } diff --git a/Mage.Sets/src/mage/cards/b/BlessingsOfNature.java b/Mage.Sets/src/mage/cards/b/BlessingsOfNature.java index 43232753809..191c602983f 100644 --- a/Mage.Sets/src/mage/cards/b/BlessingsOfNature.java +++ b/Mage.Sets/src/mage/cards/b/BlessingsOfNature.java @@ -1,7 +1,5 @@ - package mage.cards.b; -import java.util.UUID; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.counter.DistributeCountersEffect; import mage.abilities.keyword.MiracleAbility; @@ -11,15 +9,15 @@ import mage.constants.CardType; import mage.counters.CounterType; import mage.target.common.TargetCreaturePermanentAmount; +import java.util.UUID; + /** - * * @author North */ public final class BlessingsOfNature extends CardImpl { public BlessingsOfNature(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{4}{G}"); - + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{G}"); // Distribute four +1/+1 counters among any number of target creatures. this.getSpellAbility().addEffect(new DistributeCountersEffect(CounterType.P1P1, 4, false, "any number of target creatures")); diff --git a/Mage.Sets/src/mage/cards/c/CityInABottle.java b/Mage.Sets/src/mage/cards/c/CityInABottle.java index 8a570d692a1..0d36c7ffbc5 100644 --- a/Mage.Sets/src/mage/cards/c/CityInABottle.java +++ b/Mage.Sets/src/mage/cards/c/CityInABottle.java @@ -1,8 +1,5 @@ package mage.cards.c; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.StateTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; @@ -11,7 +8,6 @@ import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import static mage.cards.c.CityInABottle.getArabianNightsNamePredicates; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; @@ -23,11 +19,15 @@ import mage.filter.predicate.mageobject.NamePredicate; import mage.filter.predicate.permanent.TokenPredicate; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static mage.cards.c.CityInABottle.getArabianNightsNamePredicates; + /** - * * @author emerald000 */ public final class CityInABottle extends CardImpl { @@ -158,7 +158,7 @@ class CityInABottleStateTriggeredAbility extends StateTriggeredAbility { @Override public boolean checkTrigger(GameEvent event, Game game) { - return game.getBattlefield().contains(filter, this.getControllerId(), game, 1); + return game.getBattlefield().contains(filter, this.getSourceId(), this.getControllerId(), game, 1); } @Override diff --git a/Mage.Sets/src/mage/cards/c/ConcertedEffort.java b/Mage.Sets/src/mage/cards/c/ConcertedEffort.java index 3d17beca5d2..cd7ddf4724a 100644 --- a/Mage.Sets/src/mage/cards/c/ConcertedEffort.java +++ b/Mage.Sets/src/mage/cards/c/ConcertedEffort.java @@ -1,7 +1,5 @@ - package mage.cards.c; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.OneShotEffect; @@ -18,14 +16,15 @@ import mage.filter.predicate.mageobject.AbilityPredicate; import mage.game.Game; import mage.game.permanent.Permanent; +import java.util.UUID; + /** - * * @author emerald000 */ public final class ConcertedEffort extends CardImpl { public ConcertedEffort(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{W}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}{W}"); // At the beginning of each upkeep, creatures you control gain flying until end of turn if a creature you control has flying. The same is true for fear, first strike, double strike, landwalk, protection, trample, and vigilance. this.addAbility(new BeginningOfUpkeepTriggeredAbility(new ConcertedEffortEffect(), TargetController.ANY, false)); @@ -81,22 +80,22 @@ class ConcertedEffortEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { // Flying - if (game.getBattlefield().contains(filterFlying, source.getControllerId(), 1, game)) { + if (game.getBattlefield().contains(filterFlying, source, game, 1)) { game.addEffect(new GainAbilityControlledEffect(FlyingAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Fear - if (game.getBattlefield().contains(filterFear, source.getControllerId(), 1, game)) { + if (game.getBattlefield().contains(filterFear, source, game, 1)) { game.addEffect(new GainAbilityControlledEffect(FearAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // First strike - if (game.getBattlefield().contains(filterFirstStrike, source.getControllerId(), 1, game)) { + if (game.getBattlefield().contains(filterFirstStrike, source, game, 1)) { game.addEffect(new GainAbilityControlledEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Double strike - if (game.getBattlefield().contains(filterDoubleStrike, source.getControllerId(), 1, game)) { + if (game.getBattlefield().contains(filterDoubleStrike, source, game, 1)) { game.addEffect(new GainAbilityControlledEffect(DoubleStrikeAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } @@ -119,12 +118,12 @@ class ConcertedEffortEffect extends OneShotEffect { } // Trample - if (game.getBattlefield().contains(filterTrample, source.getControllerId(), 1, game)) { + if (game.getBattlefield().contains(filterTrample, source, game, 1)) { game.addEffect(new GainAbilityControlledEffect(TrampleAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Vigilance - if (game.getBattlefield().contains(filterVigilance, source.getControllerId(), 1, game)) { + if (game.getBattlefield().contains(filterVigilance, source, game, 1)) { game.addEffect(new GainAbilityControlledEffect(VigilanceAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } return true; diff --git a/Mage.Sets/src/mage/cards/c/CourtOfAmbition.java b/Mage.Sets/src/mage/cards/c/CourtOfAmbition.java index 9d6d4cbcfd3..453c0f88b8f 100644 --- a/Mage.Sets/src/mage/cards/c/CourtOfAmbition.java +++ b/Mage.Sets/src/mage/cards/c/CourtOfAmbition.java @@ -70,18 +70,19 @@ class CourtOfAmbitionEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { int discardCount = source.isControlledBy(game.getMonarchId()) ? 2 : 1; String message = "Discard " + CardUtil.numberToText(discardCount, "a") - + "card" + (discardCount > 1 ? 's' : "") + "? If not you lose " + (discardCount * 3) + " life"; + + " card" + (discardCount > 1 ? 's' : "") + "? If not you lose " + (discardCount * 3) + " life"; Map discardMap = new HashMap<>(); for (UUID playerId : game.getOpponents(source.getControllerId())) { Player player = game.getPlayer(playerId); if (player == null) { continue; } - if (player.getHand().size() < discardCount || !player.chooseUse(outcome, message, source, game)) { + if (player.getHand().size() < discardCount || !player.chooseUse(Outcome.LoseLife, message, source, game)) { player.loseLife(discardCount * 3, game, source, false); + continue; } TargetDiscard target = new TargetDiscard(discardCount, StaticFilters.FILTER_CARD, playerId); - player.choose(outcome, target, source.getSourceId(), game); + player.choose(Outcome.Discard, target, source.getSourceId(), game); discardMap.put(playerId, new CardsImpl(target.getTargets())); } for (Map.Entry entry : discardMap.entrySet()) { diff --git a/Mage.Sets/src/mage/cards/c/CullingScales.java b/Mage.Sets/src/mage/cards/c/CullingScales.java index 5c8a890a792..4917bc673b4 100644 --- a/Mage.Sets/src/mage/cards/c/CullingScales.java +++ b/Mage.Sets/src/mage/cards/c/CullingScales.java @@ -1,7 +1,5 @@ - package mage.cards.c; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.common.DestroyTargetEffect; @@ -12,27 +10,30 @@ import mage.constants.ComparisonType; import mage.constants.TargetController; import mage.filter.FilterPermanent; import mage.filter.common.FilterNonlandPermanent; -import mage.filter.predicate.Predicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.mageobject.ConvertedManaCostPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; +import java.util.UUID; + /** - * * @author cg5 */ public final class CullingScales extends CardImpl { private static final FilterPermanent filterNonlandPermanentWithLowestCmc = new FilterNonlandPermanent( - "nonland permanent with the lowest converted mana cost (If two or more permanents are tied for lowest cost, target any one of them.)" + "nonland permanent with the lowest converted mana cost (If two or more permanents are tied for lowest cost, target any one of them.)" ); + static { filterNonlandPermanentWithLowestCmc.add(new HasLowestCMCAmongstNonlandPermanentsPredicate()); } - + public CullingScales(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{3}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); // At the beginning of your upkeep, destroy target nonland permanent with the lowest converted mana cost. Ability ability = new BeginningOfUpkeepTriggeredAbility(new DestroyTargetEffect(), TargetController.YOU, false); @@ -48,16 +49,16 @@ public final class CullingScales extends CardImpl { public CullingScales copy() { return new CullingScales(this); } - + } -class HasLowestCMCAmongstNonlandPermanentsPredicate implements Predicate { - +class HasLowestCMCAmongstNonlandPermanentsPredicate implements ObjectSourcePlayerPredicate> { + @Override - public boolean apply(Permanent input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { FilterPermanent filter = new FilterNonlandPermanent(); - filter.add(new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, input.getConvertedManaCost())); - return !game.getBattlefield().contains(filter, 1, game); + filter.add(new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, input.getObject().getConvertedManaCost())); + return !game.getBattlefield().contains(filter, input.getSourceId(), input.getPlayerId(), game, 1); } - + } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/d/DoomForetold.java b/Mage.Sets/src/mage/cards/d/DoomForetold.java index 294e46acda8..090c663cc4a 100644 --- a/Mage.Sets/src/mage/cards/d/DoomForetold.java +++ b/Mage.Sets/src/mage/cards/d/DoomForetold.java @@ -84,7 +84,7 @@ class DoomForetoldEffect extends OneShotEffect { } FilterPermanent filter2 = filter.copy(); filter2.add(new ControllerIdPredicate(player.getId())); - if (game.getBattlefield().contains(filter2, 1, game)) { + if (game.getBattlefield().contains(filter2, source, game, 1)) { TargetPermanent target = new TargetPermanent(filter2); target.setNotTarget(true); if (player.choose(Outcome.Sacrifice, target, source.getSourceId(), game)) { diff --git a/Mage.Sets/src/mage/cards/h/HedronAlignment.java b/Mage.Sets/src/mage/cards/h/HedronAlignment.java index 2468d05c77c..c825a614074 100644 --- a/Mage.Sets/src/mage/cards/h/HedronAlignment.java +++ b/Mage.Sets/src/mage/cards/h/HedronAlignment.java @@ -1,7 +1,5 @@ - package mage.cards.h; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; @@ -24,14 +22,15 @@ import mage.filter.predicate.mageobject.NamePredicate; import mage.game.Game; import mage.players.Player; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class HedronAlignment extends CardImpl { public HedronAlignment(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}"); // Hexproof this.addAbility(HexproofAbility.getInstance()); @@ -85,7 +84,7 @@ class HedronAlignmentEffect extends OneShotEffect { Cards cardsToReveal = new CardsImpl(); controller.revealCards(sourceObject.getIdName(), cardsToReveal, game); // Check battlefield - if (!game.getBattlefield().contains(filterPermanent, source.getControllerId(), game, 1)) { + if (!game.getBattlefield().contains(filterPermanent, source, game, 1)) { return true; } if (controller.getHand().getCards(filterCard, source.getSourceId(), controller.getId(), game).isEmpty()) { diff --git a/Mage.Sets/src/mage/cards/h/HeraldOfLeshrac.java b/Mage.Sets/src/mage/cards/h/HeraldOfLeshrac.java index 9545ca7f1e8..74259177151 100644 --- a/Mage.Sets/src/mage/cards/h/HeraldOfLeshrac.java +++ b/Mage.Sets/src/mage/cards/h/HeraldOfLeshrac.java @@ -1,7 +1,5 @@ - package mage.cards.h; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.LeavesBattlefieldTriggeredAbility; @@ -17,12 +15,7 @@ import mage.abilities.keyword.CumulativeUpkeepAbility; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.TargetController; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledLandPermanent; import mage.filter.common.FilterLandPermanent; @@ -34,13 +27,15 @@ import mage.target.Target; import mage.target.TargetPermanent; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** - * * @author emerald000 */ public final class HeraldOfLeshrac extends CardImpl { private static final FilterPermanent filter = new FilterControlledLandPermanent("land you control but don't own"); + static { filter.add(TargetController.NOT_YOU.getOwnerPredicate()); } @@ -78,6 +73,7 @@ public final class HeraldOfLeshrac extends CardImpl { class HeraldOfLeshracCumulativeCost extends CostImpl { private static final FilterPermanent filter = new FilterLandPermanent("land you don't control"); + static { filter.add(TargetController.NOT_YOU.getControllerPredicate()); } @@ -105,7 +101,7 @@ class HeraldOfLeshracCumulativeCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return game.getBattlefield().contains(filter, controllerId, game, 1); + return game.getBattlefield().contains(filter, source.getSourceId(), controllerId, game, 1); } @Override diff --git a/Mage.Sets/src/mage/cards/i/Inviolability.java b/Mage.Sets/src/mage/cards/i/Inviolability.java index 0925fdc6505..149c0d6e9fd 100644 --- a/Mage.Sets/src/mage/cards/i/Inviolability.java +++ b/Mage.Sets/src/mage/cards/i/Inviolability.java @@ -1,24 +1,18 @@ - package mage.cards.i; -import java.util.UUID; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.PreventDamageToAttachedEffect; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AttachmentType; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.constants.Zone; +import mage.constants.*; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author L_J */ public final class Inviolability extends CardImpl { @@ -34,7 +28,9 @@ public final class Inviolability extends CardImpl { this.addAbility(new EnchantAbility(auraTarget.getTargetName())); // Prevent all damage that would be dealt to enchanted creature. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PreventDamageToAttachedEffect(Duration.WhileOnBattlefield, AttachmentType.EQUIPMENT, false))); + this.addAbility(new SimpleStaticAbility(new PreventDamageToAttachedEffect( + Duration.WhileOnBattlefield, AttachmentType.AURA, false + ))); } public Inviolability(final Inviolability card) { diff --git a/Mage.Sets/src/mage/cards/m/MajesticMyriarch.java b/Mage.Sets/src/mage/cards/m/MajesticMyriarch.java index 847eb1ed20b..c0475fb2342 100644 --- a/Mage.Sets/src/mage/cards/m/MajesticMyriarch.java +++ b/Mage.Sets/src/mage/cards/m/MajesticMyriarch.java @@ -103,62 +103,62 @@ class MajesticMyriarchEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { // Flying - if (game.getBattlefield().contains(filterFlying, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterFlying, source, game, 1)) { game.addEffect(new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.EndOfTurn), source); } // First strike - if (game.getBattlefield().contains(filterFirstStrike, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterFirstStrike, source, game, 1)) { game.addEffect(new GainAbilitySourceEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn), source); } // Double strike - if (game.getBattlefield().contains(filterDoubleStrike, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterDoubleStrike, source, game, 1)) { game.addEffect(new GainAbilitySourceEffect(DoubleStrikeAbility.getInstance(), Duration.EndOfTurn), source); } // Deathtouch - if (game.getBattlefield().contains(filterDeathtouch, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterDeathtouch, source, game, 1)) { game.addEffect(new GainAbilitySourceEffect(DeathtouchAbility.getInstance(), Duration.EndOfTurn), source); } // Haste - if (game.getBattlefield().contains(filterHaste, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterHaste, source, game, 1)) { game.addEffect(new GainAbilitySourceEffect(HasteAbility.getInstance(), Duration.EndOfTurn), source); } // Hexproof - if (game.getBattlefield().contains(filterHexproof, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterHexproof, source, game, 1)) { game.addEffect(new GainAbilitySourceEffect(HexproofAbility.getInstance(), Duration.EndOfTurn), source); } // Indestructible - if (game.getBattlefield().contains(filterIndestructible, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterIndestructible, source, game, 1)) { game.addEffect(new GainAbilitySourceEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn), source); } // Lifelink - if (game.getBattlefield().contains(filterLifelink, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterLifelink, source, game, 1)) { game.addEffect(new GainAbilitySourceEffect(LifelinkAbility.getInstance(), Duration.EndOfTurn), source); } // Menace - if (game.getBattlefield().contains(filterMenace, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterMenace, source, game, 1)) { game.addEffect(new GainAbilitySourceEffect(new MenaceAbility(), Duration.EndOfTurn), source); } // Reach - if (game.getBattlefield().contains(filterReach, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterReach, source, game, 1)) { game.addEffect(new GainAbilitySourceEffect(ReachAbility.getInstance(), Duration.EndOfTurn), source); } // Trample - if (game.getBattlefield().contains(filterTrample, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterTrample, source, game, 1)) { game.addEffect(new GainAbilitySourceEffect(TrampleAbility.getInstance(), Duration.EndOfTurn), source); } // Vigilance - if (game.getBattlefield().contains(filterVigilance, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterVigilance, source, game, 1)) { game.addEffect(new GainAbilitySourceEffect(VigilanceAbility.getInstance(), Duration.EndOfTurn), source); } return true; diff --git a/Mage.Sets/src/mage/cards/m/MartialGlory.java b/Mage.Sets/src/mage/cards/m/MartialGlory.java index 99862d219d0..adf5ac0bba8 100644 --- a/Mage.Sets/src/mage/cards/m/MartialGlory.java +++ b/Mage.Sets/src/mage/cards/m/MartialGlory.java @@ -32,7 +32,7 @@ public final class MartialGlory extends CardImpl { // Target creature gets +0/+3 until end of turn. Effect effect2 = new BoostTargetEffect(0,3, Duration.EndOfTurn); - effect2.setText("

Target creature gets +0/+3 until end of turn"); + effect2.setText("

Target creature gets +0/+3 until end of turn"); effect2.setTargetPointer(SecondTargetPointer.getInstance()); target = new TargetCreaturePermanent(new FilterCreaturePermanent("second creature (can be the same as the first)")); this.getSpellAbility().addEffect(effect2); diff --git a/Mage.Sets/src/mage/cards/o/OdricLunarchMarshal.java b/Mage.Sets/src/mage/cards/o/OdricLunarchMarshal.java index bd0a2c6af49..34781787fed 100644 --- a/Mage.Sets/src/mage/cards/o/OdricLunarchMarshal.java +++ b/Mage.Sets/src/mage/cards/o/OdricLunarchMarshal.java @@ -1,4 +1,3 @@ - package mage.cards.o; import mage.MageInt; @@ -94,67 +93,67 @@ class OdricLunarchMarshalEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { // First strike - if (game.getBattlefield().contains(filterFirstStrike, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterFirstStrike, source, game, 1)) { game.addEffect(new GainAbilityControlledEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Flying - if (game.getBattlefield().contains(filterFlying, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterFlying, source, game, 1)) { game.addEffect(new GainAbilityControlledEffect(FlyingAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Deathtouch - if (game.getBattlefield().contains(filterDeathtouch, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterDeathtouch, source, game, 1)) { game.addEffect(new GainAbilityControlledEffect(DeathtouchAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Double strike - if (game.getBattlefield().contains(filterDoubleStrike, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterDoubleStrike, source, game, 1)) { game.addEffect(new GainAbilityControlledEffect(DoubleStrikeAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Haste - if (game.getBattlefield().contains(filterHaste, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterHaste, source, game, 1)) { game.addEffect(new GainAbilityControlledEffect(HasteAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Hexproof - if (game.getBattlefield().contains(filterHexproof, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterHexproof, source, game, 1)) { game.addEffect(new GainAbilityControlledEffect(HexproofAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Indestructible - if (game.getBattlefield().contains(filterIndestructible, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterIndestructible, source, game, 1)) { game.addEffect(new GainAbilityControlledEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Lifelink - if (game.getBattlefield().contains(filterLifelink, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterLifelink, source, game, 1)) { game.addEffect(new GainAbilityControlledEffect(LifelinkAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Menace - if (game.getBattlefield().contains(filterMenace, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterMenace, source, game, 1)) { game.addEffect(new GainAbilityControlledEffect(new MenaceAbility(), Duration.EndOfTurn, filterCreatures), source); } // Reach - if (game.getBattlefield().contains(filterReach, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterReach, source, game, 1)) { game.addEffect(new GainAbilityControlledEffect(ReachAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Skulk - if (game.getBattlefield().contains(filterSkulk, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterSkulk, source, game, 1)) { game.addEffect(new GainAbilityControlledEffect(new SkulkAbility(), Duration.EndOfTurn, filterCreatures), source); } // Trample - if (game.getBattlefield().contains(filterTrample, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterTrample, source, game, 1)) { game.addEffect(new GainAbilityControlledEffect(TrampleAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Vigilance - if (game.getBattlefield().contains(filterVigilance, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filterVigilance, source, game, 1)) { game.addEffect(new GainAbilityControlledEffect(VigilanceAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } return true; diff --git a/Mage.Sets/src/mage/cards/r/Realmwalker.java b/Mage.Sets/src/mage/cards/r/Realmwalker.java new file mode 100644 index 00000000000..5264b65e281 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/Realmwalker.java @@ -0,0 +1,59 @@ +package mage.cards.r; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.ChooseCreatureTypeEffect; +import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; +import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.keyword.ChangelingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.mageobject.ChosenSubtypePredicate; + +/** + * + * @author weirddan455 + */ +public final class Realmwalker extends CardImpl { + + private static final FilterCreatureCard filter = new FilterCreatureCard("cast creature spells of the chosen type"); + static { + filter.add(ChosenSubtypePredicate.instance); + } + + public Realmwalker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.SHAPESHIFTER); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Changeling + this.setIsAllCreatureTypes(true); + this.addAbility(ChangelingAbility.getInstance()); + + // As Realmwalker enters the battlefield, choose a creature type. + this.addAbility(new AsEntersBattlefieldAbility(new ChooseCreatureTypeEffect(Outcome.Benefit))); + + // You may look at the top card of your library any time. + this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); + + // You may cast creature spells of the chosen type from the top of your library. + this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter))); + } + + private Realmwalker(final Realmwalker card) { + super(card); + } + + @Override + public Realmwalker copy() { + return new Realmwalker(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RemorselessPunishment.java b/Mage.Sets/src/mage/cards/r/RemorselessPunishment.java index 886c6121077..e537dac6806 100644 --- a/Mage.Sets/src/mage/cards/r/RemorselessPunishment.java +++ b/Mage.Sets/src/mage/cards/r/RemorselessPunishment.java @@ -1,7 +1,5 @@ - package mage.cards.r; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; @@ -16,14 +14,15 @@ import mage.players.Player; import mage.target.TargetPermanent; import mage.target.common.TargetOpponent; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class RemorselessPunishment extends CardImpl { public RemorselessPunishment(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{3}{B}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{B}{B}"); // Target opponent loses 5 life unless that player discards two cards or sacrifices a creature or planeswalker. Repeat this process once. getSpellAbility().addEffect(new RemorselessPunishmentEffect()); @@ -80,7 +79,7 @@ class RemorselessPunishmentEffect extends OneShotEffect { return; } } - if (game.getBattlefield().contains(filter, opponent.getId(), 1, game)) { + if (game.getBattlefield().containsControlled(filter, source.getSourceId(), opponent.getId(), game, 1)) { if (opponent.chooseUse(outcome, "Choose your " + iteration + " punishment.", null, "Sacrifice a creature or planeswalker", "Lose 5 life", source, game)) { TargetPermanent target = new TargetPermanent(1, 1, filter, true); if (target.choose(Outcome.Sacrifice, opponent.getId(), source.getId(), game)) { diff --git a/Mage.Sets/src/mage/cards/s/ShelteringAncient.java b/Mage.Sets/src/mage/cards/s/ShelteringAncient.java index 272bbc5e05e..531688db7b6 100644 --- a/Mage.Sets/src/mage/cards/s/ShelteringAncient.java +++ b/Mage.Sets/src/mage/cards/s/ShelteringAncient.java @@ -1,7 +1,5 @@ - package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.costs.Cost; @@ -11,8 +9,8 @@ import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; +import mage.constants.SubType; import mage.constants.TargetController; import mage.counters.CounterType; import mage.filter.common.FilterCreaturePermanent; @@ -22,8 +20,9 @@ import mage.players.Player; import mage.target.Target; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author emerald000 */ public final class ShelteringAncient extends CardImpl { @@ -82,7 +81,7 @@ class ShelteringAncientCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return game.getBattlefield().contains(filter, source.getSourceId(), game, 1); + return game.getBattlefield().contains(filter, source, game, 1); } @Override diff --git a/Mage.Sets/src/mage/cards/s/SurtlandElementalist.java b/Mage.Sets/src/mage/cards/s/SurtlandElementalist.java new file mode 100644 index 00000000000..5d8d9d2b309 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SurtlandElementalist.java @@ -0,0 +1,98 @@ +package mage.cards.s; + +import java.util.UUID; + +import mage.ApprovingObject; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.costs.OrCost; +import mage.abilities.costs.common.RevealTargetFromHandCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInHand; + +/** + * + * @author weirddan455 + */ +public final class SurtlandElementalist extends CardImpl { + + private static final FilterCard filter = new FilterCard("a Giant card from your hand"); + static { + filter.add(SubType.GIANT.getPredicate()); + } + + public SurtlandElementalist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{U}{U}"); + + this.subtype.add(SubType.GIANT); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(8); + this.toughness = new MageInt(8); + + // As an additional cost to cast this spell, reveal a Giant card from your hand or pay {2}. + this.getSpellAbility().addCost(new OrCost( + new RevealTargetFromHandCost(new TargetCardInHand(filter)), + new GenericManaCost(2), + "reveal a Giant card from your hand or pay {2}")); + + // Whenever Surtland Elementalist attacks, you may cast an instant or sorcery spell from your hand without paying its mana cost. + this.addAbility(new AttacksTriggeredAbility(new SurtlandElementalistEffect(), true)); + } + + private SurtlandElementalist(final SurtlandElementalist card) { + super(card); + } + + @Override + public SurtlandElementalist copy() { + return new SurtlandElementalist(this); + } +} + +class SurtlandElementalistEffect extends OneShotEffect { + + public SurtlandElementalistEffect () { + super(Outcome.PlayForFree); + this.staticText = "cast an instant or sorcery spell from your hand without paying its mana cost"; + } + + private SurtlandElementalistEffect(final SurtlandElementalistEffect effect) { + super(effect); + } + + @Override + public SurtlandElementalistEffect copy() { + return new SurtlandElementalistEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player != null) { + TargetCardInHand target = new TargetCardInHand(0, 1, StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY); + if (player.chooseTarget(Outcome.PlayForFree, target, source, game)) { + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); + boolean cardWasCast = player.cast(player.chooseAbilityForCast(card, game, true), + game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); + return cardWasCast; + } + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheCheeseStandsAlone.java b/Mage.Sets/src/mage/cards/t/TheCheeseStandsAlone.java index 51a15f4c2f2..571754535a8 100644 --- a/Mage.Sets/src/mage/cards/t/TheCheeseStandsAlone.java +++ b/Mage.Sets/src/mage/cards/t/TheCheeseStandsAlone.java @@ -1,7 +1,5 @@ - package mage.cards.t; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; @@ -18,8 +16,9 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.players.Player; +import java.util.UUID; + /** - * * @author spjspj */ public final class TheCheeseStandsAlone extends CardImpl { @@ -47,6 +46,7 @@ class CheeseStandsAloneContinuousEffect extends ContinuousRuleModifyingEffectImp private static final FilterControlledPermanent filter = new FilterControlledPermanent(); private boolean wonAlready = false; + static { filter.add(new NamePredicate("The Cheese Stands Alone")); } @@ -57,7 +57,7 @@ class CheeseStandsAloneContinuousEffect extends ContinuousRuleModifyingEffectImp } public CheeseStandsAloneContinuousEffect(final CheeseStandsAloneContinuousEffect effect) { - super(effect); + super(effect); } @Override @@ -72,7 +72,7 @@ class CheeseStandsAloneContinuousEffect extends ContinuousRuleModifyingEffectImp if (controller.getHand().isEmpty()) { int numberPerms = new PermanentsOnBattlefieldCount(new FilterControlledPermanent()).calculate(game, source, this); if (numberPerms == 1) { - if (game.getBattlefield().contains(filter, source.getControllerId(), 1, game)) { + if (game.getBattlefield().containsControlled(filter, source, game, 1)) { if (!wonAlready) { wonAlready = true; controller.won(game); diff --git a/Mage.Sets/src/mage/cards/t/TidalInfluence.java b/Mage.Sets/src/mage/cards/t/TidalInfluence.java index cdb1f64ed2a..a25f2d06c04 100644 --- a/Mage.Sets/src/mage/cards/t/TidalInfluence.java +++ b/Mage.Sets/src/mage/cards/t/TidalInfluence.java @@ -1,7 +1,5 @@ - package mage.cards.t; -import java.util.UUID; import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.StateTriggeredAbility; @@ -31,8 +29,9 @@ import mage.filter.predicate.mageobject.NamePredicate; import mage.game.Game; import mage.game.events.GameEvent; +import java.util.UUID; + /** - * * @author LoneFox */ public final class TidalInfluence extends CardImpl { @@ -44,26 +43,26 @@ public final class TidalInfluence extends CardImpl { } public TidalInfluence(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}"); // Cast Tidal Influence only if no permanents named Tidal Influence are on the battlefield. this.getSpellAbility().addCost(new TidalInfluenceCost()); // Tidal Influence enters the battlefield with a tide counter on it. this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.TIDE.createInstance()), - "with a tide counter on it.")); + "with a tide counter on it.")); // At the beginning of your upkeep, put a tide counter on Tidal Influence. this.addAbility(new BeginningOfUpkeepTriggeredAbility(new AddCountersSourceEffect(CounterType.TIDE.createInstance()), - TargetController.YOU, false)); + TargetController.YOU, false)); // As long as there is exactly one tide counter on Tidal Influence, all blue creatures get -2/-0. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect( - new BoostAllEffect(-2, -0, Duration.WhileOnBattlefield, filter, false), - new SourceHasCounterCondition(CounterType.TIDE, 1, 1), - "As long as there is exactly one tide counter on {this}, all blue creatures get -2/-0."))); + new BoostAllEffect(-2, -0, Duration.WhileOnBattlefield, filter, false), + new SourceHasCounterCondition(CounterType.TIDE, 1, 1), + "As long as there is exactly one tide counter on {this}, all blue creatures get -2/-0."))); // As long as there are exactly three tide counters on Tidal Influence, all blue creatures get +2/+0. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect( - new BoostAllEffect(2, -0, Duration.WhileOnBattlefield, filter, false), - new SourceHasCounterCondition(CounterType.TIDE, 3, 3), - "As long as there are exactly three tide counter on {this}, all blue creatures get +2/-0."))); + new BoostAllEffect(2, -0, Duration.WhileOnBattlefield, filter, false), + new SourceHasCounterCondition(CounterType.TIDE, 3, 3), + "As long as there are exactly three tide counter on {this}, all blue creatures get +2/-0."))); // Whenever there are four tide counters on Tidal Influence, remove all tide counters from it. this.addAbility(new TidalInfluenceTriggeredAbility(new RemoveAllCountersSourceEffect(CounterType.TIDE))); } @@ -97,7 +96,7 @@ class TidalInfluenceCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return !game.getBattlefield().contains(filter, 1, game); + return !game.getBattlefield().contains(filter, source, game, 1); } @Override diff --git a/Mage.Sets/src/mage/cards/t/TravelersCloak.java b/Mage.Sets/src/mage/cards/t/TravelersCloak.java index 474de5cc44d..c78c2a14715 100644 --- a/Mage.Sets/src/mage/cards/t/TravelersCloak.java +++ b/Mage.Sets/src/mage/cards/t/TravelersCloak.java @@ -1,13 +1,13 @@ - package mage.cards.t; -import java.util.UUID; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.AsEntersBattlefieldAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.Effect; import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.ChooseCreatureTypeEffect; import mage.abilities.effects.common.ChooseLandTypeEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; @@ -15,24 +15,24 @@ import mage.abilities.keyword.EnchantAbility; import mage.abilities.keyword.LandwalkAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AttachmentType; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Outcome; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.common.FilterLandPermanent; -import mage.filter.predicate.mageobject.ChosenSubtypePredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author emerald000 */ public final class TravelersCloak extends CardImpl { public TravelersCloak(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}"); this.subtype.add(SubType.AURA); // Enchant creature @@ -41,18 +41,17 @@ public final class TravelersCloak extends CardImpl { this.getSpellAbility().addEffect(new AttachEffect(Outcome.AddAbility)); Ability ability = new EnchantAbility(auraTarget.getTargetName()); this.addAbility(ability); - + // As Traveler's Cloak enters the battlefield, choose a land type. this.addAbility(new AsEntersBattlefieldAbility(new ChooseLandTypeEffect(Outcome.AddAbility))); - + // When Traveler's Cloak enters the battlefield, draw a card. this.addAbility(new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1), false)); - + // Enchanted creature has landwalk of the chosen type. FilterLandPermanent filter = new FilterLandPermanent("Landwalk of the chosen type"); - filter.add(ChosenSubtypePredicate.instance); - Ability landwalkAbility = new LandwalkAbility(filter); - Effect effect = new GainAbilityAttachedEffect(landwalkAbility, AttachmentType.AURA); + filter.add(TravelersCloakChosenSubtypePredicate.instance); + Effect effect = new TravelersCloakGainAbilityAttachedEffect(filter); effect.setText("Enchanted creature has landwalk of the chosen type"); this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect)); } @@ -66,3 +65,35 @@ public final class TravelersCloak extends CardImpl { return new TravelersCloak(this); } } + +class TravelersCloakGainAbilityAttachedEffect extends GainAbilityAttachedEffect { + + public TravelersCloakGainAbilityAttachedEffect(FilterLandPermanent filter) { + super(new LandwalkAbility(filter), AttachmentType.AURA); + } + + @Override + public void afterGain(Game game, Ability source, Permanent permanent, Ability addedAbility) { + super.afterGain(game, source, permanent, addedAbility); + + // ChooseLandTypeEffect keep settings in original source, but we must transfer it to real permanent + Object val = game.getState().getValue(source.getSourceId() + "_type"); + game.getState().setValue(permanent.getId() + "_landwalk_type", val); + } +} + +enum TravelersCloakChosenSubtypePredicate implements ObjectSourcePlayerPredicate> { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + SubType subType = ChooseCreatureTypeEffect.getChosenCreatureType(input.getSourceId(), game, "_landwalk_type"); + return input.getObject().hasSubtype(subType, game); + } + + @Override + public String toString() { + return "Chosen subtype"; + } +} + diff --git a/Mage.Sets/src/mage/cards/v/VivienMonstersAdvocate.java b/Mage.Sets/src/mage/cards/v/VivienMonstersAdvocate.java index 79dd0e089ad..a26aa44de8c 100644 --- a/Mage.Sets/src/mage/cards/v/VivienMonstersAdvocate.java +++ b/Mage.Sets/src/mage/cards/v/VivienMonstersAdvocate.java @@ -107,7 +107,8 @@ class VivienMonstersAdvocateTokenEffect extends OneShotEffect { if (permanent == null) { continue; } - Choice choice = new ChoiceImpl(); + Choice choice = new ChoiceImpl(true); + choice.setMessage("Choose vigilance, reach, or trample counter"); choice.setChoices(choices); player.choose(outcome, choice, game); String chosen = choice.getChoice(); diff --git a/Mage.Sets/src/mage/cards/w/WeightOfConscience.java b/Mage.Sets/src/mage/cards/w/WeightOfConscience.java index 49f6d7c665e..96a45851fd7 100644 --- a/Mage.Sets/src/mage/cards/w/WeightOfConscience.java +++ b/Mage.Sets/src/mage/cards/w/WeightOfConscience.java @@ -1,9 +1,5 @@ package mage.cards.w; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; @@ -25,8 +21,12 @@ import mage.target.TargetPermanent; import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetCreaturePermanent; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + /** - * * @author emerald000 */ public final class WeightOfConscience extends CardImpl { @@ -84,8 +84,8 @@ class WeightOfConscienceEffect extends OneShotEffect { // It was not blinked, use the standard method enchantment = game.getPermanentOrLKIBattlefield(source.getSourceId()); } - if (controller != null - && enchantment != null + if (controller != null + && enchantment != null && enchantment.getAttachedTo() != null) { Permanent creature = game.getPermanent(enchantment.getAttachedTo()); if (creature != null) { @@ -119,10 +119,10 @@ class WeightOfConscienceTarget extends TargetControlledCreaturePermanent { if (player != null) { // Choosing first target if (this.getTargets().isEmpty()) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(filterUntapped, sourceControllerId, game)) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(filterUntapped, sourceControllerId, sourceId, game)) { for (SubType subtype : permanent.getSubtype(game)) { if (subtype.getSubTypeSet() == SubTypeSet.CreatureType) { - if (game.getBattlefield().contains(new FilterControlledCreaturePermanent(subtype, subtype.toString()), sourceControllerId, game, 2)) { + if (game.getBattlefield().contains(new FilterControlledCreaturePermanent(subtype, subtype.toString()), sourceId, sourceControllerId, game, 2)) { possibleTargets.add(permanent.getId()); } } @@ -133,7 +133,7 @@ class WeightOfConscienceTarget extends TargetControlledCreaturePermanent { UUID firstTargetId = this.getTargets().get(0); Permanent firstTargetCreature = game.getPermanent(firstTargetId); if (firstTargetCreature != null) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(filterUntapped, sourceControllerId, game)) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(filterUntapped, sourceControllerId, sourceId, game)) { if (!permanent.getId().equals(firstTargetId) && firstTargetCreature.shareCreatureTypes(permanent, game)) { possibleTargets.add(permanent.getId()); } @@ -146,8 +146,8 @@ class WeightOfConscienceTarget extends TargetControlledCreaturePermanent { @Override public boolean canChoose(UUID sourceId, UUID sourceControllerId, Game game) { - for (Permanent permanent1 : game.getBattlefield().getActivePermanents(filterUntapped, sourceControllerId, game)) { - for (Permanent permanent2 : game.getBattlefield().getActivePermanents(filterUntapped, sourceControllerId, game)) { + for (Permanent permanent1 : game.getBattlefield().getActivePermanents(filterUntapped, sourceControllerId, sourceId, game)) { + for (Permanent permanent2 : game.getBattlefield().getActivePermanents(filterUntapped, sourceControllerId, sourceId, game)) { if (!Objects.equals(permanent1, permanent2) && permanent1.shareCreatureTypes(permanent2, game)) { return true; } @@ -162,10 +162,10 @@ class WeightOfConscienceTarget extends TargetControlledCreaturePermanent { Permanent targetPermanent = game.getPermanent(id); if (targetPermanent != null) { if (this.getTargets().isEmpty()) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(filterUntapped, source.getControllerId(), game)) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(filterUntapped, source.getControllerId(), source.getSourceId(), game)) { for (SubType subtype : permanent.getSubtype(game)) { if (subtype.getSubTypeSet() == SubTypeSet.CreatureType) { - if (game.getBattlefield().contains(new FilterControlledCreaturePermanent(subtype, subtype.toString()), source.getControllerId(), game, 2)) { + if (game.getBattlefield().contains(new FilterControlledCreaturePermanent(subtype, subtype.toString()), source, game, 2)) { return true; } } @@ -173,9 +173,7 @@ class WeightOfConscienceTarget extends TargetControlledCreaturePermanent { } } else { Permanent firstTarget = game.getPermanent(this.getTargets().get(0)); - if (firstTarget != null && firstTarget.shareCreatureTypes(targetPermanent, game)) { - return true; - } + return firstTarget != null && firstTarget.shareCreatureTypes(targetPermanent, game); } } } diff --git a/Mage.Sets/src/mage/cards/w/Wirecat.java b/Mage.Sets/src/mage/cards/w/Wirecat.java index d7de3f37296..79bb613a15a 100644 --- a/Mage.Sets/src/mage/cards/w/Wirecat.java +++ b/Mage.Sets/src/mage/cards/w/Wirecat.java @@ -69,7 +69,7 @@ public final class Wirecat extends CardImpl { @Override public boolean applies(Permanent permanent, Ability source, Game game) { if (permanent.getId().equals(source.getSourceId())) { - return game.getBattlefield().contains(StaticFilters.FILTER_ENCHANTMENT_PERMANENT, 1, game); + return game.getBattlefield().contains(StaticFilters.FILTER_ENCHANTMENT_PERMANENT, source, game, 1); } return false; } diff --git a/Mage.Sets/src/mage/sets/Kaldheim.java b/Mage.Sets/src/mage/sets/Kaldheim.java index 92e076fa44b..da078d6c60e 100644 --- a/Mage.Sets/src/mage/sets/Kaldheim.java +++ b/Mage.Sets/src/mage/sets/Kaldheim.java @@ -44,9 +44,11 @@ public final class Kaldheim extends ExpansionSet { cards.add(new SetCardInfo("Gladewalker Ritualist", 392, Rarity.UNCOMMON, mage.cards.g.GladewalkerRitualist.class)); cards.add(new SetCardInfo("Hengegate Pathway", 260, Rarity.RARE, mage.cards.h.HengegatePathway.class)); cards.add(new SetCardInfo("Rampage of the Valkyries", 393, Rarity.UNCOMMON, mage.cards.r.RampageOfTheValkyries.class)); + cards.add(new SetCardInfo("Realmwalker", 188, Rarity.RARE, mage.cards.r.Realmwalker.class)); cards.add(new SetCardInfo("Renegade Reaper", 386, Rarity.UNCOMMON, mage.cards.r.RenegadeReaper.class)); cards.add(new SetCardInfo("Showdown of the Skalds", 229, Rarity.RARE, mage.cards.s.ShowdownOfTheSkalds.class)); cards.add(new SetCardInfo("Starnheim Aspirant", 380, Rarity.UNCOMMON, mage.cards.s.StarnheimAspirant.class)); + cards.add(new SetCardInfo("Surtland Elementalist", 375, Rarity.RARE, mage.cards.s.SurtlandElementalist.class)); cards.add(new SetCardInfo("Surtland Flinger", 377, Rarity.RARE, mage.cards.s.SurtlandFlinger.class)); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/TargetAmountAITest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/TargetAmountAITest.java new file mode 100644 index 00000000000..09776653e3e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/TargetAmountAITest.java @@ -0,0 +1,53 @@ +package org.mage.test.AI.basic; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; + +/** + * @author JayDi85 + */ +public class TargetAmountAITest extends CardTestPlayerBaseWithAIHelps { + + @Test + public void test_AI_ChooseTargets() { + // Distribute four +1/+1 counters among any number of target creatures. + addCard(Zone.HAND, playerA, "Blessings of Nature", 1); // {4}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); + // + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 4); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 4); // 2/2 + + // ai must choose by special dialog, not full simulation + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Blessings of Nature"); + + setStopAt(1, PhaseStep.END_TURN); + //setStrictChooseMode(true); // ai must choose + execute(); + assertAllCommandsUsed(); + + assertPowerToughness(playerA, "Balduvian Bears", 2 + 4, 2 + 4); // boost one creature (it's just a choose code, so can be different from simulation results) + } + + @Test + public void test_AI_SimulateTargets() { + // Distribute four +1/+1 counters among any number of target creatures. + addCard(Zone.HAND, playerA, "Blessings of Nature", 1); // {4}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); + // + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 4); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 4); // 2/2 + + // AI must put creatures on own permanents (all in one creature to boost it) + aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertPowerToughness(playerA, "Balduvian Bears", 2 + 1, 2 + 1); // boost each possible creatures + assertPowerToughness(playerB, "Balduvian Bears", 2, 2); // no boost for enemy + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/conditional/LegendarySorceryTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/conditional/LegendarySorceryTest.java index 68936f22903..aa70d087898 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/conditional/LegendarySorceryTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/conditional/LegendarySorceryTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.conditional; import mage.constants.PhaseStep; @@ -7,7 +6,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class LegendarySorceryTest extends CardTestPlayerBase { @@ -55,10 +53,13 @@ public class LegendarySorceryTest extends CardTestPlayerBase { // Flying, first strike, vigilance, trample, haste, protection from black and from red addCard(Zone.BATTLEFIELD, playerB, "Akroma, Angel of Wrath", 1); // Legendary + // can't cast cause you don't have a legendary creature (only opponent have) castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Urza's Ruinous Blast"); + //setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + //assertAllCommandsUsed(); assertGraveyardCount(playerA, "Urza's Ruinous Blast", 0); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/cmr/ArchelosLagoonMysticTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/cmr/ArchelosLagoonMysticTest.java new file mode 100644 index 00000000000..b637b9e7476 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/cmr/ArchelosLagoonMysticTest.java @@ -0,0 +1,46 @@ +package org.mage.test.cards.single.cmr; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ + +public class ArchelosLagoonMysticTest extends CardTestPlayerBase { + + @Test + public void test_Playable() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2 + 2); // add lands before Archelos, Lagoon Mystic to ignore ETB effects + + // As long as Archelos, Lagoon Mystic is tapped, other permanents enter the battlefield tapped. + // As long as Archelos, Lagoon Mystic is untapped, other permanents enter the battlefield untapped. + addCard(Zone.BATTLEFIELD, playerA, "Archelos, Lagoon Mystic", 1); + // + addCard(Zone.HAND, playerA, "Grizzly Bears", 1); // {1}{G} + addCard(Zone.HAND, playerA, "Deranged Outcast", 1); // {1}{G} + + // first - untapped + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentTapped("untapped", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Archelos, Lagoon Mystic", false, 1); + checkPermanentTapped("untapped", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears", false, 1); + + // prepare tapped mystic + attack(1, playerA, "Archelos, Lagoon Mystic", playerB); + checkPermanentTapped("tapped", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Archelos, Lagoon Mystic", true, 1); + + // second - tapped + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Deranged Outcast"); + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); + checkPermanentTapped("tapped", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Archelos, Lagoon Mystic", true, 1); + checkPermanentTapped("tapped", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Deranged Outcast", true, 1); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/inv/TravelersCloakTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/inv/TravelersCloakTest.java new file mode 100644 index 00000000000..4e83fda10fb --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/inv/TravelersCloakTest.java @@ -0,0 +1,54 @@ +package org.mage.test.cards.single.inv; + +import mage.abilities.keyword.LandwalkAbility; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.permanent.Permanent; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ + +public class TravelersCloakTest extends CardTestPlayerBase { + + @Test + public void test_MustHaveLandWalkOfTheChosenType() { + // Enchant creature + // As Traveler's Cloak enters the battlefield, choose a land type. + // When Traveler's Cloak enters the battlefield, draw a card. + // Enchanted creature has landwalk of the chosen type. + addCard(Zone.HAND, playerA, "Traveler's Cloak"); // {2}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + // + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1); + addCard(Zone.BATTLEFIELD, playerB, "Kitesail Corsair", 1); + + // cast and assign landwalk ability to creature + checkAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears", LandwalkAbility.class, false); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Traveler's Cloak", "Grizzly Bears"); + setChoice(playerA, "Swamp"); // land type for landwalk + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkAbility("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears", LandwalkAbility.class, true); + + // check that it can't be blocked + attack(1, playerA, "Grizzly Bears"); + runCode("check blocking", 1, PhaseStep.DECLARE_BLOCKERS, playerB, (info, player, game) -> { + Permanent blocker = game.getBattlefield().getAllActivePermanents() + .stream() + .filter(p -> p.getName().equals("Kitesail Corsair")) + .findFirst() + .get(); + Assert.assertFalse("Grizzly Bears must be protected from blocking by Kitesail Corsair", + game.getCombat().getGroups().get(0).canBlock(blocker, game)); + }); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/m21/ConspicuousSnoopTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/m21/ConspicuousSnoopTest.java index 140b141c3d4..d9424400c9b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/m21/ConspicuousSnoopTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/m21/ConspicuousSnoopTest.java @@ -9,7 +9,7 @@ import org.mage.test.serverside.base.CardTestPlayerBase; public class ConspicuousSnoopTest extends CardTestPlayerBase { @Test - public void testTopCardLibraryRevealed(){ + public void test_TopCardLibraryRevealed() { // Play with the top card of your library revealed. addCard(Zone.BATTLEFIELD, playerA, "Conspicuous Snoop"); addCard(Zone.LIBRARY, playerA, "Swamp"); @@ -23,43 +23,49 @@ public class ConspicuousSnoopTest extends CardTestPlayerBase { } @Test - public void castGoblinSpellsFromLibrary(){ + public void test_castGoblinSpellsFromLibrary() { + skipInitShuffling(); + removeAllCardsFromLibrary(playerA); + // You may cast Goblin spells from the top of your library. addCard(Zone.BATTLEFIELD, playerA, "Conspicuous Snoop"); - - addCard(Zone.LIBRARY, playerA, "Goblin Lackey"); - skipInitShuffling(); - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); - // Whenever Goblin Lackey deals damage to a player, you may put a Goblin permanent card from your hand onto the battlefield. + addCard(Zone.LIBRARY, playerA, "Atog", 1); // second from top + addCard(Zone.LIBRARY, playerA, "Goblin Lackey", 1); // first from top + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2 * 2); + + // Whenever Goblin Lackey deals damage to a player, you may put a Goblin permanent card from your hand onto the battlefield. + checkPlayableAbility("can play goblin", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Lackey", true); + checkPlayableAbility("can't play non goblin before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Atog", false); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Goblin Lackey"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPlayableAbility("can't play non goblin after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Atog", false); setStopAt(1, PhaseStep.PRECOMBAT_MAIN); setStrictChooseMode(true); execute(); assertAllCommandsUsed(); - assertPermanentCount(playerA, "Goblin Lackey", 1); + assertPermanentCount(playerA, "Goblin Lackey", 1); } @Test - public void hasActivatedAbilities(){ + public void test_hasActivatedAbilities() { addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); // Play with the top card of your library revealed. // You may cast Goblin spells from the top of your library. // As long as the top card of your library is a Goblin card, Conspicuous Snoop has all activated abilities of that card. addCard(Zone.BATTLEFIELD, playerA, "Conspicuous Snoop"); // {R}: Goblin Balloon Brigade gains flying until end of turn. - addCard(Zone.LIBRARY, playerA, "Goblin Balloon Brigade"); + addCard(Zone.LIBRARY, playerA, "Goblin Balloon Brigade"); skipInitShuffling(); setStopAt(1, PhaseStep.PRECOMBAT_MAIN); - + setStrictChooseMode(true); execute(); - + assertAllCommandsUsed(); assertAbilityCount(playerA, "Conspicuous Snoop", ActivatedAbility.class, 3); // (2 X casts + gains flying ) - } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/split/CastSplitCardsWithSpliceTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/split/CastSplitCardsWithSpliceTest.java index e9d2b3d5b1d..85b9b889479 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/split/CastSplitCardsWithSpliceTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/split/CastSplitCardsWithSpliceTest.java @@ -79,8 +79,6 @@ public class CastSplitCardsWithSpliceTest extends CardTestPlayerBase { addTarget(playerA, "Bow of Nylea"); // target right // must used all mana - //showAvaileableMana("after", 1, PhaseStep.POSTCOMBAT_MAIN, playerA); - setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index e34fcf16490..8e27ddeed28 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -1956,7 +1956,7 @@ public class TestPlayer implements Player { int index = 0; for (Map.Entry entry : rEffects.entrySet()) { if (entry.getValue().startsWith(choice)) { - choices.remove(choice); + choices.remove(0); return index; } index++; diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java index d5d228dd145..6e1eb646b87 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java @@ -14,8 +14,10 @@ import mage.players.Player; import mage.players.PlayerType; import mage.server.game.GameFactory; import mage.server.game.PlayerFactory; +import mage.server.managers.ConfigSettings; import mage.server.tournament.TournamentFactory; -import mage.server.util.ConfigSettings; +import mage.server.util.ConfigFactory; +import mage.server.util.ConfigWrapper; import mage.server.util.PluginClassLoader; import mage.server.util.config.GamePlugin; import mage.server.util.config.Plugin; @@ -100,7 +102,7 @@ public abstract class MageTestBase { public static void init() { Logger.getRootLogger().setLevel(Level.DEBUG); deleteSavedGames(); - ConfigSettings config = ConfigSettings.instance; + ConfigSettings config = new ConfigWrapper(ConfigFactory.loadFromFile("config/config.xml")); config.getGameTypes().forEach((gameType) -> { GameFactory.instance.addGameType(gameType.getName(), loadGameType(gameType), loadPlugin(gameType)); }); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java index 1d9c9f9f022..1d88a74644c 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java @@ -23,7 +23,9 @@ import mage.game.permanent.PermanentCard; import mage.game.tournament.TournamentType; import mage.players.Player; import mage.server.game.GameFactory; -import mage.server.util.ConfigSettings; +import mage.server.managers.ConfigSettings; +import mage.server.util.ConfigFactory; +import mage.server.util.ConfigWrapper; import mage.server.util.PluginClassLoader; import mage.server.util.config.GamePlugin; import mage.server.util.config.Plugin; @@ -114,7 +116,7 @@ public abstract class MageTestPlayerBase { logger.debug("Default charset: " + Charset.defaultCharset()); deleteSavedGames(); - ConfigSettings config = ConfigSettings.instance; + ConfigSettings config = new ConfigWrapper(ConfigFactory.loadFromFile("config/config.xml")); for (GamePlugin plugin : config.getGameTypes()) { GameFactory.instance.addGameType(plugin.getName(), loadGameType(plugin), loadPlugin(plugin)); } diff --git a/Mage/src/main/java/mage/abilities/common/LegendarySpellAbility.java b/Mage/src/main/java/mage/abilities/common/LegendarySpellAbility.java index 0cc00740fde..2ad18db7a90 100644 --- a/Mage/src/main/java/mage/abilities/common/LegendarySpellAbility.java +++ b/Mage/src/main/java/mage/abilities/common/LegendarySpellAbility.java @@ -2,11 +2,7 @@ package mage.abilities.common; import mage.abilities.Ability; import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.SuperType; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.predicate.Predicates; import mage.game.Game; @@ -65,7 +61,7 @@ class LegendarySpellAbilityCheckEffect extends ContinuousRuleModifyingEffectImpl @Override public boolean applies(GameEvent event, Ability source, Game game) { return event.getSourceId().equals(source.getSourceId()) - && !game.getBattlefield().contains(filter, event.getPlayerId(), 1, game); + && !game.getBattlefield().containsControlled(filter, source, game, 1); } @Override diff --git a/Mage/src/main/java/mage/abilities/condition/common/MetalcraftCondition.java b/Mage/src/main/java/mage/abilities/condition/common/MetalcraftCondition.java index 51e76836797..66cc9ce3bb0 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/MetalcraftCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/MetalcraftCondition.java @@ -22,7 +22,7 @@ public enum MetalcraftCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - return game.getBattlefield().contains(filter, source.getControllerId(), 3, game); + return game.getBattlefield().containsControlled(filter, source, game, 3); } @Override diff --git a/Mage/src/main/java/mage/abilities/costs/common/ControlPermanentCost.java b/Mage/src/main/java/mage/abilities/costs/common/ControlPermanentCost.java index 17c366f5fcd..c557d3b7d83 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/ControlPermanentCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/ControlPermanentCost.java @@ -1,15 +1,15 @@ package mage.abilities.costs.common; import mage.abilities.Ability; +import mage.abilities.costs.Cost; import mage.abilities.costs.CostImpl; import mage.filter.common.FilterControlledPermanent; import mage.game.Game; import java.util.UUID; -import mage.abilities.costs.Cost; public class ControlPermanentCost extends CostImpl { - private FilterControlledPermanent filter; + private final FilterControlledPermanent filter; public ControlPermanentCost(FilterControlledPermanent filter) { this.filter = filter.copy(); @@ -23,7 +23,7 @@ public class ControlPermanentCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return game.getBattlefield().contains(filter, controllerId, 1, game); + return game.getBattlefield().containsControlled(filter, source.getSourceId(), controllerId, game, 1); } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/ChooseCreatureTypeEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ChooseCreatureTypeEffect.java index c1bdd3f9f48..1f89ff5a785 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ChooseCreatureTypeEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ChooseCreatureTypeEffect.java @@ -1,7 +1,5 @@ - package mage.abilities.effects.common; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; @@ -14,6 +12,8 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.util.CardUtil; +import java.util.UUID; + /** * @author LevelX2 */ @@ -56,15 +56,19 @@ public class ChooseCreatureTypeEffect extends OneShotEffect { return new ChooseCreatureTypeEffect(this); } + public static SubType getChosenCreatureType(UUID objectId, Game game) { + return getChosenCreatureType(objectId, game, "_type"); + } + /** - * - * @param objectId sourceId the effect was exeuted under + * @param objectId sourceId the effect was exeuted under * @param game + * @param typePostfix special postfix if you want to store multiple choices from different effects * @return */ - public static SubType getChosenCreatureType(UUID objectId, Game game) { + public static SubType getChosenCreatureType(UUID objectId, Game game, String typePostfix) { SubType creatureType = null; - Object savedCreatureType = game.getState().getValue(objectId + "_type"); + Object savedCreatureType = game.getState().getValue(objectId + typePostfix); if (savedCreatureType != null) { creatureType = SubType.byDescription(savedCreatureType.toString()); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityAttachedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityAttachedEffect.java index 3eb23d9a7b7..437d4a8b9db 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityAttachedEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityAttachedEffect.java @@ -96,10 +96,23 @@ public class GainAbilityAttachedEffect extends ContinuousEffectImpl { } if (permanent != null) { permanent.addAbility(ability, source.getSourceId(), game); + afterGain(game, source, permanent, ability); } return true; } + /** + * Calls after ability gain. Override it to apply additional data (example: transfer ability's settings from original to destination source) + * + * @param game + * @param source + * @param permanent + * @param addedAbility + */ + public void afterGain(Game game, Ability source, Permanent permanent, Ability addedAbility) { + // + } + private void setText() { StringBuilder sb = new StringBuilder(); sb.append(attachmentType.verb()); diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardEffect.java index b0119f94853..34b136f26ad 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardEffect.java @@ -61,7 +61,7 @@ public class PlayTheTopCardEffect extends AsThoughEffectImpl { && playerId.equals(source.getControllerId()) && cardToCheck.isOwnedBy(source.getControllerId()) && (!cardToCheck.getManaCost().isEmpty() || cardToCheck.isLand()) - && filter.match(cardToCheck, game)) { + && filter.match(cardToCheck, source.getSourceId(), source.getControllerId(), game)) { Player player = game.getPlayer(cardToCheck.getOwnerId()); UUID needCardID = player.getLibrary().getFromTop(game) == null ? null : player.getLibrary().getFromTop(game).getId(); diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/AmassEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/AmassEffect.java index f443ca65bcf..fab9b36f968 100644 --- a/Mage/src/main/java/mage/abilities/effects/keyword/AmassEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/keyword/AmassEffect.java @@ -67,7 +67,7 @@ public class AmassEffect extends OneShotEffect { if (player == null) { return false; } - if (!game.getBattlefield().contains(filter, source.getControllerId(), 1, game)) { + if (!game.getBattlefield().containsControlled(filter, source, game, 1)) { new CreateTokenEffect(new ZombieArmyToken()).apply(game, source); } Target target = new TargetPermanent(filter); diff --git a/Mage/src/main/java/mage/abilities/keyword/ConvokeAbility.java b/Mage/src/main/java/mage/abilities/keyword/ConvokeAbility.java index 7af6c49a480..b5728d69b6c 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ConvokeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ConvokeAbility.java @@ -104,7 +104,7 @@ public class ConvokeAbility extends SimpleStaticAbility implements AlternateMana @Override public void addSpecialAction(Ability source, Game game, ManaCost unpaid) { Player controller = game.getPlayer(source.getControllerId()); - if (controller != null && game.getBattlefield().contains(filterUntapped, controller.getId(), 1, game)) { + if (controller != null && game.getBattlefield().containsControlled(filterUntapped, source, game, 1)) { if (source.getAbilityType() == AbilityType.SPELL) { SpecialAction specialAction = new ConvokeSpecialAction(unpaid, this); specialAction.setControllerId(source.getControllerId()); diff --git a/Mage/src/main/java/mage/abilities/keyword/LandwalkAbility.java b/Mage/src/main/java/mage/abilities/keyword/LandwalkAbility.java index dc4c59aee19..fad955ee02c 100644 --- a/Mage/src/main/java/mage/abilities/keyword/LandwalkAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/LandwalkAbility.java @@ -14,6 +14,12 @@ import mage.game.permanent.Permanent; */ public class LandwalkAbility extends EvasionAbility { + /** + * Don't use source related filters here (example: landwalk for user selected land type). + * If you want it then use workaround from Traveler's Cloak to transfer settings after gain + * + * @param filter + */ public LandwalkAbility(FilterLandPermanent filter) { this(filter, true); } @@ -39,7 +45,6 @@ public class LandwalkAbility extends EvasionAbility { } return ruleText; } - } class LandwalkEffect extends RestrictionEffect { @@ -59,7 +64,7 @@ class LandwalkEffect extends RestrictionEffect { @Override public boolean canBeBlocked(Permanent attacker, Permanent blocker, Ability source, Game game, boolean canUseChooseDialogs) { - if (game.getBattlefield().contains(filter, blocker.getControllerId(), 1, game) + if (game.getBattlefield().contains(filter, source.getSourceId(), blocker.getControllerId(), game, 1) && null == game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_LANDWALK, source, blocker.getControllerId(), game)) { switch (filter.getMessage()) { case "plains": @@ -74,7 +79,6 @@ class LandwalkEffect extends RestrictionEffect { return null != game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_FORESTWALK, source, blocker.getControllerId(), game); default: return false; - } } return true; diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/ChosenSubtypePredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/ChosenSubtypePredicate.java index bbde6c6b86a..ab0deb49f2b 100644 --- a/Mage/src/main/java/mage/filter/predicate/mageobject/ChosenSubtypePredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/ChosenSubtypePredicate.java @@ -1,4 +1,3 @@ - package mage.filter.predicate.mageobject; import mage.MageObject; @@ -9,6 +8,9 @@ import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; /** + * Warning, chosen type assign to original source ability, but after gain you will see another sourceId, + * see Traveler's Cloak for workaround to trasfer settings + * * @author LoneFox */ public enum ChosenSubtypePredicate implements ObjectSourcePlayerPredicate> { diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 122b55f330a..d187a5ff4d5 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -2276,7 +2276,7 @@ public abstract class GameImpl implements Game, Serializable { filterLegendName.add(SuperType.LEGENDARY.getPredicate()); filterLegendName.add(new NamePredicate(legend.getName())); filterLegendName.add(new ControllerIdPredicate(legend.getControllerId())); - if (getBattlefield().contains(filterLegendName, legend.getControllerId(), this, 2)) { + if (getBattlefield().contains(filterLegendName, null, legend.getControllerId(), this, 2)) { if (!replaceEvent(GameEvent.getEvent(GameEvent.EventType.DESTROY_PERMANENT_BY_LEGENDARY_RULE, legend.getId(), legend.getControllerId()))) { Player controller = this.getPlayer(legend.getControllerId()); if (controller != null) { diff --git a/Mage/src/main/java/mage/game/command/emblems/GideonOfTheTrialsEmblem.java b/Mage/src/main/java/mage/game/command/emblems/GideonOfTheTrialsEmblem.java index 700ba7591e3..d4265310822 100644 --- a/Mage/src/main/java/mage/game/command/emblems/GideonOfTheTrialsEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/GideonOfTheTrialsEmblem.java @@ -50,7 +50,7 @@ class GideonOfTheTrialsCantLoseEffect extends ContinuousRuleModifyingEffectImpl public boolean applies(GameEvent event, Ability source, Game game) { if ((event.getType() == GameEvent.EventType.WINS && game.getOpponents(source.getControllerId()).contains(event.getPlayerId())) || (event.getType() == GameEvent.EventType.LOSES && event.getPlayerId().equals(source.getControllerId()))) { - return game.getBattlefield().contains(filter, source.getControllerId(), 1, game); + return game.getBattlefield().containsControlled(filter, source, game, 1); } return false; } diff --git a/Mage/src/main/java/mage/game/permanent/Battlefield.java b/Mage/src/main/java/mage/game/permanent/Battlefield.java index 054dfcbd4fb..24c53796f4a 100644 --- a/Mage/src/main/java/mage/game/permanent/Battlefield.java +++ b/Mage/src/main/java/mage/game/permanent/Battlefield.java @@ -1,5 +1,6 @@ package mage.game.permanent; +import mage.abilities.Ability; import mage.abilities.keyword.PhasingAbility; import mage.constants.CardType; import mage.constants.RangeOfInfluence; @@ -92,21 +93,8 @@ public class Battlefield implements Serializable { } } - /** - * Returns true if the battlefield contains at least 1 {@link Permanent} - * that matches the filter. This method ignores the range of influence. - * - * @param filter - * @param num - * @param game - * @return boolean - */ - public boolean contains(FilterPermanent filter, int num, Game game) { - return field.values() - .stream() - .filter(permanent -> filter.match(permanent, game) - && permanent.isPhasedIn()).count() >= num; - + public boolean containsControlled(FilterPermanent filter, Ability source, Game game, int num) { + return containsControlled(filter, source.getSourceId(), source.getControllerId(), game, num); } /** @@ -115,43 +103,49 @@ public class Battlefield implements Serializable { * ignores the range of influence. * * @param filter - * @param controllerId + * @param sourceId + * @param controllerId controller and source can be different (from different players) * @param num * @param game * @return boolean */ - public boolean contains(FilterPermanent filter, UUID controllerId, int num, Game game) { + public boolean containsControlled(FilterPermanent filter, UUID sourceId, UUID controllerId, Game game, int num) { return field.values() .stream() .filter(permanent -> permanent.isControlledBy(controllerId) - && filter.match(permanent, game) + && filter.match(permanent, sourceId, controllerId, game) && permanent.isPhasedIn()) .count() >= num; } + public boolean contains(FilterPermanent filter, Ability source, Game game, int num) { + return contains(filter, source.getSourceId(), source.getControllerId(), game, num); + } + /** * Returns true if the battlefield contains num or more {@link Permanent} * that is within the range of influence of the specified player id and that * matches the supplied filter. * * @param filter + * @param sourceId can be null for default SBA checks like legendary rule * @param sourcePlayerId * @param game * @param num * @return boolean */ - public boolean contains(FilterPermanent filter, UUID sourcePlayerId, Game game, int num) { + public boolean contains(FilterPermanent filter, UUID sourceId, UUID sourcePlayerId, Game game, int num) { if (game.getRangeOfInfluence() == RangeOfInfluence.ALL) { return field.values().stream() - .filter(permanent -> filter.match(permanent, null, sourcePlayerId, game) + .filter(permanent -> filter.match(permanent, sourceId, sourcePlayerId, game) && permanent.isPhasedIn()).count() >= num; } else { List range = game.getState().getPlayersInRange(sourcePlayerId, game); return field.values().stream() .filter(permanent -> range.contains(permanent.getControllerId()) - && filter.match(permanent, null, sourcePlayerId, game) + && filter.match(permanent, sourceId, sourcePlayerId, game) && permanent.isPhasedIn()) .count() >= num; } diff --git a/Mage/src/main/java/mage/target/TargetAmount.java b/Mage/src/main/java/mage/target/TargetAmount.java index 41da56d5ea1..72012fe9d67 100644 --- a/Mage/src/main/java/mage/target/TargetAmount.java +++ b/Mage/src/main/java/mage/target/TargetAmount.java @@ -6,10 +6,7 @@ import mage.abilities.dynamicvalue.common.StaticValue; import mage.constants.Outcome; import mage.game.Game; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; /** @@ -52,10 +49,10 @@ public abstract class TargetAmount extends TargetImpl { @Override public boolean doneChosing() { - return amountWasSet - && (remainingAmount == 0 - || (getMinNumberOfTargets() < getMaxNumberOfTargets() - && getTargets().size() >= getMinNumberOfTargets())); + return amountWasSet + && (remainingAmount == 0 + || (getMinNumberOfTargets() < getMaxNumberOfTargets() + && getTargets().size() >= getMinNumberOfTargets())); } @Override @@ -100,14 +97,14 @@ public abstract class TargetAmount extends TargetImpl { } chosen = isChosen(); while (remainingAmount > 0) { - if (!getTargetController(game, playerId).chooseTargetAmount(outcome, this, source, game)) { + if (!getTargetController(game, playerId).chooseTargetAmount(outcome, this, source, game)) { return chosen; } chosen = isChosen(); } return chosen = true; } - + @Override public List getTargetOptions(Ability source, Game game) { List options = new ArrayList<>(); @@ -115,21 +112,50 @@ public abstract class TargetAmount extends TargetImpl { addTargets(this, possibleTargets, options, source, game); + // debug target variations + //printTargetsVariations(possibleTargets, options); + return options; } - protected void addTargets(TargetAmount target, Set targets, List options, Ability source, Game game) { + private void printTargetsVariations(Set possibleTargets, List options) { + // debug target variations + // permanent index + amount + // example: 7 -> 2; 8 -> 3; 9 -> 1 + List list = new ArrayList<>(possibleTargets); + HashMap targetNumbers = new HashMap<>(); + for (int i = 0; i < list.size(); i++) { + targetNumbers.put(list.get(i), i); + } + List res = options + .stream() + .map(t -> t.getTargets() + .stream() + .map(id -> targetNumbers.get(id) + " -> " + t.getTargetAmount(id)) + .sorted() + .collect(Collectors.joining("; "))) + .collect(Collectors.toList()); + Collections.sort(res); + System.out.println(); + System.out.println(res.stream().collect(Collectors.joining("\n"))); + System.out.println(); + } + + protected void addTargets(TargetAmount target, Set possibleTargets, List options, Ability source, Game game) { if (!amountWasSet) { setAmount(source, game); } - for (UUID targetId : targets) { + Set usedTargets = new HashSet<>(); + for (UUID targetId : possibleTargets) { + usedTargets.add(targetId); for (int n = 1; n <= target.remainingAmount; n++) { TargetAmount t = target.copy(); t.addTarget(targetId, n, source, game, true); if (t.remainingAmount > 0) { - if (targets.size() > 1) { - Set newTargets = targets.stream().filter(newTarget -> !newTarget.equals(targetId)).collect(Collectors.toSet()); - addTargets(t, newTargets, options, source, game); + if (possibleTargets.size() > 1) { + // don't use that target again + Set newPossibleTargets = possibleTargets.stream().filter(newTarget -> !usedTargets.contains(newTarget)).collect(Collectors.toSet()); + addTargets(t, newPossibleTargets, options, source, game); } } else { options.add(t); diff --git a/pom.xml b/pom.xml index 1f2e7152bc1..6e94693b660 100644 --- a/pom.xml +++ b/pom.xml @@ -52,6 +52,18 @@
+ + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + -Dfile.encoding=UTF-8 + + + + Mage @@ -113,6 +125,21 @@ guava 29.0-jre + + org.junit.jupiter + junit-jupiter + 5.7.0 + + + org.assertj + assertj-core + 3.18.0 + + + org.apache.commons + commons-lang3 + 3.11 + diff --git a/readme.md b/readme.md index a25e05cdf0c..c8c06670b39 100644 --- a/readme.md +++ b/readme.md @@ -3,12 +3,15 @@ [![Build Status](https://travis-ci.org/magefree/mage.svg?branch=master)](https://travis-ci.org/magefree/mage) [![latest release](https://img.shields.io/github/v/release/magefree/mage)](https://github.com/magefree/mage/releases/) [![commints since latest release](https://img.shields.io/github/commits-since/magefree/mage/latest)](https://github.com/magefree/mage/commits/) [![Join the chat at https://gitter.im/magefree/mage](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/magefree/mage?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) XMage allows you to play Magic against one or more online players or computer opponents. -It includes full rules enforcement for over **20 000** unique cards and ~50.000 reprints from different editions. +It includes full rules enforcement for over **20 000** unique cards and more than 50.000 reprints from different editions. You can also find custom sets like Star Wars. All regular sets have nearly all implemented cards. It's support single matches and tournaments with dozens game modes like duel, multiplayer, standard, modern, commander, pauper, oathbreaker, historic, freeform and much more. +Local server supports a [special test mode](https://github.com/magefree/mage/wiki/Development-Testing-Tools) for +testing combos and other game situations with pre-defined conditions. + There are [public servers](http://xmageservers.online/) where you can play XMage against other players. You can also host your own server to play against the AI and/or your friends. @@ -58,6 +61,13 @@ Look [here](http://www.slightlymagic.net/forum/viewtopic.php?f=70&t=13632) for m [Wiki page](https://github.com/magefree/mage/wiki) contains detail information about private or public server setup. +### Server options + +The XMage server locates by default the configuration file from the current working directory to the relative path `config/config.xml` +(`config\config.xml` in Windows). To change this location, start the server with the property `xmage.config.path` set +to the desired location, for example `-Dxmage.config.path=config/otherconfig.xml`. The option can be set from the +XMageLauncher in `Settings > Java > Server java options`. + ## Troubleshooting / FAQ Github issues page contain [popular problems and fixes](https://github.com/magefree/mage/issues?q=is%3Aissue+label%3AFAQ+):