[DMU] Implement Enlist ability (#9431)

* implement enlist ability

* remove skips for enlist

* [DMU] Implemented Guardian of New Benalia

* add test for enlist
This commit is contained in:
Evan Kranzler 2022-08-29 18:51:13 -04:00 committed by GitHub
parent 9b094fb883
commit 697586a552
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 324 additions and 10 deletions

View file

@ -0,0 +1,90 @@
package mage.cards.g;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.DiscardCardCost;
import mage.abilities.effects.common.TapSourceEffect;
import mage.abilities.effects.common.continuous.GainAbilitySourceEffect;
import mage.abilities.effects.keyword.ScryEffect;
import mage.abilities.keyword.EnlistAbility;
import mage.abilities.keyword.IndestructibleAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class GuardianOfNewBenalia extends CardImpl {
public GuardianOfNewBenalia(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.SOLDIER);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// Enlist
this.addAbility(new EnlistAbility());
// Whenever Guardian of New Benalia enlists a creature, scry 2.
this.addAbility(new GuardianOfNewBenaliaTriggeredAbility());
// Discard a card: Guardian of New Benalia gains indestructible until end of turn. Tap it.
Ability ability = new SimpleActivatedAbility(new GainAbilitySourceEffect(
IndestructibleAbility.getInstance(), Duration.EndOfTurn
), new DiscardCardCost());
ability.addEffect(new TapSourceEffect().setText("tap it"));
this.addAbility(ability);
}
private GuardianOfNewBenalia(final GuardianOfNewBenalia card) {
super(card);
}
@Override
public GuardianOfNewBenalia copy() {
return new GuardianOfNewBenalia(this);
}
}
class GuardianOfNewBenaliaTriggeredAbility extends TriggeredAbilityImpl {
GuardianOfNewBenaliaTriggeredAbility() {
super(Zone.BATTLEFIELD, new ScryEffect(2));
}
private GuardianOfNewBenaliaTriggeredAbility(final GuardianOfNewBenaliaTriggeredAbility ability) {
super(ability);
}
@Override
public GuardianOfNewBenaliaTriggeredAbility copy() {
return new GuardianOfNewBenaliaTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.CREATURE_ENLISTED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return getSourceId().equals(event.getSourceId());
}
@Override
public String getRule() {
return "Whenever {this} enlists a creature, scry 2.";
}
}

View file

@ -4,16 +4,11 @@ import mage.cards.ExpansionSet;
import mage.constants.Rarity; import mage.constants.Rarity;
import mage.constants.SetType; import mage.constants.SetType;
import java.util.Arrays;
import java.util.List;
/** /**
* @author TheElk801 * @author TheElk801
*/ */
public final class DominariaUnited extends ExpansionSet { public final class DominariaUnited extends ExpansionSet {
private static final List<String> unfinished = Arrays.asList("Argivian Cavalier", "Balduvian Berserker", "Barkweave Crusher", "Benalish Faithbonder", "Coalition Skyknight", "Coalition Warbrute", "Guardian of New Benalia", "Hexbane Tortoise", "Keldon Flamesage", "Linebreaker Baloth", "Yavimaya Steelcrusher");
private static final DominariaUnited instance = new DominariaUnited(); private static final DominariaUnited instance = new DominariaUnited();
public static DominariaUnited getInstance() { public static DominariaUnited getInstance() {
@ -105,6 +100,7 @@ public final class DominariaUnited extends ExpansionSet {
cards.add(new SetCardInfo("Gibbering Barricade", 95, Rarity.COMMON, mage.cards.g.GibberingBarricade.class)); cards.add(new SetCardInfo("Gibbering Barricade", 95, Rarity.COMMON, mage.cards.g.GibberingBarricade.class));
cards.add(new SetCardInfo("Goblin Picker", 128, Rarity.COMMON, mage.cards.g.GoblinPicker.class)); cards.add(new SetCardInfo("Goblin Picker", 128, Rarity.COMMON, mage.cards.g.GoblinPicker.class));
cards.add(new SetCardInfo("Griffin Protector", 18, Rarity.COMMON, mage.cards.g.GriffinProtector.class)); cards.add(new SetCardInfo("Griffin Protector", 18, Rarity.COMMON, mage.cards.g.GriffinProtector.class));
cards.add(new SetCardInfo("Guardian of New Benalia", 19, Rarity.RARE, mage.cards.g.GuardianOfNewBenalia.class));
cards.add(new SetCardInfo("Hammerhand", 129, Rarity.COMMON, mage.cards.h.Hammerhand.class)); cards.add(new SetCardInfo("Hammerhand", 129, Rarity.COMMON, mage.cards.h.Hammerhand.class));
cards.add(new SetCardInfo("Haughty Djinn", 52, Rarity.RARE, mage.cards.h.HaughtyDjinn.class)); cards.add(new SetCardInfo("Haughty Djinn", 52, Rarity.RARE, mage.cards.h.HaughtyDjinn.class));
cards.add(new SetCardInfo("Haunted Mire", 248, Rarity.COMMON, mage.cards.h.HauntedMire.class)); cards.add(new SetCardInfo("Haunted Mire", 248, Rarity.COMMON, mage.cards.h.HauntedMire.class));

View file

@ -0,0 +1,132 @@
package org.mage.test.cards.abilities.keywords;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author TheElk801
*/
public class EnlistTest extends CardTestPlayerBase {
private static final String crusher = "Barkweave Crusher";
private static final String lion = "Silvercoat Lion";
private static final String goblin = "Raging Goblin";
private static final String angel = "Serra Angel";
@Test
public void testRegularChooseYes() {
addCard(Zone.BATTLEFIELD, playerA, crusher);
addCard(Zone.BATTLEFIELD, playerA, lion);
attack(1, playerA, crusher);
setChoice(playerA, true);
setChoice(playerA, lion);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPowerToughness(playerA, crusher, 2 + 2, 5);
assertTapped(crusher, true);
assertTapped(lion, true);
assertLife(playerB, 20 - 2 - 2);
}
@Test
public void testRegularChooseNo() {
addCard(Zone.BATTLEFIELD, playerA, crusher);
addCard(Zone.BATTLEFIELD, playerA, lion);
attack(1, playerA, crusher);
setChoice(playerA, false);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPowerToughness(playerA, crusher, 2, 5);
assertTapped(crusher, true);
assertTapped(lion, false);
assertLife(playerB, 20 - 2);
}
@Test
public void testSummoningSick() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
addCard(Zone.BATTLEFIELD, playerA, crusher);
addCard(Zone.HAND, playerA, lion);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lion);
attack(1, playerA, crusher);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPowerToughness(playerA, crusher, 2, 5);
assertTapped(crusher, true);
assertTapped(lion, false);
assertLife(playerB, 20 - 2);
}
@Test
public void testAttackWithBoth() {
addCard(Zone.BATTLEFIELD, playerA, crusher);
addCard(Zone.BATTLEFIELD, playerA, lion);
attack(1, playerA, crusher);
attack(1, playerA, lion);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPowerToughness(playerA, crusher, 2, 5);
assertTapped(crusher, true);
assertTapped(lion, true);
assertLife(playerB, 20 - 2 - 2);
}
@Test
public void testHaste() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain");
addCard(Zone.BATTLEFIELD, playerA, crusher);
addCard(Zone.HAND, playerA, goblin);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, goblin);
attack(1, playerA, crusher);
setChoice(playerA, true);
setChoice(playerA, goblin);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPowerToughness(playerA, crusher, 2 + 1, 5);
assertTapped(crusher, true);
assertTapped(goblin, true);
assertLife(playerB, 20 - 2 - 1);
}
@Test
public void testVigilance() {
addCard(Zone.BATTLEFIELD, playerA, crusher);
addCard(Zone.BATTLEFIELD, playerA, angel);
attack(1, playerA, crusher);
attack(1, playerA, angel);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPowerToughness(playerA, crusher, 2, 5);
assertTapped(crusher, true);
assertTapped(angel, false);
assertLife(playerB, 20 - 2 - 4);
}
}

View file

@ -1733,7 +1733,7 @@ public class TestPlayer implements Player {
// Second check to filter creature for combat - less strict to workaround issue in #3038 // Second check to filter creature for combat - less strict to workaround issue in #3038
FilterCreatureForCombat secondFilter = new FilterCreatureForCombat(); FilterCreatureForCombat secondFilter = new FilterCreatureForCombat();
// secondFilter.add(Predicates.not(AttackingPredicate.instance)); // secondFilter.add(Predicates.not(AttackingPredicate.instance));
secondFilter.add(Predicates.not(new SummoningSicknessPredicate())); secondFilter.add(Predicates.not(SummoningSicknessPredicate.instance));
// TODO: Cannot enforce legal attackers multiple times per combat. See issue #3038 // TODO: Cannot enforce legal attackers multiple times per combat. See issue #3038
Permanent attacker = findPermanent(secondFilter, groups[0], computerPlayer.getId(), game, false); Permanent attacker = findPermanent(secondFilter, groups[0], computerPlayer.getId(), game, false);
if (attacker != null && attacker.canAttack(defenderId, game)) { if (attacker != null && attacker.canAttack(defenderId, game)) {

View file

@ -1,16 +1,33 @@
package mage.abilities.keyword; package mage.abilities.keyword;
import mage.abilities.Ability;
import mage.abilities.StaticAbility; import mage.abilities.StaticAbility;
import mage.abilities.common.delayed.ReflexiveTriggeredAbility;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.continuous.BoostSourceEffect;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone; import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.filter.predicate.permanent.AttackingPredicate;
import mage.filter.predicate.permanent.SummoningSicknessPredicate;
import mage.filter.predicate.permanent.TappedPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.TargetPermanent;
/** /**
* @author TheElk801 * @author TheElk801
* TODO: Implement this
*/ */
public class EnlistAbility extends StaticAbility { public class EnlistAbility extends StaticAbility {
public EnlistAbility() { public EnlistAbility() {
super(Zone.BATTLEFIELD, null); super(Zone.BATTLEFIELD, new EnlistEffect());
} }
private EnlistAbility(final EnlistAbility ability) { private EnlistAbility(final EnlistAbility ability) {
@ -28,3 +45,74 @@ public class EnlistAbility extends StaticAbility {
"without summoning sickness. When you do, add its power to this creature's until end of turn.)</i>"; "without summoning sickness. When you do, add its power to this creature's until end of turn.)</i>";
} }
} }
class EnlistEffect extends ReplacementEffectImpl {
private static final FilterPermanent filter = new FilterControlledCreaturePermanent(
"another untapped nonattacking creature you control without summoning sickness"
);
static {
filter.add(AnotherPredicate.instance);
filter.add(TappedPredicate.UNTAPPED);
filter.add(Predicates.not(SummoningSicknessPredicate.instance));
filter.add(Predicates.not(AttackingPredicate.instance));
}
public EnlistEffect() {
super(Duration.WhileOnBattlefield, Outcome.Detriment);
}
public EnlistEffect(EnlistEffect effect) {
super(effect);
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ATTACKER_DECLARED;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
return event.getSourceId().equals(source.getSourceId());
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Permanent creature = game.getPermanent(event.getSourceId());
Player controller = game.getPlayer(source.getControllerId());
if (creature == null || controller == null
|| !game.getBattlefield().contains(filter, source, game, 1)
|| !controller.chooseUse(outcome, "Enlist a creature for " + creature.getLogName() + '?', source, game)) {
return false;
}
TargetPermanent target = new TargetPermanent(filter);
target.setNotTarget(true);
controller.choose(outcome, target, source, game);
Permanent permanent = game.getPermanent(target.getFirstTarget());
if (permanent == null || !permanent.tap(source, game)) {
return false;
}
game.fireReflexiveTriggeredAbility(new ReflexiveTriggeredAbility(
new BoostSourceEffect(
permanent.getPower().getValue(),
0, Duration.EndOfTurn
), false
), source);
game.fireEvent(GameEvent.getEvent(
GameEvent.EventType.CREATURE_ENLISTED,
permanent.getId(), source, source.getControllerId()
));
return false;
}
@Override
public EnlistEffect copy() {
return new EnlistEffect(this);
}
}

View file

@ -5,10 +5,10 @@ import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public class SummoningSicknessPredicate implements Predicate<Permanent> { public enum SummoningSicknessPredicate implements Predicate<Permanent> {
instance;
@Override @Override
public boolean apply(Permanent input, Game game) { public boolean apply(Permanent input, Game game) {

View file

@ -354,6 +354,14 @@ public class GameEvent implements Serializable {
BECOMES_EXERTED, BECOMES_EXERTED,
BECOMES_RENOWNED, BECOMES_RENOWNED,
GAINS_CLASS_LEVEL, GAINS_CLASS_LEVEL,
/* CREATURE_ENLISTED
targetId id of the enlisted creature
sourceId id of the creature that enlisted
playerId player who controls the creatures
amount not used for this event
flag not used for this event
*/
CREATURE_ENLISTED,
/* BECOMES_MONARCH /* BECOMES_MONARCH
targetId playerId of the player that becomes the monarch targetId playerId of the player that becomes the monarch
sourceId id of the source object that created that effect, if no effect exist it's null sourceId id of the source object that created that effect, if no effect exist it's null