mirror of
https://github.com/correl/mage.git
synced 2024-12-26 03:00:11 +00:00
Merge branch 'master' of https://github.com/spjspj/mage
This commit is contained in:
commit
735a7cebb3
18 changed files with 59 additions and 167 deletions
|
@ -41,7 +41,7 @@ public class MageVersion implements Serializable, Comparable<MageVersion> {
|
|||
public final static int MAGE_VERSION_MAJOR = 1;
|
||||
public final static int MAGE_VERSION_MINOR = 4;
|
||||
public final static int MAGE_VERSION_PATCH = 30;
|
||||
public final static String MAGE_VERSION_MINOR_PATCH = "V1";
|
||||
public final static String MAGE_VERSION_MINOR_PATCH = "V2";
|
||||
public final static String MAGE_VERSION_INFO = "";
|
||||
|
||||
private final int major;
|
||||
|
|
|
@ -25,16 +25,11 @@
|
|||
* authors and should not be interpreted as representing official policies, either expressed
|
||||
* or implied, of BetaSteward_at_googlemail.com.
|
||||
*/
|
||||
|
||||
package mage.game;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import mage.constants.MultiplayerAttackOption;
|
||||
import mage.constants.RangeOfInfluence;
|
||||
import mage.game.match.MatchType;
|
||||
import mage.players.Player;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -67,22 +62,6 @@ public class FreeForAll extends GameImpl {
|
|||
this.numPlayers = numPlayers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getOpponents(UUID playerId) {
|
||||
Set<UUID> opponents = new HashSet<>();
|
||||
for (UUID opponentId: this.getPlayer(playerId).getInRange()) {
|
||||
if (!opponentId.equals(playerId)) {
|
||||
opponents.add(opponentId);
|
||||
}
|
||||
}
|
||||
return opponents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpponent(Player player, UUID playerToCheck) {
|
||||
return !player.getId().equals(playerToCheck) && player.getInRange().contains(playerToCheck);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FreeForAll copy() {
|
||||
return new FreeForAll(this);
|
||||
|
|
|
@ -27,8 +27,6 @@
|
|||
*/
|
||||
package mage.game;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
|
@ -83,22 +81,6 @@ public class MomirDuel extends GameImpl {
|
|||
state.getTurnMods().add(new TurnMod(startingPlayerId, PhaseStep.DRAW));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getOpponents(UUID playerId) {
|
||||
Set<UUID> opponents = new HashSet<>();
|
||||
for (UUID opponentId : this.getPlayer(playerId).getInRange()) {
|
||||
if (!opponentId.equals(playerId)) {
|
||||
opponents.add(opponentId);
|
||||
}
|
||||
}
|
||||
return opponents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpponent(Player player, UUID playerToCheck) {
|
||||
return !player.getId().equals(playerToCheck);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MomirDuel copy() {
|
||||
return new MomirDuel(this);
|
||||
|
|
|
@ -27,8 +27,6 @@
|
|||
*/
|
||||
package mage.game;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
|
@ -44,7 +42,6 @@ import mage.game.match.MatchType;
|
|||
import mage.game.turn.TurnMod;
|
||||
import mage.players.Player;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author nigelzor
|
||||
|
@ -86,22 +83,6 @@ public class MomirGame extends GameImpl {
|
|||
state.getTurnMods().add(new TurnMod(startingPlayerId, PhaseStep.DRAW));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getOpponents(UUID playerId) {
|
||||
Set<UUID> opponents = new HashSet<>();
|
||||
for (UUID opponentId : this.getPlayer(playerId).getInRange()) {
|
||||
if (!opponentId.equals(playerId)) {
|
||||
opponents.add(opponentId);
|
||||
}
|
||||
}
|
||||
return opponents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpponent(Player player, UUID playerToCheck) {
|
||||
return !player.getId().equals(playerToCheck) && player.getInRange().contains(playerToCheck);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MomirGame copy() {
|
||||
return new MomirGame(this);
|
||||
|
|
|
@ -25,18 +25,14 @@
|
|||
* authors and should not be interpreted as representing official policies, either expressed
|
||||
* or implied, of BetaSteward_at_googlemail.com.
|
||||
*/
|
||||
|
||||
package mage.game;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import mage.constants.MultiplayerAttackOption;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.RangeOfInfluence;
|
||||
import mage.game.match.MatchType;
|
||||
import mage.game.turn.TurnMod;
|
||||
import mage.players.Player;
|
||||
|
||||
public class TwoPlayerDuel extends GameImpl {
|
||||
|
||||
|
@ -64,22 +60,6 @@ public class TwoPlayerDuel extends GameImpl {
|
|||
state.getTurnMods().add(new TurnMod(startingPlayerId, PhaseStep.DRAW));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getOpponents(UUID playerId) {
|
||||
Set<UUID> opponents = new HashSet<>();
|
||||
for (UUID opponentId: this.getPlayer(playerId).getInRange()) {
|
||||
if (!opponentId.equals(playerId)) {
|
||||
opponents.add(opponentId);
|
||||
}
|
||||
}
|
||||
return opponents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpponent(Player player, UUID playerToCheck) {
|
||||
return !player.getId().equals(playerToCheck);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TwoPlayerDuel copy() {
|
||||
return new TwoPlayerDuel(this);
|
||||
|
|
|
@ -44,6 +44,7 @@ import mage.constants.SetTargetPointer;
|
|||
import mage.constants.Zone;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.filter.predicate.permanent.AnotherPredicate;
|
||||
import mage.filter.predicate.permanent.TokenPredicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
@ -58,6 +59,7 @@ public final class BrambleSovereign extends CardImpl {
|
|||
|
||||
static {
|
||||
filter.add(Predicates.not(new TokenPredicate()));
|
||||
filter.add(new AnotherPredicate());
|
||||
}
|
||||
|
||||
public BrambleSovereign(UUID ownerId, CardSetInfo setInfo) {
|
||||
|
@ -72,7 +74,7 @@ public final class BrambleSovereign extends CardImpl {
|
|||
Zone.BATTLEFIELD,
|
||||
new DoIfCostPaid(new BrambleSovereignEffect(), new ManaCostsImpl("{1}{G}")),
|
||||
filter, false, SetTargetPointer.PERMANENT,
|
||||
"Whenever a nontoken creature enters the battlefield, you may pay {1}{G}. "
|
||||
"Whenever another nontoken creature enters the battlefield, you may pay {1}{G}. "
|
||||
+ "If you do, that creature's controller creates a token that's a copy of that creature."
|
||||
));
|
||||
}
|
||||
|
|
|
@ -42,9 +42,9 @@ import mage.abilities.keyword.IndestructibleAbility;
|
|||
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.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.common.FilterControlledCreaturePermanent;
|
||||
import mage.filter.predicate.permanent.AnotherPredicate;
|
||||
|
@ -147,12 +147,14 @@ class DauntlessBodyguardGainAbilityEffect extends OneShotEffect {
|
|||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
MageObjectReference mor;
|
||||
Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId());
|
||||
if (sourcePermanent == null) {
|
||||
return false;
|
||||
}
|
||||
mor = (MageObjectReference) game.getState().getValue(sourcePermanent.getId() + "_chosenCreature");
|
||||
MageObjectReference mor = (MageObjectReference) game.getState().getValue(sourcePermanent.getId() + "_chosenCreature");
|
||||
if (mor == null) {
|
||||
return false;
|
||||
}
|
||||
Permanent chosenPermanent = mor.getPermanent(game);
|
||||
if (chosenPermanent != null) {
|
||||
ContinuousEffect effect = new GainAbilityTargetEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn);
|
||||
|
|
|
@ -44,7 +44,7 @@ import mage.choices.ChoiceColor;
|
|||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.common.FilterControlledCreaturePermanent;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.common.TargetControlledCreaturePermanent;
|
||||
|
@ -59,7 +59,8 @@ public final class FoodChain extends CardImpl {
|
|||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}");
|
||||
|
||||
// Exile a creature you control: Add X mana of any one color, where X is the exiled creature's converted mana cost plus one. Spend this mana only to cast creature spells.
|
||||
Ability ability = new SimpleManaAbility(Zone.BATTLEFIELD, new FoodChainManaEffect(), new ExileTargetCost(new TargetControlledCreaturePermanent(1, 1, new FilterControlledCreaturePermanent("a creature you control"), true)));
|
||||
Ability ability = new SimpleManaAbility(Zone.BATTLEFIELD, new FoodChainManaEffect(),
|
||||
new ExileTargetCost(new TargetControlledCreaturePermanent(1, 1, StaticFilters.FILTER_CONTROLLED_A_CREATURE, true)));
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
@ -41,9 +41,9 @@ import mage.cards.CardImpl;
|
|||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.AsThoughEffectType;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.game.ExileZone;
|
||||
|
@ -107,7 +107,7 @@ class KheruMindEaterExileEffect extends OneShotEffect {
|
|||
Card card = game.getCard(target.getFirstTarget());
|
||||
MageObject sourceObject = game.getObject(source.getSourceId());
|
||||
if (card != null && sourceObject != null) {
|
||||
if (player.moveCardToExileWithInfo(card, CardUtil.getCardExileZoneId(game, source), sourceObject.getIdName(), source.getSourceId(), game, Zone.HAND, true)) {
|
||||
if (player.moveCardsToExile(card, source, game, false, CardUtil.getCardExileZoneId(game, source), sourceObject.getIdName())) {
|
||||
card.setFaceDown(true, game);
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -34,13 +34,12 @@ import mage.abilities.Ability;
|
|||
import mage.abilities.DelayedTriggeredAbility;
|
||||
import mage.abilities.common.BeginningOfYourEndStepTriggeredAbility;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.effects.ContinuousEffect;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect;
|
||||
import mage.abilities.effects.common.GainLifeEffect;
|
||||
import mage.abilities.effects.common.LoseLifeTargetEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilitySourceEffect;
|
||||
import mage.abilities.keyword.HasteAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.CardImpl;
|
||||
|
@ -52,7 +51,6 @@ import mage.game.events.GameEvent.EventType;
|
|||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.common.TargetOpponent;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -121,6 +119,7 @@ class BeginningOfYourUpkeepdelayTriggeredAbility extends DelayedTriggeredAbility
|
|||
|
||||
public BeginningOfYourUpkeepdelayTriggeredAbility() {
|
||||
this(new ObzedatGhostCouncilReturnEffect(), TargetController.YOU);
|
||||
this.addEffect(new GainAbilitySourceEffect(HasteAbility.getInstance(), Duration.Custom));
|
||||
}
|
||||
|
||||
public BeginningOfYourUpkeepdelayTriggeredAbility(Effect effect, TargetController targetController) {
|
||||
|
@ -175,11 +174,8 @@ class ObzedatGhostCouncilReturnEffect extends OneShotEffect {
|
|||
// return it from every public zone - http://www.mtgsalvation.com/forums/magic-fundamentals/magic-rulings/magic-rulings-archives/513186-obzedat-gc-as-edh-commander
|
||||
if (zone != Zone.BATTLEFIELD && zone != Zone.LIBRARY && zone != Zone.HAND) {
|
||||
Player owner = game.getPlayer(card.getOwnerId());
|
||||
if (owner != null && owner.moveCards(card, Zone.BATTLEFIELD, source, game)) {
|
||||
Permanent permanent = game.getPermanent(card.getId());
|
||||
ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.WhileOnBattlefield);
|
||||
effect.setTargetPointer(new FixedTarget(permanent, game));
|
||||
game.addEffect(effect, source);
|
||||
if (owner != null) {
|
||||
owner.moveCards(card, Zone.BATTLEFIELD, source, game);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -40,13 +40,13 @@ import mage.abilities.effects.common.continuous.LoseAllAbilitiesTargetEffect;
|
|||
import mage.abilities.effects.common.continuous.SetPowerToughnessTargetEffect;
|
||||
import mage.abilities.effects.common.cost.SpellsCostReductionAllEffect;
|
||||
import mage.abilities.keyword.PartnerWithAbility;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.filter.predicate.mageobject.CardTypePredicate;
|
||||
|
@ -71,9 +71,7 @@ public final class WillKenrith extends CardImpl {
|
|||
// +2: Until your next turn, up to two target creatures each have base power and toughness 0/3 and lose all abilities.
|
||||
Ability ability = new LoyaltyAbility(
|
||||
new SetPowerToughnessTargetEffect(0, 3, Duration.UntilYourNextTurn)
|
||||
.setText("until your next turn, up to two target creatures each have base power and toughness 0/3"),
|
||||
2
|
||||
);
|
||||
.setText("until your next turn, up to two target creatures each have base power and toughness 0/3"), 2);
|
||||
ability.addEffect(new LoseAllAbilitiesTargetEffect(Duration.UntilYourNextTurn)
|
||||
.setText("and lose all abilities")
|
||||
);
|
||||
|
|
|
@ -58,7 +58,7 @@ public enum CardRepository {
|
|||
// raise this if db structure was changed
|
||||
private static final long CARD_DB_VERSION = 51;
|
||||
// raise this if new cards were added to the server
|
||||
private static final long CARD_CONTENT_VERSION = 111;
|
||||
private static final long CARD_CONTENT_VERSION = 112;
|
||||
private Dao<CardInfo, Object> cardDao;
|
||||
private Set<String> classNames;
|
||||
|
||||
|
|
|
@ -139,21 +139,37 @@ public interface Game extends MageItem, Serializable {
|
|||
PlayerList getPlayerList();
|
||||
|
||||
/**
|
||||
* Returns a Set of opponents in range for the given playerId
|
||||
* Returns a Set of opponents in range for the given playerId This return
|
||||
* also a player, that has dies this turn.
|
||||
*
|
||||
* @param playerId
|
||||
* @return
|
||||
*/
|
||||
Set<UUID> getOpponents(UUID playerId);
|
||||
default public Set<UUID> getOpponents(UUID playerId) {
|
||||
Set<UUID> opponents = new HashSet<>();
|
||||
Player player = getPlayer(playerId);
|
||||
for (UUID opponentId : player.getInRange()) {
|
||||
if (!opponentId.equals(playerId)) {
|
||||
opponents.add(opponentId);
|
||||
}
|
||||
}
|
||||
return opponents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given playerToCheckId is an opponent of player
|
||||
* Checks if the given playerToCheckId is an opponent of player As long as
|
||||
* no team formats are implemented, this method returns always true for each
|
||||
* playerId not equal to the player it is checked for. Also if this player
|
||||
* is out of range. This method can't handle that only players in range are
|
||||
* processed because it can only return TRUE or FALSE.
|
||||
*
|
||||
* @param player
|
||||
* @param playerToCheckId
|
||||
* @return
|
||||
*/
|
||||
boolean isOpponent(Player player, UUID playerToCheckId);
|
||||
default public boolean isOpponent(Player player, UUID playerToCheckId) {
|
||||
return !player.getId().equals(playerToCheckId);
|
||||
}
|
||||
|
||||
Turn getTurn();
|
||||
|
||||
|
|
|
@ -27,14 +27,13 @@
|
|||
*/
|
||||
package mage.game;
|
||||
|
||||
import java.util.*;
|
||||
import mage.constants.MultiplayerAttackOption;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.RangeOfInfluence;
|
||||
import mage.game.turn.TurnMod;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public abstract class GameCanadianHighlanderImpl extends GameImpl {
|
||||
|
||||
protected boolean startingPlayerSkipsDraw = true;
|
||||
|
@ -160,19 +159,4 @@ public abstract class GameCanadianHighlanderImpl extends GameImpl {
|
|||
super.endMulligan(playerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getOpponents(UUID playerId) {
|
||||
Set<UUID> opponents = new HashSet<>();
|
||||
for (UUID opponentId : getState().getPlayersInRange(playerId, this)) {
|
||||
if (!opponentId.equals(playerId)) {
|
||||
opponents.add(opponentId);
|
||||
}
|
||||
}
|
||||
return opponents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpponent(Player player, UUID playerToCheck) {
|
||||
return !player.getId().equals(playerToCheck);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,9 +27,7 @@
|
|||
*/
|
||||
package mage.game;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
|
@ -200,22 +198,6 @@ public abstract class GameCommanderImpl extends GameImpl {
|
|||
return super.checkStateBasedActions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getOpponents(UUID playerId) {
|
||||
Set<UUID> opponents = new HashSet<>();
|
||||
for (UUID opponentId : getState().getPlayersInRange(playerId, this)) {
|
||||
if (!opponentId.equals(playerId)) {
|
||||
opponents.add(opponentId);
|
||||
}
|
||||
}
|
||||
return opponents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpponent(Player player, UUID playerToCheck) {
|
||||
return !player.getId().equals(playerToCheck);
|
||||
}
|
||||
|
||||
public void setAlsoHand(boolean alsoHand) {
|
||||
this.alsoHand = alsoHand;
|
||||
}
|
||||
|
|
|
@ -132,22 +132,6 @@ public abstract class GameTinyLeadersImpl extends GameImpl {
|
|||
return commander;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getOpponents(UUID playerId) {
|
||||
Set<UUID> opponents = new HashSet<>();
|
||||
for (UUID opponentId : getState().getPlayersInRange(playerId, this)) {
|
||||
if (!opponentId.equals(playerId)) {
|
||||
opponents.add(opponentId);
|
||||
}
|
||||
}
|
||||
return opponents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpponent(Player player, UUID playerToCheck) {
|
||||
return !player.getId().equals(playerToCheck);
|
||||
}
|
||||
|
||||
public void setAlsoHand(boolean alsoHand) {
|
||||
this.alsoHand = alsoHand;
|
||||
}
|
||||
|
|
|
@ -795,6 +795,7 @@ public interface Player extends MageItem, Copyable<Player> {
|
|||
* @param withName
|
||||
* @return
|
||||
*/
|
||||
@Deprecated
|
||||
boolean moveCardToExileWithInfo(Card card, UUID exileId, String exileName, UUID sourceId, Game game, Zone fromZone, boolean withName);
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
[![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) [![Build Status](https://travis-ci.org/magefree/mage.svg?branch=master)](https://travis-ci.org/magefree/mage)
|
||||
|
||||
XMage allows you to play Magic against one or more online players or computer opponents. It includes full rules enforcement for over **17.200** unique cards (over 33.100 counting all cards from different editions). Starting with *Morningtide*, all regular sets have nearly all the cards implemented.
|
||||
XMage allows you to play Magic against one or more online players or computer opponents. It includes full rules enforcement for over **17.200** unique cards (over 33.100 counting all cards from different editions). Starting with *Morningtide*, all regular sets have nearly all the cards implemented. A more detailed information which cards are implemented can be found [here](https://github.com/magefree/mage/wiki/Set-implementation-list).
|
||||
|
||||
There are public servers where you can play XMage against other players. You can also host your own server to play against the AI and/or your friends.
|
||||
|
||||
|
@ -22,6 +22,10 @@ XMage community:
|
|||
* Booster (also Cube) draft tournaments (4-16)
|
||||
* Sealed (also from Cube) tournaments (2-16)
|
||||
|
||||
## Issues / bugs
|
||||
|
||||
Before you create a new issue, take a look at the [List of things already fixed but not yet released](https://github.com/magefree/mage/wiki/Features-and-fixes-not-released-yet#features-and-fixes-not-released-yet) to avoid creating uneccessary new issues.
|
||||
Also there is always a bug thread in the [Official XMage forum](http://www.slightlymagic.net/forum/viewforum.php?f=70) which we check regularly.
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
Loading…
Reference in a new issue