[MKC] Implement Serene Sleuth

This commit is contained in:
jmlundeen 2025-09-01 14:12:31 -05:00
parent 78694fce0b
commit 8656548676
3 changed files with 228 additions and 0 deletions

View file

@ -0,0 +1,132 @@
package mage.cards.s;
import mage.MageInt;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.keyword.InvestigateEffect;
import mage.abilities.triggers.BeginningOfCombatTriggeredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.permanent.GoadedPredicate;
import mage.game.Game;
import java.util.UUID;
/**
*
* @author Jmlundeen
*/
public final class SereneSleuth extends CardImpl {
private final static FilterPermanent filter = new FilterControlledCreaturePermanent("goaded creatures you control");
static {
filter.add(GoadedPredicate.instance);
}
public SereneSleuth(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.DETECTIVE);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// When Serene Sleuth enters the battlefield, investigate.
this.addAbility(new EntersBattlefieldTriggeredAbility(new InvestigateEffect()));
// At the beginning of combat on your turn, investigate for each goaded creature you control. Then each creature you control is no longer goaded.
Ability triggeredAbility = new BeginningOfCombatTriggeredAbility(
new InvestigateEffect(new PermanentsOnBattlefieldCount(filter))
.setText("investigate for each goaded creature you control.")
);
triggeredAbility.addEffect(new SereneSleuthEffect(filter)
.concatBy("Then"));
this.addAbility(triggeredAbility);
}
private SereneSleuth(final SereneSleuth card) {
super(card);
}
@Override
public SereneSleuth copy() {
return new SereneSleuth(this);
}
}
class SereneSleuthEffect extends ContinuousEffectImpl {
private MageObjectReference sourceMor;
private final FilterPermanent filter;
public SereneSleuthEffect(FilterPermanent filter) {
super(Duration.Custom, Layer.RulesEffects, SubLayer.NA, Outcome.Benefit);
staticText = "each creature you control is no longer goaded";
this.filter = filter;
}
private SereneSleuthEffect(final SereneSleuthEffect effect) {
super(effect);
this.sourceMor = effect.sourceMor;
this.filter = effect.filter;
}
public SereneSleuthEffect copy() {
return new SereneSleuthEffect(this);
}
public MageObjectReference getSourceMor() {
return sourceMor;
}
@Override
public void init(Ability source, Game game) {
super.init(source, game);
// discard previous effect if it exists to not clutter
for (ContinuousEffect effect : game.getContinuousEffects().getLayeredEffects(game)) {
if (effect instanceof SereneSleuthEffect) {
MageObjectReference effectsMor = ((SereneSleuthEffect) effect).getSourceMor();
if (effectsMor != null && effectsMor.refersTo(source.getSourceId(), game)) {
effect.discard();
this.affectedObjectList.addAll(effect.getAffectedObjects());
}
}
}
sourceMor = new MageObjectReference(source.getSourceObject(game), game);
setAffectedObjectsSet(true);
game.getBattlefield()
.getActivePermanents(
filter, source.getControllerId(), source, game
).stream()
.map(permanent -> new MageObjectReference(permanent, game))
.forEach(mor -> {
if (!this.affectedObjectList.contains(mor)) {
this.affectedObjectList.add(mor);
}
});
}
@Override
public boolean apply(Game game, Ability source) {
if (getAffectedObjectsSet()) {
this.affectedObjectList.removeIf(mor -> !mor.zoneCounterIsCurrent(game)
|| mor.getPermanent(game) == null);
if (affectedObjectList.isEmpty()) {
discard();
return false;
}
for (MageObjectReference mor : this.affectedObjectList) {
mor.getPermanent(game).getGoadingPlayers().clear();
}
return true;
}
return true;
}
}

View file

@ -244,6 +244,9 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet {
cards.add(new SetCardInfo("Selesnya Sanctuary", 290, Rarity.UNCOMMON, mage.cards.s.SelesnyaSanctuary.class));
cards.add(new SetCardInfo("Selfless Squire", 82, Rarity.RARE, mage.cards.s.SelflessSquire.class));
cards.add(new SetCardInfo("Selvala, Explorer Returned", 218, Rarity.RARE, mage.cards.s.SelvalaExplorerReturned.class));
cards.add(new SetCardInfo("Serene Sleuth", 14, Rarity.RARE, mage.cards.s.SereneSleuth.class));
cards.add(new SetCardInfo("Serene Sleuth", 14, Rarity.RARE, mage.cards.s.SereneSleuth.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Serene Sleuth", 325, Rarity.RARE, mage.cards.s.SereneSleuth.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Sevinne's Reclamation", 83, Rarity.RARE, mage.cards.s.SevinnesReclamation.class));
cards.add(new SetCardInfo("Sheltered Thicket", 291, Rarity.RARE, mage.cards.s.ShelteredThicket.class));
cards.add(new SetCardInfo("Shimmer Dragon", 117, Rarity.RARE, mage.cards.s.ShimmerDragon.class));

View file

@ -0,0 +1,93 @@
package org.mage.test.cards.single.mkc;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
import static org.junit.Assert.assertTrue;
/**
*
* @author Jmlundeen
*/
public class SereneSleuthTest extends CardTestPlayerBase {
/*
Serene Sleuth
{1}{W}
Creature - Human Detective
When Serene Sleuth enters the battlefield, investigate.
At the beginning of combat on your turn, investigate for each goaded creature you control. Then each creature you control is no longer goaded.
2/2
*/
private static final String sereneSleuth = "Serene Sleuth";
/*
Baeloth Barrityl, Entertainer
{4}{R}
Legendary Creature - Elf Shaman
Creatures your opponents control with power less than Baeloth Barrityl's power are goaded.
Whenever a goaded attacking or blocking creature dies, you create a Treasure token.
Choose a Background
2/5
*/
private static final String baelothBarritylEntertainer = "Baeloth Barrityl, Entertainer";
/*
Fugitive Wizard
{U}
Creature - Human Wizard
1/1
*/
private static final String fugitiveWizard = "Fugitive Wizard";
/*
Cloudshift
{W}
Instant
Exile target creature you control, then return that card to the battlefield under your control.
*/
private static final String cloudshift = "Cloudshift";
@Test
public void testSereneSleuth() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerB, baelothBarritylEntertainer);
addCard(Zone.BATTLEFIELD, playerA, fugitiveWizard);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
addCard(Zone.HAND, playerA, sereneSleuth);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sereneSleuth);
setStopAt(3, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, "Clue Token", 1 + 1);
assertTrue("fugitive wizard is not goaded", getPermanent(fugitiveWizard).getGoadingPlayers().isEmpty());
}
@Test
public void testSereneSleuthReGoaded() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerB, baelothBarritylEntertainer);
addCard(Zone.BATTLEFIELD, playerA, fugitiveWizard);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
addCard(Zone.BATTLEFIELD, playerB, "Plains");
addCard(Zone.HAND, playerA, sereneSleuth);
addCard(Zone.HAND, playerB, cloudshift);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sereneSleuth);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, cloudshift, baelothBarritylEntertainer);
setStopAt(3, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, "Clue Token", 1 + 1 + 1);
assertTrue("fugitive wizard is goaded", getPermanent(fugitiveWizard).getGoadingPlayers().isEmpty());
}
}