[BOK] reworked implementation of Shirei, Shizo's Caretaker and added test

This commit is contained in:
Evan Kranzler 2022-02-24 17:25:17 -05:00
parent 2a00609918
commit 3cbfe4d623
13 changed files with 140 additions and 176 deletions

View file

@ -69,7 +69,7 @@ class LifelineEffect extends OneShotEffect {
Effect effect = new ReturnToBattlefieldUnderOwnerControlTargetEffect(false, false);
effect.setTargetPointer(new FixedTarget(card, game));
effect.setText("return that card to the battlefield under it's owner's control at the beginning of the next end step");
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(Zone.BATTLEFIELD, effect, TargetController.ANY), source);
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect, TargetController.ANY), source);
return true;
}
return false;

View file

@ -118,7 +118,7 @@ class MaddeningImpCreateDelayedTriggeredAbilityEffect extends OneShotEffect {
}
}
AtTheBeginOfNextEndStepDelayedTriggeredAbility delayedAbility
= new AtTheBeginOfNextEndStepDelayedTriggeredAbility(Zone.ALL, new MaddeningImpDelayedDestroyEffect(activeCreatures), TargetController.ANY, new InvertCondition(TargetAttackedThisTurnCondition.instance));
= new AtTheBeginOfNextEndStepDelayedTriggeredAbility(new MaddeningImpDelayedDestroyEffect(activeCreatures), TargetController.ANY, new InvertCondition(TargetAttackedThisTurnCondition.instance));
delayedAbility.getDuration();
game.addDelayedTriggeredAbility(delayedAbility, source);
return true;

View file

@ -103,7 +103,7 @@ class NettlingImpDelayedDestroyEffect extends OneShotEffect {
DestroyTargetEffect effect = new DestroyTargetEffect();
effect.setTargetPointer(new FixedTarget(source.getFirstTarget(), game));
AtTheBeginOfNextEndStepDelayedTriggeredAbility delayedAbility
= new AtTheBeginOfNextEndStepDelayedTriggeredAbility(Zone.ALL, effect, TargetController.ANY, new InvertCondition(TargetAttackedThisTurnCondition.instance));
= new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect, TargetController.ANY, new InvertCondition(TargetAttackedThisTurnCondition.instance));
delayedAbility.getDuration();
delayedAbility.getTargets().addAll(source.getTargets());
game.addDelayedTriggeredAbility(delayedAbility, source);

View file

@ -104,7 +104,7 @@ class NorrittDelayedDestroyEffect extends OneShotEffect {
DestroyTargetEffect effect = new DestroyTargetEffect();
effect.setTargetPointer(new FixedTarget(source.getFirstTarget(), game));
AtTheBeginOfNextEndStepDelayedTriggeredAbility delayedAbility
= new AtTheBeginOfNextEndStepDelayedTriggeredAbility(Zone.ALL, effect, TargetController.ANY, new InvertCondition(TargetAttackedThisTurnCondition.instance));
= new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect, TargetController.ANY, new InvertCondition(TargetAttackedThisTurnCondition.instance));
delayedAbility.getDuration();
delayedAbility.getTargets().addAll(source.getTargets());
game.addDelayedTriggeredAbility(delayedAbility, source);

View file

@ -55,7 +55,7 @@ public final class SeeRed extends CardImpl {
// At the beginning of your end step, if you didn't attack with a creature this turn, sacrifice See Red.
this.addAbility(new ConditionalInterveningIfTriggeredAbility(
new AtTheBeginOfNextEndStepDelayedTriggeredAbility(Zone.BATTLEFIELD, new SacrificeSourceEffect(), TargetController.YOU),
new AtTheBeginOfNextEndStepDelayedTriggeredAbility(new SacrificeSourceEffect(), TargetController.YOU),
new InvertCondition(ControllerAttackedThisTurnCondition.instance),
"At the beginning of your end step, if you didn't attack with a creature this turn, sacrifice {this}."), new AttackedThisTurnWatcher());
}

View file

@ -1,30 +1,38 @@
package mage.cards.s;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.DiesCreatureTriggeredAbility;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.ReturnToBattlefieldUnderYourControlTargetEffect;
import mage.cards.Card;
import mage.abilities.condition.common.SourceOnBattlefieldCondition;
import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect;
import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.PowerPredicate;
import java.util.UUID;
/**
* @author emerald000
* @author TheElk801
*/
public final class ShireiShizosCaretaker extends CardImpl {
private static final FilterPermanent filter = new FilterCreaturePermanent();
static {
filter.add(TargetController.YOU.getOwnerPredicate());
filter.add(new PowerPredicate(ComparisonType.FEWER_THAN, 2));
}
private static final String rule1 = "you may return that card to the battlefield " +
"t the beginning of the next end step if {this} is still on the battlefield";
private static final String rule2 = "Whenever a creature with power 1 or less " +
"is put into your graveyard from the battlefield, ";
public ShireiShizosCaretaker(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}");
addSuperType(SuperType.LEGENDARY);
@ -34,7 +42,12 @@ public final class ShireiShizosCaretaker extends CardImpl {
this.toughness = new MageInt(2);
// Whenever a creature with power 1 or less is put into your graveyard from the battlefield, you may return that card to the battlefield at the beginning of the next end step if Shirei, Shizo's Caretaker is still on the battlefield.
this.addAbility(new ShireiShizosCaretakerTriggeredAbility(this.getId()));
this.addAbility(new DiesCreatureTriggeredAbility(new CreateDelayedTriggeredAbilityEffect(
new AtTheBeginOfNextEndStepDelayedTriggeredAbility(new ConditionalOneShotEffect(
new ReturnFromGraveyardToBattlefieldTargetEffect(), SourceOnBattlefieldCondition.instance,
"you may return that card to the battlefield if {this} is still on the battlefield"
), TargetController.ANY, null, true)
).setText(rule1), false, filter, true).setTriggerPhrase(rule2));
}
private ShireiShizosCaretaker(final ShireiShizosCaretaker card) {
@ -46,110 +59,3 @@ public final class ShireiShizosCaretaker extends CardImpl {
return new ShireiShizosCaretaker(this);
}
}
class ShireiShizosCaretakerTriggeredAbility extends TriggeredAbilityImpl {
ShireiShizosCaretakerTriggeredAbility(UUID shireiId) {
super(Zone.BATTLEFIELD, new ShireiShizosCaretakerEffect(shireiId), false);
}
ShireiShizosCaretakerTriggeredAbility(final ShireiShizosCaretakerTriggeredAbility ability) {
super(ability);
}
@Override
public ShireiShizosCaretakerTriggeredAbility copy() {
return new ShireiShizosCaretakerTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ZONE_CHANGE;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
Permanent LKIpermanent = game.getPermanentOrLKIBattlefield(zEvent.getTargetId());
Card card = game.getCard(zEvent.getTargetId());
if (card != null
&& LKIpermanent != null
&& card.isOwnedBy(this.controllerId)
&& zEvent.isDiesEvent()
&& card.isCreature(game)
&& LKIpermanent.getPower().getValue() <= 1) {
for (Effect effect : this.getEffects()) {
effect.setTargetPointer(new FixedTarget(zEvent.getTargetId()));
}
return true;
}
return false;
}
@Override
public String getRule() {
return "Whenever a creature with power 1 or less is put into your graveyard from the battlefield, you may return that card to the battlefield at the beginning of the next end step if Shirei, Shizo's Caretaker is still on the battlefield.";
}
}
class ShireiShizosCaretakerEffect extends OneShotEffect {
protected final UUID shireiId;
ShireiShizosCaretakerEffect(UUID shireiId) {
super(Outcome.PutCreatureInPlay);
this.staticText = "you may return that card to the battlefield at the beginning of the next end step if {this} is still on the battlefield.";
this.shireiId = shireiId;
}
ShireiShizosCaretakerEffect(final ShireiShizosCaretakerEffect effect) {
super(effect);
this.shireiId = effect.shireiId;
}
@Override
public ShireiShizosCaretakerEffect copy() {
return new ShireiShizosCaretakerEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Card card = game.getCard(this.getTargetPointer().getFirst(game, source));
if (card != null) {
Effect effect = new ShireiShizosCaretakerReturnEffect(shireiId);
effect.setText("return that card to the battlefield if {this} is still on the battlefield");
DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect);
delayedAbility.getEffects().get(0).setTargetPointer(new FixedTarget(card, game));
game.addDelayedTriggeredAbility(delayedAbility, source);
return true;
}
return false;
}
}
class ShireiShizosCaretakerReturnEffect extends ReturnToBattlefieldUnderYourControlTargetEffect {
protected final UUID shireiId;
ShireiShizosCaretakerReturnEffect(UUID shireiId) {
this.shireiId = shireiId;
}
ShireiShizosCaretakerReturnEffect(final ShireiShizosCaretakerReturnEffect effect) {
super(effect);
this.shireiId = effect.shireiId;
}
@Override
public ShireiShizosCaretakerReturnEffect copy() {
return new ShireiShizosCaretakerReturnEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
if (game.getBattlefield().containsPermanent(shireiId)) {
return super.apply(game, source);
}
return false;
}
}

View file

@ -88,7 +88,7 @@ class TilonallisSummonerEffect extends OneShotEffect {
.setText("exile those tokens unless you have the city's blessing");
exileEffect.setTargetPointer(new FixedTargets(new CardsImpl(effect.getLastAddedTokenIds()), game));
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(
Zone.ALL, exileEffect, TargetController.ANY, new InvertCondition(CitysBlessingCondition.instance)), source);
exileEffect, TargetController.ANY, new InvertCondition(CitysBlessingCondition.instance)), source);
}
}
return true;

View file

@ -0,0 +1,73 @@
package org.mage.test.cards.single.bok;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author TheElk801
*/
public class ShireiShizosCaretakerTest extends CardTestPlayerBase {
private static final String shirei = "Shirei, Shizo's Caretaker";
private static final String rats = "Muck Rats";
private static final String murder = "Murder";
private static final String blink = "Momentary Blink";
@Test
public void testRegular() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
addCard(Zone.BATTLEFIELD, playerA, shirei);
addCard(Zone.BATTLEFIELD, playerA, rats);
addCard(Zone.HAND, playerA, murder);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, murder, rats);
setChoice(playerA, true);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, shirei, 1);
assertPermanentCount(playerA, rats, 1);
}
@Test
public void testLeftBattlefield() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 6);
addCard(Zone.BATTLEFIELD, playerA, shirei);
addCard(Zone.BATTLEFIELD, playerA, rats);
addCard(Zone.HAND, playerA, murder, 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, murder, rats);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, murder, shirei);
setChoice(playerA, true);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, shirei, 0);
assertPermanentCount(playerA, rats, 0);
}
@Test
public void testBlinked() {
addCard(Zone.BATTLEFIELD, playerA, "Scrubland", 5);
addCard(Zone.BATTLEFIELD, playerA, shirei);
addCard(Zone.BATTLEFIELD, playerA, rats);
addCard(Zone.HAND, playerA, murder);
addCard(Zone.HAND, playerA, blink);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, murder, rats);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, blink, shirei);
setChoice(playerA, true);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, shirei, 1);
assertPermanentCount(playerA, rats, 0);
}
}

View file

@ -1,4 +1,3 @@
package mage.abilities.common.delayed;
import mage.abilities.DelayedTriggeredAbility;
@ -6,34 +5,32 @@ import mage.abilities.condition.Condition;
import mage.abilities.effects.Effect;
import mage.constants.Duration;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
/**
*
* @author North
*/
public class AtTheBeginOfNextEndStepDelayedTriggeredAbility extends DelayedTriggeredAbility {
private TargetController targetController;
private Condition condition;
private final TargetController targetController;
private final Condition condition;
public AtTheBeginOfNextEndStepDelayedTriggeredAbility(Effect effect) {
this(effect, TargetController.ANY);
}
public AtTheBeginOfNextEndStepDelayedTriggeredAbility(Effect effect, TargetController targetController) {
this(Zone.ALL, effect, targetController);
this(effect, targetController, null);
}
public AtTheBeginOfNextEndStepDelayedTriggeredAbility(Zone zone, Effect effect, TargetController targetController) {
this(zone, effect, targetController, null);
public AtTheBeginOfNextEndStepDelayedTriggeredAbility(Effect effect, TargetController targetController, Condition condition) {
this(effect, targetController, condition, false);
}
public AtTheBeginOfNextEndStepDelayedTriggeredAbility(Zone zone, Effect effect, TargetController targetController, Condition condition) {
super(effect, Duration.Custom);
public AtTheBeginOfNextEndStepDelayedTriggeredAbility(Effect effect, TargetController targetController, Condition condition, boolean optional) {
super(effect, Duration.Custom, true, optional);
this.zone = zone;
this.targetController = targetController;
this.condition = condition;
@ -52,33 +49,34 @@ public class AtTheBeginOfNextEndStepDelayedTriggeredAbility extends DelayedTrigg
@Override
public boolean checkTrigger(GameEvent event, Game game) {
boolean correctEndPhase = false;
switch (targetController) {
case ANY:
correctEndPhase = true;
break;
case YOU:
correctEndPhase = event.getPlayerId().equals(this.controllerId);
if (!isControlledBy(event.getPlayerId())) {
return false;
}
break;
case OPPONENT:
if (game.getPlayer(this.getControllerId()).hasOpponent(event.getPlayerId(), game)) {
correctEndPhase = true;
if (!game.getOpponents(this.getControllerId()).contains(event.getPlayerId())) {
return false;
}
break;
case CONTROLLER_ATTACHED_TO:
Permanent attachment = game.getPermanent(sourceId);
if (attachment != null && attachment.getAttachedTo() != null) {
Permanent attachedTo = game.getPermanent(attachment.getAttachedTo());
if (attachedTo != null && attachedTo.isControlledBy(event.getPlayerId())) {
correctEndPhase = true;
}
}
}
if (correctEndPhase) {
return !(condition != null && !condition.apply(game, this));
}
Permanent attachment = game.getPermanent(getSourceId());
if (attachment == null || attachment.getAttachedTo() == null) {
return false;
}
Permanent attachedTo = game.getPermanent(attachment.getAttachedTo());
if (attachedTo == null || !attachedTo.isControlledBy(event.getPlayerId())) {
return false;
}
break;
default:
throw new UnsupportedOperationException("TargetController not supported");
}
return condition == null || condition.apply(game, this);
}
@Override
public AtTheBeginOfNextEndStepDelayedTriggeredAbility copy() {
@ -86,23 +84,17 @@ public class AtTheBeginOfNextEndStepDelayedTriggeredAbility extends DelayedTrigg
}
@Override
public String getRule() {
StringBuilder sb = new StringBuilder();
public String getTriggerPhrase() {
switch (targetController) {
case YOU:
sb.append("At the beginning of your next end step, ");
break;
return "At the beginning of your next end step, ";
case OPPONENT:
sb.append("At the beginning of an opponent's next end step, ");
break;
return "At the beginning of an opponent's next end step, ";
case ANY:
sb.append("At the beginning of the next end step, ");
break;
return "At the beginning of the next end step, ";
case CONTROLLER_ATTACHED_TO:
sb.append("At the beginning of the next end step of enchanted creature's controller, ");
break;
return "At the beginning of the next end step of enchanted creature's controller, ";
}
sb.append(getEffects().getText(modes.getMode()));
return sb.toString();
throw new UnsupportedOperationException("TargetController not supported");
}
}

View file

@ -1,11 +1,9 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.game.Game;
/**
* As long as the sourceId permanent is
* on the battlefield, the condition is true.
@ -13,18 +11,15 @@ import mage.game.Game;
* @author LevelX2
*/
public enum SourceOnBattlefieldCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
return (game.getPermanent(source.getSourceId()) != null);
return source.getSourcePermanentIfItStillExists(game) != null;
}
@Override
public String toString() {
return "if {this} is on the battlefield";
}
}

View file

@ -48,9 +48,7 @@ public class CreateDelayedTriggeredAbilityEffect extends OneShotEffect {
DelayedTriggeredAbility delayedAbility = ability.copy();
if (this.copyTargets) {
if (source.getTargets().isEmpty()) {
for (Effect effect : delayedAbility.getEffects()) {
effect.setTargetPointer(targetPointer);
}
delayedAbility.getEffects().setTargetPointer(targetPointer);
} else {
delayedAbility.getTargets().addAll(source.getTargets());
for (Effect effect : delayedAbility.getEffects()) {

View file

@ -93,7 +93,7 @@ class DarettiScrapSavantEffect extends OneShotEffect {
Effect effect = new ReturnFromGraveyardToBattlefieldTargetEffect();
effect.setTargetPointer(new FixedTarget(card, game));
effect.setText("return that card to the battlefield at the beginning of the next end step");
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(Zone.COMMAND, effect, TargetController.ANY), source);
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect, TargetController.ANY), source);
return true;
}
return false;

View file

@ -53,7 +53,7 @@ class LilianaDefiantNecromancerEmblemEffect extends OneShotEffect {
Effect effect = new ReturnFromGraveyardToBattlefieldTargetEffect();
effect.setTargetPointer(new FixedTarget(card, game));
effect.setText("return that card to the battlefield at the beginning of the next end step");
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(Zone.COMMAND, effect, TargetController.ANY), source);
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect, TargetController.ANY), source);
return true;
}
return false;