Implemented Yorion, Sky Nomad (#6407)

* Implemented Yorion, Sky Nomad

* Implemented Yorion, Sky Nomad (but for real this time)

* updated game creation to use the correct deck size for limited
This commit is contained in:
Evan Kranzler 2020-04-16 08:10:18 -04:00 committed by GitHub
parent 378dfbf89a
commit 8494e98693
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 185 additions and 37 deletions

View file

@ -400,7 +400,7 @@ class TestGame extends GameImpl {
private int numPlayers;
public TestGame(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
super(attackOption, range, mulligan, startLife);
super(attackOption, range, mulligan, startLife, 60);
}
public TestGame(final TestGame game) {

View file

@ -151,7 +151,7 @@ public class Brawl extends Constructed {
for (Ability ability : companion.getAbilities()) {
if (ability instanceof CompanionAbility) {
CompanionAbility companionAbility = (CompanionAbility) ability;
if (!companionAbility.isLegal(cards)) {
if (!companionAbility.isLegal(cards, getDeckMinSize())) {
invalid.put(companion.getName(), "Deck invalid for companion");
valid = false;
}

View file

@ -234,7 +234,7 @@ public class Commander extends Constructed {
for (Ability ability : companion.getAbilities()) {
if (ability instanceof CompanionAbility) {
CompanionAbility companionAbility = (CompanionAbility) ability;
if (!companionAbility.isLegal(cards)) {
if (!companionAbility.isLegal(cards, getDeckMinSize())) {
invalid.put(companion.getName(), "Deck invalid for companion");
valid = false;
}

View file

@ -158,7 +158,7 @@ public class FreeformCommander extends Constructed {
for (Ability ability : companion.getAbilities()) {
if (ability instanceof CompanionAbility) {
CompanionAbility companionAbility = (CompanionAbility) ability;
if (!companionAbility.isLegal(cards)) {
if (!companionAbility.isLegal(cards, getDeckMinSize())) {
invalid.put(companion.getName(), "Deck invalid for companion");
valid = false;
}

View file

@ -189,7 +189,7 @@ public class PennyDreadfulCommander extends Constructed {
for (Ability ability : companion.getAbilities()) {
if (ability instanceof CompanionAbility) {
CompanionAbility companionAbility = (CompanionAbility) ability;
if (!companionAbility.isLegal(cards)) {
if (!companionAbility.isLegal(cards, getDeckMinSize())) {
invalid.put(companion.getName(), "Deck invalid for companion");
valid = false;
}

View file

@ -10,7 +10,7 @@ import mage.game.mulligan.Mulligan;
public class BrawlDuel extends GameCommanderImpl {
public BrawlDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
super(attackOption, range, mulligan, startLife);
super(attackOption, range, mulligan, startLife, 60);
}
public BrawlDuel(final BrawlDuel game) {

View file

@ -17,7 +17,7 @@ public class BrawlFreeForAll extends GameCommanderImpl {
private int numPlayers;
public BrawlFreeForAll(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
super(attackOption, range, mulligan, startLife);
super(attackOption, range, mulligan, startLife, 60);
}
public BrawlFreeForAll(final BrawlFreeForAll game) {

View file

@ -10,7 +10,7 @@ import mage.game.mulligan.Mulligan;
public class CommanderDuel extends GameCommanderImpl {
public CommanderDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
super(attackOption, range, mulligan, startLife);
super(attackOption, range, mulligan, startLife, 100);
}
public CommanderDuel(final CommanderDuel game) {

View file

@ -2,14 +2,14 @@
package mage.game;
import java.util.UUID;
import mage.constants.MultiplayerAttackOption;
import mage.constants.RangeOfInfluence;
import mage.game.match.MatchType;
import mage.game.mulligan.Mulligan;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public class CommanderFreeForAll extends GameCommanderImpl {
@ -17,7 +17,7 @@ public class CommanderFreeForAll extends GameCommanderImpl {
private int numPlayers;
public CommanderFreeForAll(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
super(attackOption, range, mulligan, startLife);
super(attackOption, range, mulligan, startLife, 100);
}
public CommanderFreeForAll(final CommanderFreeForAll game) {

View file

@ -15,7 +15,7 @@ public class FreeForAll extends GameImpl {
private int numPlayers;
public FreeForAll(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
super(attackOption, range, mulligan, startLife);
super(attackOption, range, mulligan, startLife, 60);
}
public FreeForAll(final FreeForAll game) {

View file

@ -11,7 +11,7 @@ import mage.game.mulligan.Mulligan;
public class FreeformCommanderDuel extends GameCommanderImpl {
public FreeformCommanderDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
super(attackOption, range, mulligan, startLife);
super(attackOption, range, mulligan, startLife, 60);
}
public FreeformCommanderDuel(final FreeformCommanderDuel game) {

View file

@ -17,7 +17,7 @@ public class FreeformCommanderFreeForAll extends GameCommanderImpl {
private int numPlayers;
public FreeformCommanderFreeForAll(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
super(attackOption, range, mulligan, startLife);
super(attackOption, range, mulligan, startLife, 60);
}
public FreeformCommanderFreeForAll(final FreeformCommanderFreeForAll game) {

View file

@ -24,7 +24,7 @@ import mage.players.Player;
public class MomirDuel extends GameImpl {
public MomirDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
super(attackOption, range, mulligan, startLife);
super(attackOption, range, mulligan, startLife, 60);
}
public MomirDuel(final MomirDuel game) {

View file

@ -26,7 +26,7 @@ public class MomirGame extends GameImpl {
private int numPlayers;
public MomirGame(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
super(attackOption, range, mulligan, startLife);
super(attackOption, range, mulligan, startLife, 60);
}
public MomirGame(final MomirGame game) {

View file

@ -32,7 +32,7 @@ public class OathbreakerFreeForAll extends GameCommanderImpl {
private static final String COMMANDER_NAME_SIGNATURE_SPELL = "Signature Spell";
public OathbreakerFreeForAll(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
super(attackOption, range, mulligan, startLife);
super(attackOption, range, mulligan, startLife, 100);
this.startingPlayerSkipsDraw = false;
}

View file

@ -17,7 +17,7 @@ public class PennyDreadfulCommanderFreeForAll extends GameCommanderImpl {
private int numPlayers;
public PennyDreadfulCommanderFreeForAll(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
super(attackOption, range, mulligan, startLife);
super(attackOption, range, mulligan, startLife, 60);
}
public PennyDreadfulCommanderFreeForAll(final PennyDreadfulCommanderFreeForAll game) {

View file

@ -1,4 +1,3 @@
package mage.game;
import mage.constants.MultiplayerAttackOption;
@ -13,7 +12,11 @@ import java.util.UUID;
public class TwoPlayerDuel extends GameImpl {
public TwoPlayerDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
super(attackOption, range, mulligan, startLife);
this(attackOption, range, mulligan, startLife, 60);
}
public TwoPlayerDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife, int startingSize) {
super(attackOption, range, mulligan, startLife, startingSize);
}
public TwoPlayerDuel(final TwoPlayerDuel game) {

View file

@ -6,7 +6,6 @@ import mage.game.match.MatchOptions;
import mage.game.mulligan.Mulligan;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class TwoPlayerMatch extends MatchImpl {
@ -18,7 +17,7 @@ public class TwoPlayerMatch extends MatchImpl {
@Override
public void startGame() throws GameException {
Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans());
TwoPlayerDuel game = new TwoPlayerDuel(options.getAttackOption(), options.getRange(), mulligan, 20);
TwoPlayerDuel game = new TwoPlayerDuel(options.getAttackOption(), options.getRange(), mulligan, 20, options.isLimited() ? 40 : 60);
// Sets a start message about the match score
game.setStartMessage(this.createGameStartMessage());
initGame(game);

View file

@ -96,7 +96,7 @@ enum KaheeraTheOrphanguardCompanionCondition implements CompanionCondition {
}
@Override
public boolean isLegal(Set<Card> deck) {
public boolean isLegal(Set<Card> deck, int startingSize) {
return deck.stream()
.filter(MageObject::isCreature)
.allMatch(KaheeraTheOrphanguardCompanionCondition::checkTypes);

View file

@ -66,7 +66,7 @@ enum KerugaCondition implements CompanionCondition {
}
@Override
public boolean isLegal(Set<Card> deck) {
public boolean isLegal(Set<Card> deck, int startingSize) {
return deck.stream().allMatch(card -> card.isLand() || card.getConvertedManaCost() >= 3);
}
}

View file

@ -83,7 +83,7 @@ enum LutriTheSpellchaserCompanionCondition implements CompanionCondition {
}
@Override
public boolean isLegal(Set<Card> deck) {
public boolean isLegal(Set<Card> deck, int startingSize) {
Map<String, Integer> cardMap = new HashMap();
deck.stream()
.filter(card -> !card.isLand())

View file

@ -58,7 +58,7 @@ enum OboshThePreypiercerCompanionCondition implements CompanionCondition {
}
@Override
public boolean isLegal(Set<Card> deck) {
public boolean isLegal(Set<Card> deck, int startingSize) {
return deck
.stream()
.filter(card -> !card.isLand())

View file

@ -64,7 +64,7 @@ enum UmoriCondition implements CompanionCondition {
}
@Override
public boolean isLegal(Set<Card> deck) {
public boolean isLegal(Set<Card> deck, int startingSize) {
Set<CardType> cardTypes = new HashSet<>();
for (Card card : deck) {
// Lands are fine.

View file

@ -0,0 +1,141 @@
package mage.cards.y;
import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.ReturnToBattlefieldUnderOwnerControlTargetEffect;
import mage.abilities.keyword.CompanionAbility;
import mage.abilities.keyword.CompanionCondition;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.*;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.permanent.AnotherPredicate;
import mage.game.Game;
import mage.game.permanent.PermanentCard;
import mage.game.permanent.PermanentMeld;
import mage.players.Player;
import mage.target.TargetPermanent;
import mage.target.targetpointer.FixedTargets;
import mage.util.CardUtil;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* @author TheElk801
*/
public final class YorionSkyNomad extends CardImpl {
public YorionSkyNomad(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W/U}{W/U}");
this.addSuperType(SuperType.LEGENDARY);
this.subtype.add(SubType.BIRD);
this.subtype.add(SubType.SERPENT);
this.power = new MageInt(4);
this.toughness = new MageInt(5);
// Companion Your starting deck contains at least twenty cards more than the minimum deck size.
this.addAbility(new CompanionAbility(YorionSkyNomadCompanionCondition.instance));
// Flying
this.addAbility(FlyingAbility.getInstance());
// When Yorion enters the battlefield, exile any number of other nonland permanents you own and control. Return those cards to the battlefield at the beginning of the next end step.
this.addAbility(new EntersBattlefieldTriggeredAbility(new YorionSkyNomadEffect()));
}
private YorionSkyNomad(final YorionSkyNomad card) {
super(card);
}
@Override
public YorionSkyNomad copy() {
return new YorionSkyNomad(this);
}
}
enum YorionSkyNomadCompanionCondition implements CompanionCondition {
instance;
@Override
public String getRule() {
return "Your starting deck contains at least twenty cards more than the minimum deck size.";
}
@Override
public boolean isLegal(Set<Card> deck, int startingSize) {
return deck.size() >= startingSize + 20;
}
}
class YorionSkyNomadEffect extends OneShotEffect {
private static final FilterPermanent filter
= new FilterControlledPermanent("other nonland permanents you own and control");
static {
filter.add(Predicates.not(CardType.LAND.getPredicate()));
filter.add(TargetController.YOU.getOwnerPredicate());
filter.add(AnotherPredicate.instance);
}
YorionSkyNomadEffect() {
super(Outcome.Benefit);
staticText = "exile any number of other nonland permanents you own and control. " +
"Return those cards to the battlefield at the beginning of the next end step.";
}
private YorionSkyNomadEffect(final YorionSkyNomadEffect effect) {
super(effect);
}
@Override
public YorionSkyNomadEffect copy() {
return new YorionSkyNomadEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
MageObject sourceObject = source.getSourceObject(game);
if (sourceObject == null || controller == null) {
return false;
}
TargetPermanent target = new TargetPermanent(0, Integer.MAX_VALUE, filter, true);
controller.choose(outcome, target, source.getSourceId(), game);
Set<Card> toExile = target.getTargets().stream().map(game::getPermanent).collect(Collectors.toSet());
UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter());
controller.moveCardsToExile(toExile, source, game, true, exileId, sourceObject.getIdName());
Cards cardsToReturn = new CardsImpl();
for (Card exiled : toExile) {
if (exiled instanceof PermanentMeld) {
MeldCard meldCard = (MeldCard) ((PermanentCard) exiled).getCard();
Card topCard = meldCard.getTopHalfCard();
Card bottomCard = meldCard.getBottomHalfCard();
if (topCard.getZoneChangeCounter(game) == meldCard.getTopLastZoneChangeCounter()) {
cardsToReturn.add(topCard);
}
if (bottomCard.getZoneChangeCounter(game) == meldCard.getBottomLastZoneChangeCounter()) {
cardsToReturn.add(bottomCard);
}
} else if (exiled.getZoneChangeCounter(game) == game.getState().getZoneChangeCounter(exiled.getId()) - 1) {
cardsToReturn.add(exiled);
}
}
Effect effect = new ReturnToBattlefieldUnderOwnerControlTargetEffect();
effect.setTargetPointer(new FixedTargets(cardsToReturn, game));
AtTheBeginOfNextEndStepDelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect);
game.addDelayedTriggeredAbility(delayedAbility, source);
return true;
}
}

View file

@ -324,6 +324,7 @@ public final class IkoriaLairOfBehemoths extends ExpansionSet {
cards.add(new SetCardInfo("Wind-Scarred Crag", 258, Rarity.COMMON, mage.cards.w.WindScarredCrag.class));
cards.add(new SetCardInfo("Wingfold Pteron", 71, Rarity.COMMON, mage.cards.w.WingfoldPteron.class));
cards.add(new SetCardInfo("Wingspan Mentor", 72, Rarity.UNCOMMON, mage.cards.w.WingspanMentor.class));
cards.add(new SetCardInfo("Yorion, Sky Nomad", 232, Rarity.RARE, mage.cards.y.YorionSkyNomad.class));
cards.add(new SetCardInfo("Winota, Joiner of Forces", 216, Rarity.MYTHIC, mage.cards.w.WinotaJoinerOfForces.class));
cards.add(new SetCardInfo("Yidaro, Wandering Monster", 141, Rarity.RARE, mage.cards.y.YidaroWanderingMonster.class));
cards.add(new SetCardInfo("Zagoth Crystal", 242, Rarity.UNCOMMON, mage.cards.z.ZagothCrystal.class));

View file

@ -34,8 +34,8 @@ public class CompanionAbility extends StaticAbility {
return "Companion &mdash; " + condition.getRule();
}
public boolean isLegal(Set<Card> cards) {
return condition.isLegal(cards);
public boolean isLegal(Set<Card> cards, int startingSize) {
return condition.isLegal(cards, startingSize);
}
}

View file

@ -17,7 +17,8 @@ public interface CompanionCondition extends Serializable {
/**
* @param deck The set of cards to check.
* @param startingSize
* @return Whether the companion is valid for that deck.
*/
boolean isLegal(Set<Card> deck);
boolean isLegal(Set<Card> deck, int startingSize);
}

View file

@ -12,7 +12,7 @@ import java.util.UUID;
public abstract class GameCanadianHighlanderImpl extends GameImpl {
public GameCanadianHighlanderImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
super(attackOption, range, mulligan, startLife);
super(attackOption, range, mulligan, startLife, 100);
}
public GameCanadianHighlanderImpl(final GameCanadianHighlanderImpl game) {

View file

@ -31,8 +31,8 @@ public abstract class GameCommanderImpl extends GameImpl {
protected boolean startingPlayerSkipsDraw = true;
public GameCommanderImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
super(attackOption, range, mulligan, startLife);
public GameCommanderImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife, int startingSize) {
super(attackOption, range, mulligan, startLife, startingSize);
}
public GameCommanderImpl(final GameCommanderImpl game) {

View file

@ -130,6 +130,7 @@ public abstract class GameImpl implements Game, Serializable {
private int priorityTime;
private final int startLife;
private final int startingSize;
protected PlayerList playerList; // auto-generated from state, don't copy
// infinite loop check (no copy of this attributes neccessary)
@ -144,7 +145,7 @@ public abstract class GameImpl implements Game, Serializable {
// temporary store for income concede commands, don't copy
private final LinkedList<UUID> concedingPlayers = new LinkedList<>();
public GameImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
public GameImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife, int startingSize) {
this.id = UUID.randomUUID();
this.range = range;
this.mulligan = mulligan;
@ -152,6 +153,7 @@ public abstract class GameImpl implements Game, Serializable {
this.state = new GameState();
this.startLife = startLife;
this.executingRollback = false;
this.startingSize = startingSize;
initGameDefaultWatchers();
}
@ -181,6 +183,7 @@ public abstract class GameImpl implements Game, Serializable {
this.saveGame = game.saveGame;
this.startLife = game.startLife;
this.enterWithCounters.putAll(game.enterWithCounters);
this.startingSize = game.startingSize;
}
@Override
@ -939,7 +942,7 @@ public abstract class GameImpl implements Game, Serializable {
for (Ability ability : card.getAbilities(this)) {
if (ability instanceof CompanionAbility) {
CompanionAbility companionAbility = (CompanionAbility) ability;
if (companionAbility.isLegal(new HashSet<>(player.getLibrary().getCards(this)))) {
if (companionAbility.isLegal(new HashSet<>(player.getLibrary().getCards(this)), startingSize)) {
potentialCompanions.add(card);
break;
}

View file

@ -32,7 +32,7 @@ public abstract class GameTinyLeadersImpl extends GameImpl {
protected boolean startingPlayerSkipsDraw = true;
public GameTinyLeadersImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
super(attackOption, range, mulligan, startLife);
super(attackOption, range, mulligan, startLife, 50);
}
public GameTinyLeadersImpl(final GameTinyLeadersImpl game) {