diff --git a/Mage.Sets/src/mage/cards/t/TomBombadil.java b/Mage.Sets/src/mage/cards/t/TomBombadil.java new file mode 100644 index 00000000000..b864be76b59 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TomBombadil.java @@ -0,0 +1,188 @@ +package mage.cards.t; + +import java.util.Optional; +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SagaAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.CountersOnPermanentsCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.HexproofAbility; +import mage.abilities.keyword.IndestructibleAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.stack.StackObject; +import mage.players.Player; +import mage.util.CardUtil; +import mage.abilities.hint.common.CountersOnPermanentsHint; + +/** + * @author alexander-novo + */ +public final class TomBombadil extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.SAGA); + private static final CountersOnPermanentsCondition condition = new CountersOnPermanentsCondition(filter, + CounterType.LORE, ComparisonType.MORE_THAN, 3); + private static final CountersOnPermanentsHint hint = new CountersOnPermanentsHint(condition); + + public TomBombadil(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[] { CardType.CREATURE }, "{W}{U}{B}{R}{G}"); + + addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.GOD); + this.subtype.add(SubType.BARD); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // As long as there are four or more lore counters among Sagas you control, Tom + // Bombadil has hexproof and indestructible. + Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(HexproofAbility.getInstance()), + condition, + "As long as there are four or more lore counters among Sagas you control, {this} has hexproof")); + ability.addEffect(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(IndestructibleAbility.getInstance()), condition, "and has indestructible")); + this.addAbility(ability.addHint(hint)); + + // Whenever the final chapter ability of a Saga you control resolves, reveal + // cards from the top of your library until you reveal a Saga card. Put that + // card onto the battlefield and the rest on the bottom of your library in a + // random order. This ability triggers only once each turn. + this.addAbility(new TomBombadilTriggeredAbility().setTriggersOnce(true)); + } + + private TomBombadil(final TomBombadil card) { + super(card); + } + + @Override + public TomBombadil copy() { + return new TomBombadil(this); + } +} + +class TomBombadilTriggeredAbility extends TriggeredAbilityImpl { + + public TomBombadilTriggeredAbility() { + super(Zone.BATTLEFIELD, new TomBombadilEffect(), false); + } + + public TomBombadilTriggeredAbility(final TomBombadilTriggeredAbility ability) { + super(ability); + } + + @Override + public TomBombadilTriggeredAbility copy() { + return new TomBombadilTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.RESOLVING_ABILITY; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + // At this point, the stack ability no longer exists, so we can only reference + // the ability that it came from. For EventType.RESOLVING_ABILITY, targetID is + // the ID of the original ability (on the permanent) that the resolving ability + // came from. + Optional ability_opt = game.getAbility(event.getTargetId(), event.getSourceId()); + if (!ability_opt.isPresent()) + return false; + + // Make sure it was a triggered ability (needed for checking if it's a chapter + // ability) + Ability ability = ability_opt.get(); + if (!(ability instanceof TriggeredAbility)) + return false; + + // Make sure it was a chapter ability + TriggeredAbility triggeredAbility = (TriggeredAbility) ability; + if (!SagaAbility.isChapterAbility(triggeredAbility)) + return false; + + // There's a chance that the permanent that this abiltiy came from no longer + // exists, so try and find it on the battlefield or check last known + // information. + // This permanent is needed to check if the resolving ability was the final + // chapter ability on that permanent. + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); + if (permanent == null + || !permanent.isControlledBy(getControllerId()) + || !permanent.hasSubtype(SubType.SAGA, game)) { + return false; + } + // Find the max chapter number from that permanent + int maxChapter = CardUtil + .castStream(permanent.getAbilities(game).stream(), SagaAbility.class) + .map(SagaAbility::getMaxChapter) + .mapToInt(SagaChapter::getNumber) + .sum(); + // Check if the ability was the last one + return SagaAbility.isFinalAbility(triggeredAbility, maxChapter); + } + + @Override + public String getRule() { + return "Whenever the final chapter ability of a Saga you control resolves, reveal cards from the top of your library until you reveal a Saga card. Put that card onto the battlefield and the rest on the bottom of your library in a random order."; + } +} + +// From PrismaticBridgeEffect +class TomBombadilEffect extends OneShotEffect { + + public TomBombadilEffect() { + super(Outcome.PutCardInPlay); + this.staticText = "reveal cards from the top of your library until you reveal a Saga card. Put that card onto the battlefield and the rest on the bottom of your library in a random order"; + } + + private TomBombadilEffect(final TomBombadilEffect effect) { + super(effect); + } + + @Override + public TomBombadilEffect copy() { + return new TomBombadilEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Cards toReveal = new CardsImpl(); + Card toBattlefield = null; + for (Card card : controller.getLibrary().getCards(game)) { + toReveal.add(card); + if (card.hasSubtype(SubType.SAGA, game)) { + toBattlefield = card; + break; + } + } + controller.revealCards(source, toReveal, game); + if (toBattlefield != null) { + controller.moveCards(toBattlefield, Zone.BATTLEFIELD, source, game); + toReveal.retainZone(Zone.LIBRARY, game); + } + controller.putCardsOnBottomOfLibrary(toReveal, game, source, false); + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java b/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java index f335be3cec9..fbc5597135f 100644 --- a/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java +++ b/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java @@ -37,6 +37,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Swamp", 276, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("The One Ring", 246, Rarity.MYTHIC, mage.cards.t.TheOneRing.class)); cards.add(new SetCardInfo("The Shire", 260, Rarity.RARE, mage.cards.t.TheShire.class)); + cards.add(new SetCardInfo("Tom Bombadil", 234, Rarity.MYTHIC, mage.cards.t.TomBombadil.class)); cards.add(new SetCardInfo("Trailblazer's Boots", 398, Rarity.RARE, mage.cards.t.TrailblazersBoots.class)); cards.add(new SetCardInfo("You Cannot Pass!", 38, Rarity.UNCOMMON, mage.cards.y.YouCannotPass.class)); diff --git a/Mage/src/main/java/mage/abilities/condition/common/CountersOnPermanentsCondition.java b/Mage/src/main/java/mage/abilities/condition/common/CountersOnPermanentsCondition.java new file mode 100644 index 00000000000..ab75fdbe90f --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/CountersOnPermanentsCondition.java @@ -0,0 +1,84 @@ +package mage.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.constants.ComparisonType; +import mage.counters.Counter; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * Condition which counts the number of a particular kind of counter on + * permanents, and sees if it exceeds or falls short of a threshold + * + * @author alexander-novo + */ + +public class CountersOnPermanentsCondition implements Condition { + // Which permanents to consider counters on + public final FilterPermanent filter; + // Which counter type to count + public final CounterType counterType; + // Whether to check if the number of counters exceeds or falls short of the + // threshold + public final ComparisonType comparisonType; + // The threshold to compare against + public final int threshold; + + /** + * + * @param filter Which permanents to consider counters on + * @param counterType Which counter type to count + * @param comparisonType Whether to check if the number of counters exceeds or + * falls short of the threshold + * @param threshold The threshold to compare against + */ + public CountersOnPermanentsCondition(FilterPermanent filter, CounterType counterType, ComparisonType comparisonType, + int threshold) { + this.filter = filter; + this.counterType = counterType; + this.comparisonType = comparisonType; + this.threshold = threshold; + } + + @Override + public boolean apply(Game game, Ability source) { + int totalCounters = 0; + for (Permanent permanent : game.getBattlefield().getActivePermanents(this.filter, + source.getControllerId(), source, game)) { + for (Counter counter : permanent.getCounters(game).values()) { + if (counter.getName().equals(this.counterType.getName())) { + totalCounters += counter.getCount(); + if (ComparisonType.compare(totalCounters, this.comparisonType, this.threshold)) { + return true; + } + } + + } + } + return false; + } + + @Override + public String toString() { + String comparisonText; + switch (this.comparisonType) { + case MORE_THAN: + comparisonText = String.format("%d or more", threshold + 1); + break; + case EQUAL_TO: + comparisonText = String.format("%d or fewer", threshold - 1); + break; + case FEWER_THAN: + comparisonText = String.format("%d", threshold); + break; + default: + throw new IllegalArgumentException("comparison rules for " + this.comparisonType + " missing"); + + } + return "there are " + comparisonText + this.counterType.getName() + " counters among " + + this.filter.getMessage(); + } +} diff --git a/Mage/src/main/java/mage/abilities/hint/common/CountersOnPermanentsHint.java b/Mage/src/main/java/mage/abilities/hint/common/CountersOnPermanentsHint.java new file mode 100644 index 00000000000..c81f9307042 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/hint/common/CountersOnPermanentsHint.java @@ -0,0 +1,72 @@ +package mage.abilities.hint.common; + +import mage.abilities.Ability; +import mage.abilities.hint.Hint; +import mage.cards.Card; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.counters.Counter; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.abilities.condition.common.CountersOnPermanentsCondition; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * A hint which keeps track of how many counters of a specific type there are + * among some type of permanents + * + * @author alexander-novo + */ +public class CountersOnPermanentsHint implements Hint { + + // Which permanents to consider counters on + public final FilterPermanent filter; + // Which counter type to count + public final CounterType counterType; + + /** + * @param filter Which permanents to consider counters on + * @param counterType Which counter type to count + */ + public CountersOnPermanentsHint(FilterPermanent filter, CounterType counterType) { + this.filter = filter; + this.counterType = counterType; + } + + /** + * Copy parameters from a {@link CountersOnPermanentsCondition} + */ + public CountersOnPermanentsHint(CountersOnPermanentsCondition condition) { + this.filter = condition.filter; + this.counterType = condition.counterType; + } + + @Override + public String getText(Game game, Ability ability) { + int totalCounters = 0; + for (Permanent permanent : game.getBattlefield().getActivePermanents(this.filter, + ability.getControllerId(), ability, game)) { + for (Counter counter : permanent.getCounters(game).values()) { + if (counter.getName().equals(this.counterType.getName())) { + totalCounters += counter.getCount(); + } + + } + } + return this.counterType.getName() + " counters among " + this.filter.getMessage() + ": " + totalCounters; + } + + @Override + public Hint copy() { + return this; + } +}