[OTJ] Implementing "commit crime" mechanic (#11859)

* [OTJ] Implement Oko the Ringleader

* [OTJ] Implement Duelist of the Mind

* update implementation of crime mechanic to match new info

* [OTJ] Implement Marauding Sphinx

* [OTJ] Implement Hardbristle Bandit

* [OTJ] Implement Intimidation Campaign

* [OTJ] Implement Freestrider Lookout

* add initial test

* add more tests

* apply requested changes

* applied requested changes

* fix verify failure
This commit is contained in:
Evan Kranzler 2024-03-28 11:19:27 -04:00 committed by GitHub
parent 6d7f42e5d7
commit fa0f9f3d00
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 896 additions and 2 deletions

View file

@ -0,0 +1,57 @@
package mage.cards.d;
import mage.MageInt;
import mage.abilities.common.CommittedCrimeTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.common.CardsDrawnThisTurnDynamicValue;
import mage.abilities.effects.common.DrawDiscardControllerEffect;
import mage.abilities.effects.common.continuous.SetBasePowerSourceEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.VigilanceAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class DuelistOfTheMind extends CardImpl {
public DuelistOfTheMind(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.ADVISOR);
this.power = new MageInt(0);
this.toughness = new MageInt(3);
// Flying
this.addAbility(FlyingAbility.getInstance());
// Vigilance
this.addAbility(VigilanceAbility.getInstance());
// Duelist of the Mind's power is equal to the number of cards you've drawn this turn.
this.addAbility(new SimpleStaticAbility(
Zone.ALL, new SetBasePowerSourceEffect(CardsDrawnThisTurnDynamicValue.instance)
).addHint(CardsDrawnThisTurnDynamicValue.getHint()));
// Whenever you commit a crime, you may draw a card. If you do, discard a card. This ability triggers only once each turn.
this.addAbility(new CommittedCrimeTriggeredAbility(
new DrawDiscardControllerEffect(1, 1, true), false
).setTriggersOnceEachTurn(true));
}
private DuelistOfTheMind(final DuelistOfTheMind card) {
super(card);
}
@Override
public DuelistOfTheMind copy() {
return new DuelistOfTheMind(this);
}
}

View file

@ -0,0 +1,47 @@
package mage.cards.f;
import mage.MageInt;
import mage.abilities.common.CommittedCrimeTriggeredAbility;
import mage.abilities.effects.common.LookLibraryAndPickControllerEffect;
import mage.abilities.keyword.ReachAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.PutCards;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class FreestriderLookout extends CardImpl {
public FreestriderLookout(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.ROGUE);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// Reach
this.addAbility(ReachAbility.getInstance());
// Whenever you commit a crime, look at the top five cards of your library. You may put a land card from among them onto the battlefield tapped. Put the rest on the bottom of your library in a random order. This ability triggers only once each turn.
this.addAbility(new CommittedCrimeTriggeredAbility(new LookLibraryAndPickControllerEffect(
5, 1, StaticFilters.FILTER_CARD_LAND_A,
PutCards.BATTLEFIELD_TAPPED, PutCards.BOTTOM_RANDOM
)).setTriggersOnceEachTurn(true));
}
private FreestriderLookout(final FreestriderLookout card) {
super(card);
}
@Override
public FreestriderLookout copy() {
return new FreestriderLookout(this);
}
}

View file

@ -0,0 +1,42 @@
package mage.cards.h;
import mage.MageInt;
import mage.abilities.common.CommittedCrimeTriggeredAbility;
import mage.abilities.effects.common.UntapSourceEffect;
import mage.abilities.mana.AnyColorManaAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class HardbristleBandit extends CardImpl {
public HardbristleBandit(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}");
this.subtype.add(SubType.PLANT);
this.subtype.add(SubType.ROGUE);
this.power = new MageInt(1);
this.toughness = new MageInt(1);
// {T}: Add one mana of any color.
this.addAbility(new AnyColorManaAbility());
// Whenever you commit a crime, untap Hardbristle Bandit. This ability triggers only once each turn.
this.addAbility(new CommittedCrimeTriggeredAbility(new UntapSourceEffect()).setTriggersOnceEachTurn(true));
}
private HardbristleBandit(final HardbristleBandit card) {
super(card);
}
@Override
public HardbristleBandit copy() {
return new HardbristleBandit(this);
}
}

View file

@ -0,0 +1,44 @@
package mage.cards.i;
import mage.abilities.Ability;
import mage.abilities.common.CommittedCrimeTriggeredAbility;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.LoseLifeOpponentsEffect;
import mage.abilities.effects.common.ReturnToHandSourceEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class IntimidationCampaign extends CardImpl {
public IntimidationCampaign(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}{B}");
// When Intimidation Campaign enters the battlefield, each opponent loses 1 life, you gain 1 life, and you draw a card.
Ability ability = new EntersBattlefieldTriggeredAbility(new LoseLifeOpponentsEffect(1));
ability.addEffect(new GainLifeEffect(1).concatBy(","));
ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy(", and you"));
this.addAbility(ability);
// Whenever you commit a crime, you may return Intimidation Campaign to its owner's hand.
this.addAbility(new CommittedCrimeTriggeredAbility(
new ReturnToHandSourceEffect(true), true
));
}
private IntimidationCampaign(final IntimidationCampaign card) {
super(card);
}
@Override
public IntimidationCampaign copy() {
return new IntimidationCampaign(this);
}
}

View file

@ -0,0 +1,51 @@
package mage.cards.m;
import mage.MageInt;
import mage.abilities.common.CommittedCrimeTriggeredAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.keyword.SurveilEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.VigilanceAbility;
import mage.abilities.keyword.WardAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class MaraudingSphinx extends CardImpl {
public MaraudingSphinx(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{U}");
this.subtype.add(SubType.SPHINX);
this.subtype.add(SubType.ROGUE);
this.power = new MageInt(3);
this.toughness = new MageInt(5);
// Flying
this.addAbility(FlyingAbility.getInstance());
// Vigilance
this.addAbility(VigilanceAbility.getInstance());
// Ward {2}
this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}")));
// Whenever you commit a crime, surveil 2. This ability triggers only once each turn.
this.addAbility(new CommittedCrimeTriggeredAbility(new SurveilEffect(2), false).setTriggersOnceEachTurn(true));
}
private MaraudingSphinx(final MaraudingSphinx card) {
super(card);
}
@Override
public MaraudingSphinx copy() {
return new MaraudingSphinx(this);
}
}

View file

@ -0,0 +1,142 @@
package mage.cards.o;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.LoyaltyAbility;
import mage.abilities.common.BeginningOfCombatTriggeredAbility;
import mage.abilities.condition.common.CommittedCrimeCondition;
import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenCopyTargetEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.discard.DiscardControllerEffect;
import mage.abilities.keyword.HexproofAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterNonlandPermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.ElkToken;
import mage.target.common.TargetControlledCreaturePermanent;
import mage.util.functions.CopyApplier;
import mage.watchers.common.CommittedCrimeWatcher;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class OkoTheRingleader extends CardImpl {
public OkoTheRingleader(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{G}{U}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.OKO);
this.setStartingLoyalty(3);
// At the beginning of combat on your turn, Oko, the Ringleader becomes a copy of up to one target creature you control until end of turn, except he has hexproof.
Ability ability = new BeginningOfCombatTriggeredAbility(
new OkoTheRingleaderCopySelfEffect(), TargetController.YOU, false
);
ability.addTarget(new TargetControlledCreaturePermanent(0, 1));
this.addAbility(ability);
// +1: Draw two cards. If you've committed a crime this turn, discard a card. Otherwise, discard two cards.
ability = new LoyaltyAbility(new DrawCardSourceControllerEffect(2), 1);
ability.addEffect(new ConditionalOneShotEffect(
new DiscardControllerEffect(1), new DiscardControllerEffect(2),
CommittedCrimeCondition.instance, "if you've committed a crime this turn, " +
"discard a card. Otherwise, discard two cards"
));
this.addAbility(ability.addHint(CommittedCrimeCondition.getHint()), new CommittedCrimeWatcher());
// -1: Create a 3/3 green Elk creature token.
this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new ElkToken()), -1));
// -5: For each other nonland permanent you control, create a token that's a copy of that permanent.
this.addAbility(new LoyaltyAbility(new OkoTheRingleaderCopyTokenEffect(), -5));
}
private OkoTheRingleader(final OkoTheRingleader card) {
super(card);
}
@Override
public OkoTheRingleader copy() {
return new OkoTheRingleader(this);
}
}
class OkoTheRingleaderCopySelfEffect extends OneShotEffect {
private static final CopyApplier applier = new CopyApplier() {
@Override
public boolean apply(Game game, MageObject blueprint, Ability source, UUID targetObjectId) {
blueprint.getAbilities().add(HexproofAbility.getInstance());
return true;
}
};
OkoTheRingleaderCopySelfEffect() {
super(Outcome.Benefit);
staticText = "{this} becomes a copy of up to one target creature " +
"you control until end of turn, except he has hexproof";
}
private OkoTheRingleaderCopySelfEffect(final OkoTheRingleaderCopySelfEffect effect) {
super(effect);
}
@Override
public OkoTheRingleaderCopySelfEffect copy() {
return new OkoTheRingleaderCopySelfEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source));
if (permanent == null || creature == null) {
return false;
}
game.copyPermanent(Duration.EndOfTurn, creature, permanent.getId(), source, applier);
return true;
}
}
class OkoTheRingleaderCopyTokenEffect extends OneShotEffect {
private static final FilterPermanent filter = new FilterNonlandPermanent();
static {
filter.add(AnotherPredicate.instance);
filter.add(TargetController.YOU.getControllerPredicate());
}
OkoTheRingleaderCopyTokenEffect() {
super(Outcome.Benefit);
staticText = "for each other nonland permanent you control, create a token that's a copy of that permanent";
}
private OkoTheRingleaderCopyTokenEffect(final OkoTheRingleaderCopyTokenEffect effect) {
super(effect);
}
@Override
public OkoTheRingleaderCopyTokenEffect copy() {
return new OkoTheRingleaderCopyTokenEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) {
new CreateTokenCopyTargetEffect().setSavedPermanent(permanent).apply(game, source);
}
return true;
}
}

View file

@ -41,17 +41,21 @@ public final class OutlawsOfThunderJunction extends ExpansionSet {
cards.add(new SetCardInfo("Creosote Heath", 255, Rarity.COMMON, mage.cards.c.CreosoteHeath.class));
cards.add(new SetCardInfo("Cunning Coyote", 118, Rarity.UNCOMMON, mage.cards.c.CunningCoyote.class));
cards.add(new SetCardInfo("Desperate Bloodseeker", 86, Rarity.COMMON, mage.cards.d.DesperateBloodseeker.class));
cards.add(new SetCardInfo("Duelist of the Mind", 45, Rarity.RARE, mage.cards.d.DuelistOfTheMind.class));
cards.add(new SetCardInfo("Eroded Canyon", 256, Rarity.COMMON, mage.cards.e.ErodedCanyon.class));
cards.add(new SetCardInfo("Festering Gulch", 257, Rarity.COMMON, mage.cards.f.FesteringGulch.class));
cards.add(new SetCardInfo("Forest", 276, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Forlorn Flats", 258, Rarity.COMMON, mage.cards.f.ForlornFlats.class));
cards.add(new SetCardInfo("Form a Posse", 204, Rarity.UNCOMMON, mage.cards.f.FormAPosse.class));
cards.add(new SetCardInfo("Freestrider Lookout", 163, Rarity.RARE, mage.cards.f.FreestriderLookout.class));
cards.add(new SetCardInfo("Frontier Seeker", 13, Rarity.UNCOMMON, mage.cards.f.FrontierSeeker.class));
cards.add(new SetCardInfo("Hardbristle Bandit", 168, Rarity.COMMON, mage.cards.h.HardbristleBandit.class));
cards.add(new SetCardInfo("Hell to Pay", 126, Rarity.RARE, mage.cards.h.HellToPay.class));
cards.add(new SetCardInfo("High Noon", 15, Rarity.RARE, mage.cards.h.HighNoon.class));
cards.add(new SetCardInfo("Holy Cow", 16, Rarity.COMMON, mage.cards.h.HolyCow.class));
cards.add(new SetCardInfo("Honest Rutstein", 207, Rarity.UNCOMMON, mage.cards.h.HonestRutstein.class));
cards.add(new SetCardInfo("Inspiring Vantage", 269, Rarity.RARE, mage.cards.i.InspiringVantage.class));
cards.add(new SetCardInfo("Intimidation Campaign", 208, Rarity.UNCOMMON, mage.cards.i.IntimidationCampaign.class));
cards.add(new SetCardInfo("Island", 273, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Jagged Barrens", 259, Rarity.COMMON, mage.cards.j.JaggedBarrens.class));
cards.add(new SetCardInfo("Jolene, Plundering Pugilist", 210, Rarity.UNCOMMON, mage.cards.j.JolenePlunderingPugilist.class));
@ -60,7 +64,9 @@ public final class OutlawsOfThunderJunction extends ExpansionSet {
cards.add(new SetCardInfo("Loan Shark", 55, Rarity.COMMON, mage.cards.l.LoanShark.class));
cards.add(new SetCardInfo("Lonely Arroyo", 260, Rarity.COMMON, mage.cards.l.LonelyArroyo.class));
cards.add(new SetCardInfo("Lush Oasis", 261, Rarity.COMMON, mage.cards.l.LushOasis.class));
cards.add(new SetCardInfo("Marauding Sphinx", 56, Rarity.UNCOMMON, mage.cards.m.MaraudingSphinx.class));
cards.add(new SetCardInfo("Mountain", 275, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Oko, the Ringleader", 223, Rarity.MYTHIC, mage.cards.o.OkoTheRingleader.class));
cards.add(new SetCardInfo("Plains", 272, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Plan the Heist", 62, Rarity.UNCOMMON, mage.cards.p.PlanTheHeist.class));
cards.add(new SetCardInfo("Rakish Crew", 99, Rarity.UNCOMMON, mage.cards.r.RakishCrew.class));

View file

@ -0,0 +1,275 @@
package org.mage.test.cards.abilities.keywords;
import mage.abilities.common.CommittedCrimeTriggeredAbility;
import mage.abilities.effects.common.GainLifeEffect;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author TheElk801
*/
public class CommittedCrimeTest extends CardTestPlayerBase {
private void makeTester() {
addCustomCardWithAbility(
"tester", playerA, new CommittedCrimeTriggeredAbility(new GainLifeEffect(1), false)
);
}
private static final String spike = "Lava Spike";
@Test
public void testSpikeOpponent() {
makeTester();
addCard(Zone.BATTLEFIELD, playerA, "Mountain");
addCard(Zone.HAND, playerA, spike);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, spike, playerB);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerA, 20 + 1);
assertLife(playerB, 20 - 3);
}
@Test
public void testSpikeSelf() {
makeTester();
addCard(Zone.BATTLEFIELD, playerA, "Mountain");
addCard(Zone.HAND, playerA, spike);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, spike, playerA);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerA, 20 - 3);
assertLife(playerB, 20);
}
private static final String voidslime = "Voidslime";
private static final String sanctifier = "Cathedral Sanctifier";
@Test
public void testCounterSpellOpponent() {
makeTester();
addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 3);
addCard(Zone.BATTLEFIELD, playerB, "Plains");
addCard(Zone.HAND, playerA, voidslime);
addCard(Zone.HAND, playerB, sanctifier);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, sanctifier);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, voidslime, sanctifier);
setStrictChooseMode(true);
setStopAt(2, PhaseStep.END_TURN);
execute();
assertLife(playerA, 20 + 1);
}
@Test
public void testCounterAbilityOpponent() {
makeTester();
addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 3);
addCard(Zone.BATTLEFIELD, playerB, "Plains");
addCard(Zone.HAND, playerA, voidslime);
addCard(Zone.HAND, playerB, sanctifier);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, sanctifier);
waitStackResolved(2, PhaseStep.PRECOMBAT_MAIN, true);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, voidslime, "stack");
setStrictChooseMode(true);
setStopAt(2, PhaseStep.END_TURN);
execute();
assertLife(playerA, 20 + 1);
assertLife(playerB, 20);
}
@Test
public void testCounterSpellSelf() {
makeTester();
addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 3);
addCard(Zone.BATTLEFIELD, playerA, "Plains");
addCard(Zone.HAND, playerA, voidslime);
addCard(Zone.HAND, playerA, sanctifier);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sanctifier);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, voidslime, sanctifier);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerA, 20);
}
@Test
public void testCounterAbilitySelf() {
makeTester();
addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 3);
addCard(Zone.BATTLEFIELD, playerA, "Plains");
addCard(Zone.HAND, playerA, voidslime);
addCard(Zone.HAND, playerA, sanctifier);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sanctifier);
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, voidslime, "stack");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerA, 20);
}
private static final String bear = "Grizzly Bears";
private static final String murder = "Murder";
@Test
public void testMurderOpponent() {
makeTester();
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
addCard(Zone.BATTLEFIELD, playerB, bear);
addCard(Zone.HAND, playerA, murder);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, murder, bear);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerB, bear, 0);
assertGraveyardCount(playerB, bear, 1);
assertGraveyardCount(playerA, murder, 1);
assertLife(playerA, 20 + 1);
}
@Test
public void testMurderSelf() {
makeTester();
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
addCard(Zone.BATTLEFIELD, playerA, bear);
addCard(Zone.HAND, playerA, murder);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, murder, bear);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, bear, 0);
assertGraveyardCount(playerA, bear, 1);
assertGraveyardCount(playerA, murder, 1);
assertLife(playerA, 20);
}
private static final String purge = "Coffin Purge";
@Test
public void testGraveyardOpponent() {
makeTester();
addCard(Zone.BATTLEFIELD, playerA, "Swamp");
addCard(Zone.HAND, playerA, purge);
addCard(Zone.GRAVEYARD, playerB, bear);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, purge, bear);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerB, bear, 0);
assertExileCount(playerB, bear, 1);
assertLife(playerA, 20 + 1);
}
@Test
public void testGraveyardSelf() {
makeTester();
addCard(Zone.BATTLEFIELD, playerA, "Swamp");
addCard(Zone.HAND, playerA, purge);
addCard(Zone.GRAVEYARD, playerA, bear);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, purge, bear);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, bear, 0);
assertExileCount(playerA, bear, 1);
assertLife(playerA, 20);
}
private static final String desert = "Sunscorched Desert";
@Test
public void testTriggerOpponent() {
makeTester();
addCard(Zone.HAND, playerA, desert);
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, desert);
addTarget(playerA, playerB);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerA, 20 + 1);
assertLife(playerB, 20 - 1);
}
@Test
public void testTriggerSelf() {
makeTester();
addCard(Zone.HAND, playerA, desert);
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, desert);
addTarget(playerA, playerA);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerA, 20 - 1);
}
private static final String fireslinger = "Goblin Fireslinger";
@Test
public void testActivateOpponent() {
makeTester();
addCard(Zone.BATTLEFIELD, playerA, fireslinger);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}", playerB);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerA, 20 + 1);
assertLife(playerB, 20 - 1);
}
@Test
public void testActivateSelf() {
makeTester();
addCard(Zone.BATTLEFIELD, playerA, fireslinger);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}", playerA);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerA, 20 - 1);
}
}

View file

@ -142,14 +142,14 @@ public class VerifyCardDataTest {
skipListAddName(SKIP_LIST_TYPE, "UNH", "Old Fogey"); // uses summon word as a joke card
skipListAddName(SKIP_LIST_TYPE, "UND", "Old Fogey");
skipListAddName(SKIP_LIST_TYPE, "UST", "capital offense"); // uses "instant" instead "Instant" as a joke card
skipListAddName(SKIP_LIST_TYPE, "OTJ", "Lonely Arroyo"); // temporary
skipListAddName(SKIP_LIST_TYPE, "OTJ", "Arid Archway"); // temporary
// subtype
// skipListAddName(SKIP_LIST_SUBTYPE, set, cardName);
skipListAddName(SKIP_LIST_SUBTYPE, "UGL", "Miss Demeanor"); // uses multiple types as a joke card: Lady, of, Proper, Etiquette
skipListAddName(SKIP_LIST_SUBTYPE, "UGL", "Elvish Impersonators"); // subtype is "Elves" pun
skipListAddName(SKIP_LIST_SUBTYPE, "UND", "Elvish Impersonators");
skipListAddName(SKIP_LIST_SUBTYPE, "OTJ", "Lonely Arroyo"); // temporary
skipListAddName(SKIP_LIST_SUBTYPE, "OTJ", "Arid Archway"); // temporary
// number
// skipListAddName(SKIP_LIST_NUMBER, set, cardName);

View file

@ -0,0 +1,117 @@
package mage.abilities.common;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Controllable;
import mage.game.Game;
import mage.game.Ownerable;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.util.CardUtil;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
/**
* @author TheElk801
*/
public class CommittedCrimeTriggeredAbility extends TriggeredAbilityImpl {
public CommittedCrimeTriggeredAbility(Effect effect) {
this(effect, false);
}
public CommittedCrimeTriggeredAbility(Effect effect, boolean optional) {
super(Zone.BATTLEFIELD, effect, optional);
}
private CommittedCrimeTriggeredAbility(final CommittedCrimeTriggeredAbility ability) {
super(ability);
}
@Override
public CommittedCrimeTriggeredAbility copy() {
return new CommittedCrimeTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
switch (event.getType()) {
case SPELL_CAST:
case ACTIVATED_ABILITY:
case TRIGGERED_ABILITY:
return true;
default:
return false;
}
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return isControlledBy(getCriminal(event, game));
}
public static UUID getCriminal(GameEvent event, Game game) {
UUID controllerId;
Ability ability;
switch (event.getType()) {
case SPELL_CAST:
Spell spell = game.getSpell(event.getTargetId());
if (spell == null) {
return null;
}
controllerId = spell.getControllerId();
ability = spell.getSpellAbility();
break;
case ACTIVATED_ABILITY:
case TRIGGERED_ABILITY:
StackObject stackObject = game.getStack().getStackObject(event.getTargetId());
if (stackObject == null) {
return null;
}
controllerId = stackObject.getControllerId();
ability = stackObject.getStackAbility();
break;
default:
return null;
}
if (controllerId == null || ability == null) {
return null;
}
Set<UUID> opponents = game.getOpponents(controllerId);
Set<UUID> targets = CardUtil.getAllSelectedTargets(ability, game);
// an opponent
if (targets
.stream()
.anyMatch(opponents::contains)
// an opponent's permanent
|| targets
.stream()
.map(game::getPermanent)
.filter(Objects::nonNull)
.map(Controllable::getControllerId)
.anyMatch(opponents::contains)
// an opponent's spell or ability
|| targets
.stream()
.map(game.getStack()::getStackObject)
.filter(Objects::nonNull)
.map(Controllable::getControllerId)
.anyMatch(opponents::contains)
// a card in an opponent's graveyard
|| targets
.stream()
.filter(uuid -> Zone.GRAVEYARD.match(game.getState().getZone(uuid)))
.map(game::getCard)
.filter(Objects::nonNull)
.map(Ownerable::getOwnerId)
.anyMatch(opponents::contains)) {
return controllerId;
}
return null;
}
}

View file

@ -0,0 +1,32 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.hint.ConditionHint;
import mage.abilities.hint.Hint;
import mage.game.Game;
import mage.watchers.common.CommittedCrimeWatcher;
/**
* requires CommittedCrimeWatcher
*
* @author TheElk801
*/
public enum CommittedCrimeCondition implements Condition {
instance;
private static final Hint hint = new ConditionHint(instance, "You committed a crime this turn");
public static Hint getHint() {
return hint;
}
@Override
public boolean apply(Game game, Ability source) {
return CommittedCrimeWatcher.checkCriminality(game, source);
}
@Override
public String toString() {
return "you've committed a crime this turn";
}
}

View file

@ -0,0 +1,28 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SubType;
/**
* @author TheElk801
*/
public final class ElkToken extends TokenImpl {
public ElkToken() {
super("Elk Token", "3/3 green Elk creature token");
cardType.add(CardType.CREATURE);
color.setGreen(true);
subtype.add(SubType.ELK);
power = new MageInt(3);
toughness = new MageInt(3);
}
private ElkToken(final ElkToken token) {
super(token);
}
public ElkToken copy() {
return new ElkToken(this);
}
}

View file

@ -0,0 +1,53 @@
package mage.watchers.common;
import mage.abilities.Ability;
import mage.abilities.common.CommittedCrimeTriggeredAbility;
import mage.constants.WatcherScope;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.watchers.Watcher;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
/**
* @author TheElk801
*/
public class CommittedCrimeWatcher extends Watcher {
// players who committed a crime this turn
private final Set<UUID> criminals = new HashSet<>();
public CommittedCrimeWatcher() {
super(WatcherScope.GAME);
}
@Override
public void watch(GameEvent event, Game game) {
switch (event.getType()) {
case SPELL_CAST:
case ACTIVATED_ABILITY:
case TRIGGERED_ABILITY:
break;
default:
return;
}
Optional.ofNullable(CommittedCrimeTriggeredAbility.getCriminal(event, game)).ifPresent(criminals::add);
}
@Override
public void reset() {
super.reset();
criminals.clear();
}
public static boolean checkCriminality(Game game, Ability source) {
return game
.getState()
.getWatcher(CommittedCrimeWatcher.class)
.criminals
.contains(source.getControllerId());
}
}