Skip to content

[SLD] Implement The Fifteenth Doctor #13741

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions Mage.Sets/src/mage/cards/t/TheFifteenthDoctor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package mage.cards.t;

import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.GainAbilityControlledSpellsEffect;
import mage.abilities.keyword.ImproviseAbility;
import mage.cards.*;
import mage.constants.*;
import mage.filter.FilterCard;
import mage.filter.common.FilterArtifactCard;
import mage.filter.common.FilterNonlandCard;
import mage.filter.predicate.ObjectSourcePlayer;
import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.ManaValuePredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.players.Player;
import mage.target.TargetCard;
import mage.watchers.Watcher;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
* @author balazskristof
*/
public class TheFifteenthDoctor extends CardImpl {

private static final FilterNonlandCard filter = new FilterNonlandCard("the first nonartifact spell you cast each turn");

static {
filter.add(TheFifteenthDoctorCastSpellPredicate.instance);
}

public TheFifteenthDoctor(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{R}");

this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.TIME_LORD);
this.subtype.add(SubType.DOCTOR);
this.power = new MageInt(3);
this.toughness = new MageInt(3);

// Whenever The Fifteenth Doctor enters or attacks, mill three cards. You may put an artifact card with mana value 2 or 3 from among them into your hand.
this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new TheFifteenthDoctorEffect(), false));

// The first nonartifact spell you cast each turn has improvise. (Your artifacts can help cast that spell. Each artifact you tap after you’re done activating mana abilities pays for {1}.)
this.addAbility(new SimpleStaticAbility(
new GainAbilityControlledSpellsEffect(new ImproviseAbility(), filter)
.setText("The first nonartifact spell you cast each turn has improvise. " +
"<i>(Your artifacts can help cast that spell. Each artifact you " +
"tap after you’re done activating mana abilities pays for {1}.)")),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error: (rules) card's rules contains restricted symbol ’ for SLD - The Fifteenth Doctor - 1584

new TheFifteenthDoctorSpellWatcher()
);
}

private TheFifteenthDoctor(final TheFifteenthDoctor card) {
super(card);
}

@Override
public TheFifteenthDoctor copy() {
return new TheFifteenthDoctor(this);
}
}

class TheFifteenthDoctorEffect extends OneShotEffect {

public static final FilterCard filter = new FilterArtifactCard("an artifact card with mana value 2 or 3");

static {
filter.add(Predicates.or(
new ManaValuePredicate(ComparisonType.EQUAL_TO, 2),
new ManaValuePredicate(ComparisonType.EQUAL_TO, 3)
));
}

TheFifteenthDoctorEffect() {
super(Outcome.Benefit);
staticText = "mill three cards. You may put an artifact card " +
"with mana value 2 or 3 from among them into your hand.";
}

private TheFifteenthDoctorEffect(final TheFifteenthDoctorEffect effect) { super(effect); }

@Override
public TheFifteenthDoctorEffect copy() { return new TheFifteenthDoctorEffect(this); }

@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
Cards cards = controller.millCards(3, source, game);
String message = "Put " + filter.getMessage() +
" from among the milled cards into your hand?";
if (cards.isEmpty()
|| !controller.chooseUse(outcome, message, source, game)) {
Copy link
Member

@JayDi85 JayDi85 Jun 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use target 0, 1 with single dialog instead use + choose with two dialogs for better UX

return true;
}
TargetCard target = new TargetCard(Zone.GRAVEYARD, filter);
if (controller.choose(outcome, cards, target, source, game)) {
Card card = game.getCard(target.getFirstTarget());
if (card != null) {
return controller.moveCards(card, Zone.HAND, source, game);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not return here, see below return. Apply logic - return true id effect did anything to the game state and return false if it doesn’t (e.g. in wrong conditions or empty result).

}
}
return false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must return true here cause milled in any use cases.

}
}

class TheFifteenthDoctorSpellWatcher extends Watcher {
private final Map<UUID, Integer> nonartifactSpells;

public TheFifteenthDoctorSpellWatcher() {
super(WatcherScope.GAME);
nonartifactSpells = new HashMap<>();
}

@Override
public void watch(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.SPELL_CAST) {
Spell spell = (Spell) game.getObject(event.getTargetId());
if (spell != null && !spell.isArtifact(game)) {
nonartifactSpells.put(event.getPlayerId(), nonartifactSpellsCastThisTurn(event.getPlayerId()) + 1);
}
}
}

public int nonartifactSpellsCastThisTurn(UUID playerId) { return nonartifactSpells.getOrDefault(playerId, 0); }

@Override
public void reset() {
super.reset();
nonartifactSpells.clear();
}
}

enum TheFifteenthDoctorCastSpellPredicate implements ObjectSourcePlayerPredicate<Card> {
instance;

@Override
public boolean apply(ObjectSourcePlayer<Card> input, Game game) {
if (input.getObject() != null && !input.getObject().isArtifact(game)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need in object filter in predicates. Player and object filled all the time. Same for source param (there are possible rare use cases with empty source but it not related to predicates).

TheFifteenthDoctorSpellWatcher watcher = game.getState().getWatcher(TheFifteenthDoctorSpellWatcher.class);
return watcher != null && watcher.nonartifactSpellsCastThisTurn(input.getPlayerId()) == 0;
}
return false;
}

@Override
public String toString() { return "The first nonartifact spell you cast each turn"; }
}
2 changes: 1 addition & 1 deletion Mage.Sets/src/mage/sets/SecretLairDrop.java
Original file line number Diff line number Diff line change
Expand Up @@ -1554,7 +1554,7 @@ private SecretLairDrop() {
cards.add(new SetCardInfo("The Meep", 1581, Rarity.RARE, mage.cards.t.TheMeep.class));
// cards.add(new SetCardInfo("The Celestial Toymaker", 1582, Rarity.RARE, mage.cards.t.TheCelestialToymaker.class));
cards.add(new SetCardInfo("The Fourteenth Doctor", 1583, Rarity.RARE, mage.cards.t.TheFourteenthDoctor.class));
// cards.add(new SetCardInfo("The Fifteenth Doctor", 1584, Rarity.RARE, mage.cards.t.TheFifteenthDoctor.class));
cards.add(new SetCardInfo("The Fifteenth Doctor", 1584, Rarity.RARE, mage.cards.t.TheFifteenthDoctor.class));
cards.add(new SetCardInfo("Elspeth Tirel", 1585, Rarity.MYTHIC, mage.cards.e.ElspethTirel.class));
cards.add(new SetCardInfo("Giada, Font of Hope", 1586, Rarity.RARE, mage.cards.g.GiadaFontOfHope.class));
cards.add(new SetCardInfo("Shelter", 1587, Rarity.RARE, mage.cards.s.Shelter.class, NON_FULL_USE_VARIOUS));
Expand Down