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 da125caed0..b700d8363f 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 @@ -2571,7 +2571,7 @@ public class TestPlayer implements Player { // library if (target.getOriginalTarget() instanceof TargetCardInLibrary - || (target.getOriginalTarget() instanceof TargetCard && target.getOriginalTarget().getZone() == Zone.LIBRARY)) { + || (target.getOriginalTarget() instanceof TargetCard && target.getOriginalTarget().getZone() == Zone.LIBRARY)) { // user don't have access to library, so it must be targeted through list/revealed cards Assert.fail("Library zone is private, you must target through cards list, e.g. revealed: " + target.getOriginalTarget().getClass().getCanonicalName()); } @@ -3714,6 +3714,16 @@ public class TestPlayer implements Player { return computerPlayer.canPlayCardsFromGraveyard(); } + @Override + public void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn) { + computerPlayer.setDrawsOnOpponentsTurn(drawsOnOpponentsTurn); + } + + @Override + public boolean isDrawsOnOpponentsTurn() { + return computerPlayer.isDrawsOnOpponentsTurn(); + } + @Override public void setPayManaMode(boolean payManaMode) { computerPlayer.setPayManaMode(payManaMode); diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java index 8a408f0c73..91a4b116bd 100644 --- a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java +++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java @@ -236,6 +236,16 @@ public class PlayerStub implements Player { return false; } + @Override + public void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn) { + + } + + @Override + public boolean isDrawsOnOpponentsTurn() { + return false; + } + @Override public List getAlternativeSourceCosts() { return null; diff --git a/Mage/src/main/java/mage/filter/StaticFilters.java b/Mage/src/main/java/mage/filter/StaticFilters.java index 2ee42a1a61..4b66c81727 100644 --- a/Mage/src/main/java/mage/filter/StaticFilters.java +++ b/Mage/src/main/java/mage/filter/StaticFilters.java @@ -317,6 +317,12 @@ public final class StaticFilters { FILTER_CONTROLLED_A_PERMANENT.setLockedFilter(true); } + public static final FilterControlledPermanent FILTER_CONTROLLED_PERMANENTS = new FilterControlledPermanent("permanents you control"); + + static { + FILTER_CONTROLLED_PERMANENTS.setLockedFilter(true); + } + public static final FilterControlledPermanent FILTER_CONTROLLED_PERMANENT_SHORT_TEXT = new FilterControlledPermanent("permanent"); static { diff --git a/Mage/src/main/java/mage/game/command/emblems/TeferiWhoSlowsTheSunsetEmblem.java b/Mage/src/main/java/mage/game/command/emblems/TeferiWhoSlowsTheSunsetEmblem.java index 99ba626e57..f2d3bd7981 100644 --- a/Mage/src/main/java/mage/game/command/emblems/TeferiWhoSlowsTheSunsetEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/TeferiWhoSlowsTheSunsetEmblem.java @@ -1,19 +1,49 @@ package mage.game.command.emblems; -import mage.abilities.common.BeginningOfDrawTriggeredAbility; +import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.continuous.UntapAllDuringEachOtherPlayersUntapStepEffect; -import mage.constants.TargetController; -import mage.constants.Zone; -import mage.filter.common.FilterControlledPermanent; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; import mage.game.command.Emblem; +import mage.players.Player; public class TeferiWhoSlowsTheSunsetEmblem extends Emblem { // You get an emblem with "Untap all permanents you control during each opponent's untap step" and "You draw a card during each opponent's draw step." public TeferiWhoSlowsTheSunsetEmblem() { this.setName("Emblem Teferi"); - this.getAbilities().add(new SimpleStaticAbility(Zone.COMMAND, new UntapAllDuringEachOtherPlayersUntapStepEffect(new FilterControlledPermanent("permanents you control")))); - this.getAbilities().add(new BeginningOfDrawTriggeredAbility(Zone.COMMAND, new DrawCardSourceControllerEffect(1), TargetController.OPPONENT, false)); + this.getAbilities().add(new SimpleStaticAbility( + Zone.COMMAND, new UntapAllDuringEachOtherPlayersUntapStepEffect(StaticFilters.FILTER_CONTROLLED_PERMANENTS) + )); + this.getAbilities().add(new SimpleStaticAbility(new TeferiWhoSlowsTheSunsetEmblemEffect())); + } +} + +class TeferiWhoSlowsTheSunsetEmblemEffect extends ContinuousEffectImpl { + + TeferiWhoSlowsTheSunsetEmblemEffect() { + super(Duration.EndOfGame, Layer.RulesEffects, SubLayer.NA, Outcome.Benefit); + staticText = "you draw a card during each opponent's draw step"; + } + + private TeferiWhoSlowsTheSunsetEmblemEffect(final TeferiWhoSlowsTheSunsetEmblemEffect effect) { + super(effect); + } + + @Override + public TeferiWhoSlowsTheSunsetEmblemEffect copy() { + return new TeferiWhoSlowsTheSunsetEmblemEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + player.setDrawsOnOpponentsTurn(true); + return true; } } diff --git a/Mage/src/main/java/mage/game/turn/DrawStep.java b/Mage/src/main/java/mage/game/turn/DrawStep.java index ffa7d26f3b..df07bf4c85 100644 --- a/Mage/src/main/java/mage/game/turn/DrawStep.java +++ b/Mage/src/main/java/mage/game/turn/DrawStep.java @@ -29,6 +29,14 @@ public class DrawStep extends Step { //20091005 - 504.1/703.4c activePlayer.drawCards(1, null, game); game.applyEffects(); + for (UUID playerId : game.getState().getPlayersInRange(activePlayerId, game)) { + Player player = game.getPlayer(playerId); + if (player != null + && player.isDrawsOnOpponentsTurn() + && player.hasOpponent(activePlayerId, game)) { + player.drawCards(1, null, game); + } + } super.beginStep(game, activePlayerId); } diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index abd1239872..1a1f9111da 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -178,6 +178,10 @@ public interface Player extends MageItem, Copyable { boolean canPlayCardsFromGraveyard(); + void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn); + + boolean isDrawsOnOpponentsTurn(); + /** * Returns alternative casting costs a player can cast spells for * diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index d5bfc7ef5b..bc5662a357 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -138,6 +138,7 @@ public abstract class PlayerImpl implements Player, Serializable { protected boolean canPayLifeCost = true; protected boolean loseByZeroOrLessLife = true; protected boolean canPlayCardsFromGraveyard = true; + protected boolean drawsOnOpponentsTurn = false; protected FilterPermanent sacrificeCostFilter; @@ -239,6 +240,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.canLoseLife = player.canLoseLife; this.loseByZeroOrLessLife = player.loseByZeroOrLessLife; this.canPlayCardsFromGraveyard = player.canPlayCardsFromGraveyard; + this.drawsOnOpponentsTurn = player.drawsOnOpponentsTurn; this.attachments.addAll(player.attachments); @@ -347,6 +349,7 @@ public abstract class PlayerImpl implements Player, Serializable { ? player.getSacrificeCostFilter().copy() : null; this.loseByZeroOrLessLife = player.canLoseByZeroOrLessLife(); this.canPlayCardsFromGraveyard = player.canPlayCardsFromGraveyard(); + this.drawsOnOpponentsTurn = player.isDrawsOnOpponentsTurn(); this.alternativeSourceCosts.clear(); this.alternativeSourceCosts.addAll(player.getAlternativeSourceCosts()); @@ -470,6 +473,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.sacrificeCostFilter = null; this.loseByZeroOrLessLife = true; this.canPlayCardsFromGraveyard = false; + this.drawsOnOpponentsTurn = false; this.topCardRevealed = false; this.alternativeSourceCosts.clear(); this.clearCastSourceIdManaCosts(); @@ -637,9 +641,9 @@ public abstract class PlayerImpl implements Player, Serializable { && this.hasOpponent(sourceControllerId, game) && game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) == null && abilities.stream() - .filter(HexproofBaseAbility.class::isInstance) - .map(HexproofBaseAbility.class::cast) - .anyMatch(ability -> ability.checkObject(source, game))) { + .filter(HexproofBaseAbility.class::isInstance) + .map(HexproofBaseAbility.class::cast) + .anyMatch(ability -> ability.checkObject(source, game))) { return false; } @@ -679,7 +683,7 @@ public abstract class PlayerImpl implements Player, Serializable { game.informPlayers(getLogName() + " discards down to " + this.maxHandSize + (this.maxHandSize == 1 - ? " hand card" : " hand cards")); + ? " hand card" : " hand cards")); } discard(hand.size() - this.maxHandSize, false, false, null, game); } @@ -1152,7 +1156,7 @@ public abstract class PlayerImpl implements Player, Serializable { /** * @param originalAbility * @param game - * @param noMana cast it without paying mana costs + * @param noMana cast it without paying mana costs * @param approvingObject which object approved the cast * @return */ @@ -2914,7 +2918,7 @@ public abstract class PlayerImpl implements Player, Serializable { * @return */ private Object rollDieInner(Outcome outcome, Game game, Ability source, RollDieType rollDieType, - int sidesAmount, int chaosSidesAmount, int planarSidesAmount, int rollsAmount) { + int sidesAmount, int chaosSidesAmount, int planarSidesAmount, int rollsAmount) { if (rollsAmount == 1) { return rollDieInnerWithReplacement(game, source, rollDieType, sidesAmount, chaosSidesAmount, planarSidesAmount); } @@ -3010,8 +3014,8 @@ public abstract class PlayerImpl implements Player, Serializable { * @param outcome * @param source * @param game - * @param sidesAmount number of sides the dice has - * @param rollsAmount number of tries to roll the dice + * @param sidesAmount number of sides the dice has + * @param rollsAmount number of tries to roll the dice * @param ignoreLowestAmount remove the lowest rolls from the results * @return the number that the player rolled */ @@ -3029,18 +3033,18 @@ public abstract class PlayerImpl implements Player, Serializable { * @param outcome * @param source * @param game - * @param rollDieType die type to roll, e.g. planar or numerical - * @param sidesAmount sides per die - * @param chaosSidesAmount for planar die: chaos sides - * @param planarSidesAmount for planar die: planar sides - * @param rollsAmount rolls + * @param rollDieType die type to roll, e.g. planar or numerical + * @param sidesAmount sides per die + * @param chaosSidesAmount for planar die: chaos sides + * @param planarSidesAmount for planar die: planar sides + * @param rollsAmount rolls * @param ignoreLowestAmount for numerical die: ignore multiple rolls with - * the lowest values + * the lowest values * @return */ private List rollDiceInner(Outcome outcome, Ability source, Game game, RollDieType rollDieType, - int sidesAmount, int chaosSidesAmount, int planarSidesAmount, - int rollsAmount, int ignoreLowestAmount) { + int sidesAmount, int chaosSidesAmount, int planarSidesAmount, + int rollsAmount, int ignoreLowestAmount) { RollDiceEvent rollDiceEvent = new RollDiceEvent(source, rollDieType, sidesAmount, rollsAmount); if (ignoreLowestAmount > 0) { rollDiceEvent.incIgnoreLowestAmount(ignoreLowestAmount); @@ -3200,10 +3204,10 @@ public abstract class PlayerImpl implements Player, Serializable { /** * @param source * @param game - * @param chaosSidesAmount The number of chaos sides the planar die - * currently has (normally 1 but can be 5) + * @param chaosSidesAmount The number of chaos sides the planar die + * currently has (normally 1 but can be 5) * @param planarSidesAmount The number of chaos sides the planar die - * currently has (normally 1) + * currently has (normally 1) * @return the outcome that the player rolled. Either ChaosRoll, PlanarRoll * or BlankRoll */ @@ -3261,7 +3265,7 @@ public abstract class PlayerImpl implements Player, Serializable { for (Card card : getHand().getCards(game)) { Abilities manaAbilities = card.getAbilities(game).getAvailableActivatedManaAbilities(Zone.HAND, playerId, game); - for (Iterator it = manaAbilities.iterator(); it.hasNext();) { + for (Iterator it = manaAbilities.iterator(); it.hasNext(); ) { ActivatedManaAbilityImpl ability = it.next(); Abilities noTapAbilities = new AbilitiesImpl<>(ability); if (ability.getManaCosts().isEmpty() && !ability.isPoolDependant()) { @@ -3278,7 +3282,7 @@ public abstract class PlayerImpl implements Player, Serializable { boolean useLater = false; // sources with mana costs or mana pool dependency Abilities manaAbilities = permanent.getAbilities(game).getAvailableActivatedManaAbilities(Zone.BATTLEFIELD, playerId, game); // returns ability only if canActivate is true - for (Iterator it = manaAbilities.iterator(); it.hasNext();) { + for (Iterator it = manaAbilities.iterator(); it.hasNext(); ) { ActivatedManaAbilityImpl ability = it.next(); if (canUse == null) { canUse = permanent.canUseActivatedAbilities(game); @@ -3320,7 +3324,7 @@ public abstract class PlayerImpl implements Player, Serializable { boolean usePoolDependantAbilities = false; // use such abilities later than other if possible because it can maximize mana production while (anAbilityWasUsed && !sourceWithCosts.isEmpty()) { anAbilityWasUsed = false; - for (Iterator> iterator = sourceWithCosts.iterator(); iterator.hasNext();) { + for (Iterator> iterator = sourceWithCosts.iterator(); iterator.hasNext(); ) { Abilities manaAbilities = iterator.next(); if (usePoolDependantAbilities || !manaAbilities.hasPoolDependantAbilities()) { boolean used; @@ -3356,7 +3360,7 @@ public abstract class PlayerImpl implements Player, Serializable { * and cleared thereafter * * @param netManaAvailable the net mana produced by the triggered mana - * abaility + * abaility */ @Override public void addAvailableTriggeredMana(List netManaAvailable @@ -3438,7 +3442,7 @@ public abstract class PlayerImpl implements Player, Serializable { /** * @param ability * @param availableMana if null, it won't be checked if enough mana is - * available + * available * @param sourceObject * @param game * @return @@ -3873,10 +3877,10 @@ public abstract class PlayerImpl implements Player, Serializable { * currently cast/activate with his available resources * * @param game - * @param hidden also from hidden objects (e.g. turned face down cards ?) - * @param fromZone of objects from which zone (ALL = from all zones) + * @param hidden also from hidden objects (e.g. turned face down cards ?) + * @param fromZone of objects from which zone (ALL = from all zones) * @param hideDuplicatedAbilities if equal abilities exist return only the - * first instance + * first instance * @return */ public List getPlayable(Game game, boolean hidden, Zone fromZone, boolean hideDuplicatedAbilities) { @@ -4335,6 +4339,16 @@ public abstract class PlayerImpl implements Player, Serializable { this.canPlayCardsFromGraveyard = playCardsFromGraveyard; } + @Override + public void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn) { + this.drawsOnOpponentsTurn = drawsOnOpponentsTurn; + } + + @Override + public boolean isDrawsOnOpponentsTurn() { + return drawsOnOpponentsTurn; + } + @Override public boolean autoLoseGame() { return false; @@ -4462,7 +4476,7 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCards(Set cards, Zone toZone, - Ability source, Game game + Ability source, Game game ) { return moveCards(cards, toZone, source, game, false, false, false, null); } @@ -4621,7 +4635,7 @@ public abstract class PlayerImpl implements Player, Serializable { // identify cards from one owner Cards cards = new CardsImpl(); UUID ownerId = null; - for (Iterator it = allCards.iterator(); it.hasNext();) { + for (Iterator it = allCards.iterator(); it.hasNext(); ) { Card card = it.next(); if (cards.isEmpty()) { ownerId = card.getOwnerId(); @@ -4799,7 +4813,7 @@ public abstract class PlayerImpl implements Player, Serializable { game.informPlayers(this.getLogName() + " moves " + (withName ? card.getLogName() + (card.isCopy() ? " (Copy)" : "") : "a card face down") + ' ' + (fromZone != null ? "from " + fromZone.toString().toLowerCase(Locale.ENGLISH) - + ' ' : "") + "to the exile zone" + CardUtil.getSourceLogName(game, source, card.getId())); + + ' ' : "") + "to the exile zone" + CardUtil.getSourceLogName(game, source, card.getId())); } }