mirror of
https://github.com/magefree/mage.git
synced 2026-01-26 21:29:17 -08:00
Implementing "suspected" mechanic (#11670)
* [MKM] Implement Agrus Kos, Spirit of Justice * rework effects * [MKM] Implement Airtight Alibi * [MKM] Implement Convenient Target * [MKM] Implement Repeat Offender * add test * add more tests * add tooltip for suspected * implement requested changes
This commit is contained in:
parent
ea814ecf0c
commit
5a809f6fe4
13 changed files with 594 additions and 23 deletions
84
Mage.Sets/src/mage/cards/a/AgrusKosSpiritOfJustice.java
Normal file
84
Mage.Sets/src/mage/cards/a/AgrusKosSpiritOfJustice.java
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
package mage.cards.a;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.keyword.DoubleStrikeAbility;
|
||||
import mage.abilities.keyword.VigilanceAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class AgrusKosSpiritOfJustice extends CardImpl {
|
||||
|
||||
public AgrusKosSpiritOfJustice(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{W}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.SPIRIT);
|
||||
this.subtype.add(SubType.DETECTIVE);
|
||||
this.power = new MageInt(2);
|
||||
this.toughness = new MageInt(4);
|
||||
|
||||
// Double strike
|
||||
this.addAbility(DoubleStrikeAbility.getInstance());
|
||||
|
||||
// Vigilance
|
||||
this.addAbility(VigilanceAbility.getInstance());
|
||||
|
||||
// Whenever Agrus Kos, Spirit of Justice enters the battlefield or attacks, choose up to one target creature. If it's suspected, exile it. Otherwise, suspect it.
|
||||
Ability ability = new EntersBattlefieldOrAttacksSourceTriggeredAbility(new AgrusKosSpiritOfJusticeEffect());
|
||||
ability.addTarget(new TargetCreaturePermanent(0, 1));
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
private AgrusKosSpiritOfJustice(final AgrusKosSpiritOfJustice card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgrusKosSpiritOfJustice copy() {
|
||||
return new AgrusKosSpiritOfJustice(this);
|
||||
}
|
||||
}
|
||||
|
||||
class AgrusKosSpiritOfJusticeEffect extends OneShotEffect {
|
||||
|
||||
AgrusKosSpiritOfJusticeEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "choose up to one target creature. If it's suspected, exile it. Otherwise, suspect it";
|
||||
}
|
||||
|
||||
private AgrusKosSpiritOfJusticeEffect(final AgrusKosSpiritOfJusticeEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgrusKosSpiritOfJusticeEffect copy() {
|
||||
return new AgrusKosSpiritOfJusticeEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
|
||||
if (permanent == null) {
|
||||
return false;
|
||||
}
|
||||
if (!permanent.isSuspected()) {
|
||||
permanent.setSuspected(true, game, source);
|
||||
return true;
|
||||
}
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
return player != null && player.moveCards(permanent, Zone.EXILED, source, game);
|
||||
}
|
||||
}
|
||||
138
Mage.Sets/src/mage/cards/a/AirtightAlibi.java
Normal file
138
Mage.Sets/src/mage/cards/a/AirtightAlibi.java
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
package mage.cards.a;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.ReplacementEffectImpl;
|
||||
import mage.abilities.effects.common.AttachEffect;
|
||||
import mage.abilities.effects.common.UntapAttachedEffect;
|
||||
import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
|
||||
import mage.abilities.keyword.EnchantAbility;
|
||||
import mage.abilities.keyword.FlashAbility;
|
||||
import mage.abilities.keyword.HexproofAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class AirtightAlibi extends CardImpl {
|
||||
|
||||
public AirtightAlibi(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}");
|
||||
|
||||
this.subtype.add(SubType.AURA);
|
||||
|
||||
// Flash
|
||||
this.addAbility(FlashAbility.getInstance());
|
||||
|
||||
// Enchant creature
|
||||
TargetPermanent auraTarget = new TargetCreaturePermanent();
|
||||
this.getSpellAbility().addTarget(auraTarget);
|
||||
this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature));
|
||||
this.addAbility(new EnchantAbility(auraTarget));
|
||||
|
||||
// When Airtight Alibi enters the battlefield, untap enchanted creature. It gains hexproof until end of turn. If it's suspected, it's no longer suspected.
|
||||
Ability ability = new EntersBattlefieldTriggeredAbility(new UntapAttachedEffect());
|
||||
ability.addEffect(new GainAbilityAttachedEffect(
|
||||
HexproofAbility.getInstance(), AttachmentType.AURA, Duration.EndOfTurn
|
||||
).setText("It gains hexproof until end of turn"));
|
||||
ability.addEffect(new AirtightAlibiBecomeSuspectedEffect());
|
||||
this.addAbility(ability);
|
||||
|
||||
// Enchanted creature gets +2/+2 and can't become suspected.
|
||||
ability = new SimpleStaticAbility(new BoostEnchantedEffect(2, 2));
|
||||
ability.addEffect(new AirtightAlibiReplacementEffect());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
private AirtightAlibi(final AirtightAlibi card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AirtightAlibi copy() {
|
||||
return new AirtightAlibi(this);
|
||||
}
|
||||
}
|
||||
|
||||
class AirtightAlibiBecomeSuspectedEffect extends OneShotEffect {
|
||||
|
||||
AirtightAlibiBecomeSuspectedEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "If it's suspected, it's no longer suspected";
|
||||
}
|
||||
|
||||
private AirtightAlibiBecomeSuspectedEffect(final AirtightAlibiBecomeSuspectedEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AirtightAlibiBecomeSuspectedEffect copy() {
|
||||
return new AirtightAlibiBecomeSuspectedEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = Optional
|
||||
.ofNullable(source.getSourcePermanentOrLKI(game))
|
||||
.filter(Objects::nonNull)
|
||||
.map(Permanent::getAttachedTo)
|
||||
.map(game::getPermanent)
|
||||
.orElse(null);
|
||||
if (permanent == null || !permanent.isSuspected()) {
|
||||
return false;
|
||||
}
|
||||
permanent.setSuspected(false, game, source);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class AirtightAlibiReplacementEffect extends ReplacementEffectImpl {
|
||||
|
||||
AirtightAlibiReplacementEffect() {
|
||||
super(Duration.WhileOnBattlefield, Outcome.Benefit);
|
||||
staticText = "and can't become suspected";
|
||||
}
|
||||
|
||||
private AirtightAlibiReplacementEffect(final AirtightAlibiReplacementEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checksEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.BECOME_SUSPECTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(GameEvent event, Ability source, Game game) {
|
||||
return Optional
|
||||
.ofNullable(source.getSourcePermanentIfItStillExists(game))
|
||||
.filter(Objects::nonNull)
|
||||
.map(Permanent::getAttachedTo)
|
||||
.map(event.getTargetId()::equals)
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AirtightAlibiReplacementEffect copy() {
|
||||
return new AirtightAlibiReplacementEffect(this);
|
||||
}
|
||||
}
|
||||
90
Mage.Sets/src/mage/cards/c/ConvenientTarget.java
Normal file
90
Mage.Sets/src/mage/cards/c/ConvenientTarget.java
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
package mage.cards.c;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.AttachEffect;
|
||||
import mage.abilities.effects.common.ReturnSourceFromGraveyardToHandEffect;
|
||||
import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
|
||||
import mage.abilities.keyword.EnchantAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class ConvenientTarget extends CardImpl {
|
||||
|
||||
public ConvenientTarget(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{R}");
|
||||
|
||||
this.subtype.add(SubType.AURA);
|
||||
|
||||
// Enchant creature
|
||||
TargetPermanent auraTarget = new TargetCreaturePermanent();
|
||||
this.getSpellAbility().addTarget(auraTarget);
|
||||
this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature));
|
||||
this.addAbility(new EnchantAbility(auraTarget));
|
||||
|
||||
// When Convenient Target enters the battlefield, suspect enchanted creature.
|
||||
this.addAbility(new EntersBattlefieldTriggeredAbility(new ConvenientTargetEffect()));
|
||||
|
||||
// Enchanted creature gets +1/+1.
|
||||
this.addAbility(new SimpleStaticAbility(new BoostEnchantedEffect(1, 1)));
|
||||
|
||||
// {2}{R}: Return Convenient Target from your graveyard to your hand.
|
||||
this.addAbility(new SimpleActivatedAbility(
|
||||
Zone.GRAVEYARD, new ReturnSourceFromGraveyardToHandEffect(), new ManaCostsImpl<>("{2}{R}")
|
||||
));
|
||||
}
|
||||
|
||||
private ConvenientTarget(final ConvenientTarget card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConvenientTarget copy() {
|
||||
return new ConvenientTarget(this);
|
||||
}
|
||||
}
|
||||
|
||||
class ConvenientTargetEffect extends OneShotEffect {
|
||||
|
||||
ConvenientTargetEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "suspect enchanted creature";
|
||||
}
|
||||
|
||||
private ConvenientTargetEffect(final ConvenientTargetEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConvenientTargetEffect copy() {
|
||||
return new ConvenientTargetEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Optional
|
||||
.ofNullable(source.getSourcePermanentOrLKI(game))
|
||||
.map(Permanent::getAttachedTo)
|
||||
.map(game::getPermanent)
|
||||
.ifPresent(permanent -> permanent.setSuspected(true, game, source));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
74
Mage.Sets/src/mage/cards/r/RepeatOffender.java
Normal file
74
Mage.Sets/src/mage/cards/r/RepeatOffender.java
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
package mage.cards.r;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubType;
|
||||
import mage.counters.CounterType;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class RepeatOffender extends CardImpl {
|
||||
|
||||
public RepeatOffender(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}");
|
||||
|
||||
this.subtype.add(SubType.HUMAN);
|
||||
this.subtype.add(SubType.ASSASSIN);
|
||||
this.power = new MageInt(2);
|
||||
this.toughness = new MageInt(1);
|
||||
|
||||
// {2}{B}: If Repeat Offender is suspected, put a +1/+1 counter on it. Otherwise, suspect it.
|
||||
this.addAbility(new SimpleActivatedAbility(new RepeatOffenderEffect(), new ManaCostsImpl<>("{2}{B}")));
|
||||
}
|
||||
|
||||
private RepeatOffender(final RepeatOffender card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepeatOffender copy() {
|
||||
return new RepeatOffender(this);
|
||||
}
|
||||
}
|
||||
|
||||
class RepeatOffenderEffect extends OneShotEffect {
|
||||
|
||||
RepeatOffenderEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "if {this} is suspected, put a +1/+1 counter on it. Otherwise, suspect it";
|
||||
}
|
||||
|
||||
private RepeatOffenderEffect(final RepeatOffenderEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepeatOffenderEffect copy() {
|
||||
return new RepeatOffenderEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
|
||||
if (permanent == null) {
|
||||
return false;
|
||||
}
|
||||
if (permanent.isSuspected()) {
|
||||
return permanent.addCounters(CounterType.P1P1.createInstance(), source, game);
|
||||
}
|
||||
permanent.setSuspected(true, game, source);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,9 @@ public final class MurdersAtKarlovManor extends ExpansionSet {
|
|||
this.hasBasicLands = true;
|
||||
this.hasBoosters = false; // temporary
|
||||
|
||||
cards.add(new SetCardInfo("Agrus Kos, Spirit of Justice", 184, Rarity.MYTHIC, mage.cards.a.AgrusKosSpiritOfJustice.class));
|
||||
cards.add(new SetCardInfo("Aftermath Analyst", 148, Rarity.UNCOMMON, mage.cards.a.AftermathAnalyst.class));
|
||||
cards.add(new SetCardInfo("Airtight Alibi", 149, Rarity.COMMON, mage.cards.a.AirtightAlibi.class));
|
||||
cards.add(new SetCardInfo("Alley Assailant", 76, Rarity.COMMON, mage.cards.a.AlleyAssailant.class));
|
||||
cards.add(new SetCardInfo("Alquist Proft, Master Sleuth", 185, Rarity.MYTHIC, mage.cards.a.AlquistProftMasterSleuth.class));
|
||||
cards.add(new SetCardInfo("Anzrag, the Quake-Mole", 186, Rarity.MYTHIC, mage.cards.a.AnzragTheQuakeMole.class));
|
||||
|
|
@ -45,6 +47,7 @@ public final class MurdersAtKarlovManor extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Cold Case Cracker", 46, Rarity.COMMON, mage.cards.c.ColdCaseCracker.class));
|
||||
cards.add(new SetCardInfo("Commercial District", 259, Rarity.RARE, mage.cards.c.CommercialDistrict.class));
|
||||
cards.add(new SetCardInfo("Concealed Weapon", 117, Rarity.UNCOMMON, mage.cards.c.ConcealedWeapon.class));
|
||||
cards.add(new SetCardInfo("Convenient Target", 119, Rarity.UNCOMMON, mage.cards.c.ConvenientTarget.class));
|
||||
cards.add(new SetCardInfo("Crime Novelist", 121, Rarity.UNCOMMON, mage.cards.c.CrimeNovelist.class));
|
||||
cards.add(new SetCardInfo("Culvert Ambusher", 158, Rarity.UNCOMMON, mage.cards.c.CulvertAmbusher.class));
|
||||
cards.add(new SetCardInfo("Curious Cadaver", 194, Rarity.UNCOMMON, mage.cards.c.CuriousCadaver.class));
|
||||
|
|
@ -124,6 +127,7 @@ public final class MurdersAtKarlovManor extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Rakish Scoundrel", 225, Rarity.COMMON, mage.cards.r.RakishScoundrel.class));
|
||||
cards.add(new SetCardInfo("Raucous Theater", 266, Rarity.RARE, mage.cards.r.RaucousTheater.class));
|
||||
cards.add(new SetCardInfo("Red Herring", 142, Rarity.COMMON, mage.cards.r.RedHerring.class));
|
||||
cards.add(new SetCardInfo("Repeat Offender", 101, Rarity.COMMON, mage.cards.r.RepeatOffender.class));
|
||||
cards.add(new SetCardInfo("Riftburst Hellion", 228, Rarity.COMMON, mage.cards.r.RiftburstHellion.class));
|
||||
cards.add(new SetCardInfo("Rot Farm Mortipede", 102, Rarity.COMMON, mage.cards.r.RotFarmMortipede.class));
|
||||
cards.add(new SetCardInfo("Rubblebelt Maverick", 174, Rarity.COMMON, mage.cards.r.RubblebeltMaverick.class));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
package org.mage.test.cards.continuous;
|
||||
|
||||
import mage.abilities.keyword.MenaceAbility;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import mage.counters.CounterType;
|
||||
import mage.game.permanent.Permanent;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class SuspectTest extends CardTestPlayerBase {
|
||||
|
||||
private static final String offender = "Repeat Offender";
|
||||
private static final String bear = "Grizzly Bears";
|
||||
private static final String alibi = "Airtight Alibi";
|
||||
|
||||
private final void assertSuspected(String name, boolean suspected) {
|
||||
Permanent permanent = getPermanent(name);
|
||||
Assert.assertEquals("Permanent should " + (suspected ? "" : "not ") + "be suspected", suspected, permanent.isSuspected());
|
||||
if (suspected) {
|
||||
permanent.getAbilities().containsClass(MenaceAbility.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoSuspect() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, offender);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertSuspected(offender, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuspect() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
|
||||
addCard(Zone.BATTLEFIELD, playerA, offender);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{B}");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertSuspected(offender, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsSuspected() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3 + 3);
|
||||
addCard(Zone.BATTLEFIELD, playerA, offender);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{B}");
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{B}");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertSuspected(offender, true);
|
||||
assertCounterCount(playerA, offender, CounterType.P1P1, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCantBlock() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
|
||||
addCard(Zone.BATTLEFIELD, playerA, offender);
|
||||
addCard(Zone.BATTLEFIELD, playerB, bear);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{B}");
|
||||
|
||||
attack(2, playerB, bear);
|
||||
block(2, playerA, offender, bear);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(2, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertSuspected(offender, true);
|
||||
assertTapped(bear, true);
|
||||
assertLife(playerA, 20 - 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlibiRemoveSuspect() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Bayou", 3 + 3);
|
||||
addCard(Zone.BATTLEFIELD, playerA, offender);
|
||||
addCard(Zone.HAND, playerA, alibi);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{B}");
|
||||
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, alibi, offender);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertSuspected(offender, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlibiPreventSuspect() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Bayou", 3 + 3);
|
||||
addCard(Zone.BATTLEFIELD, playerA, offender);
|
||||
addCard(Zone.HAND, playerA, alibi);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, alibi, offender);
|
||||
|
||||
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{2}{B}");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertSuspected(offender, false);
|
||||
assertCounterCount(offender, CounterType.P1P1, 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package mage.abilities.effects;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.keyword.MenaceAbility;
|
||||
import mage.constants.*;
|
||||
import mage.counters.AbilityCounter;
|
||||
import mage.counters.BoostCounter;
|
||||
|
|
@ -9,14 +10,16 @@ import mage.game.permanent.Permanent;
|
|||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
* <p>
|
||||
* Applies boost from from boost counters and also adds abilities from ability counters and suspected mechanic
|
||||
*/
|
||||
public class ApplyCountersEffect extends ContinuousEffectImpl {
|
||||
public class ApplyStatusEffect extends ContinuousEffectImpl {
|
||||
|
||||
ApplyCountersEffect() {
|
||||
ApplyStatusEffect() {
|
||||
super(Duration.EndOfGame, Outcome.BoostCreature);
|
||||
}
|
||||
|
||||
private ApplyCountersEffect(ApplyCountersEffect effect) {
|
||||
private ApplyStatusEffect(ApplyStatusEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
|
|
@ -32,6 +35,9 @@ public class ApplyCountersEffect extends ContinuousEffectImpl {
|
|||
for (AbilityCounter counter : permanent.getCounters(game).getAbilityCounters()) {
|
||||
permanent.addAbility(counter.getAbility(), source == null ? permanent.getId() : source.getSourceId(), game);
|
||||
}
|
||||
if (permanent.isSuspected()) {
|
||||
permanent.addAbility(new MenaceAbility(false), source == null ? permanent.getId() : source.getSourceId(), game);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (layer == Layer.PTChangingEffects_7 && sublayer == SubLayer.Counters_7d) {
|
||||
|
|
@ -51,7 +57,7 @@ public class ApplyCountersEffect extends ContinuousEffectImpl {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ApplyCountersEffect copy() {
|
||||
return new ApplyCountersEffect(this);
|
||||
public ApplyStatusEffect copy() {
|
||||
return new ApplyStatusEffect(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package mage.abilities.effects;
|
||||
|
||||
import mage.ApprovingObject;
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.MageSingleton;
|
||||
|
|
@ -9,8 +8,6 @@ import mage.abilities.StaticAbility;
|
|||
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
|
||||
import mage.abilities.effects.common.continuous.CommanderReplacementEffect;
|
||||
import mage.cards.*;
|
||||
import mage.choices.Choice;
|
||||
import mage.choices.ChoiceImpl;
|
||||
import mage.constants.*;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.filter.predicate.Predicate;
|
||||
|
|
@ -55,19 +52,19 @@ public class ContinuousEffects implements Serializable {
|
|||
|
||||
private final Map<AsThoughEffectType, ContinuousEffectsList<AsThoughEffect>> asThoughEffectsMap = new EnumMap<>(AsThoughEffectType.class);
|
||||
public final List<ContinuousEffectsList<?>> allEffectsLists = new ArrayList<>(); // contains refs to real effect's list
|
||||
private final ApplyCountersEffect applyCounters;
|
||||
private final ApplyStatusEffect applyStatus;
|
||||
private final AuraReplacementEffect auraReplacementEffect;
|
||||
|
||||
private final Map<String, ContinuousEffectsList<ContinuousEffect>> lastEffectsListOnLayer = new HashMap<>(); // helps to find out new effect timestamps on layers
|
||||
|
||||
public ContinuousEffects() {
|
||||
applyCounters = new ApplyCountersEffect();
|
||||
applyStatus = new ApplyStatusEffect();
|
||||
auraReplacementEffect = new AuraReplacementEffect();
|
||||
collectAllEffects();
|
||||
}
|
||||
|
||||
protected ContinuousEffects(final ContinuousEffects effect) {
|
||||
applyCounters = effect.applyCounters.copy();
|
||||
applyStatus = effect.applyStatus.copy();
|
||||
auraReplacementEffect = effect.auraReplacementEffect.copy();
|
||||
layeredEffects = effect.layeredEffects.copy();
|
||||
continuousRuleModifyingEffects = effect.continuousRuleModifyingEffects.copy();
|
||||
|
|
@ -995,7 +992,7 @@ public class ContinuousEffects implements Serializable {
|
|||
boolean done = false;
|
||||
Map<ContinuousEffect, Set<UUID>> waitingEffects = new LinkedHashMap<>();
|
||||
Set<UUID> appliedEffects = new HashSet<>();
|
||||
applyCounters.apply(Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, null, game);
|
||||
applyStatus.apply(Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, null, game);
|
||||
activeLayerEffects = getLayeredEffects(game, "layer_6");
|
||||
|
||||
while (!done) { // loop needed if a added effect adds again an effect (e.g. Level 5- of Joraga Treespeaker)
|
||||
|
|
@ -1108,7 +1105,7 @@ public class ContinuousEffects implements Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
applyCounters.apply(Layer.PTChangingEffects_7, SubLayer.Counters_7d, null, game);
|
||||
applyStatus.apply(Layer.PTChangingEffects_7, SubLayer.Counters_7d, null, game);
|
||||
|
||||
for (ContinuousEffect effect : layer) {
|
||||
Set<Ability> abilities = layeredEffects.getAbility(effect.getId());
|
||||
|
|
@ -1410,7 +1407,7 @@ public class ContinuousEffects implements Serializable {
|
|||
for (Map.Entry<AsThoughEffectType, ContinuousEffectsList<AsThoughEffect>> entry : asThoughEffectsMap.entrySet()) {
|
||||
logger.info("... " + entry.getKey().toString() + ": " + entry.getValue().size());
|
||||
}
|
||||
logger.info("applyCounters ....................: " + (applyCounters != null ? "exists" : "null"));
|
||||
logger.info("applyStatus ....................: " + (applyStatus != null ? "exists" : "null"));
|
||||
logger.info("auraReplacementEffect ............: " + (continuousRuleModifyingEffects != null ? "exists" : "null"));
|
||||
Map<String, TraceInfo> orderedEffects = new TreeMap<>();
|
||||
traceAddContinuousEffects(orderedEffects, layeredEffects, game, "layeredEffects................");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package mage.filter.predicate.permanent;
|
||||
|
||||
import mage.filter.predicate.Predicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public enum SuspectedPredicate implements Predicate<Permanent> {
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public boolean apply(Permanent input, Game game) {
|
||||
return input.isSuspected();
|
||||
}
|
||||
}
|
||||
|
|
@ -1993,7 +1993,7 @@ public abstract class GameImpl implements Game {
|
|||
}
|
||||
newBluePrint.assignNewId();
|
||||
if (copyFromPermanent.isTransformed()) {
|
||||
TransformAbility.transformPermanent(newBluePrint,this, source);
|
||||
TransformAbility.transformPermanent(newBluePrint, this, source);
|
||||
}
|
||||
if (copyFromPermanent.isPrototyped()) {
|
||||
Abilities<Ability> abilities = copyFromPermanent.getAbilities();
|
||||
|
|
@ -3552,8 +3552,9 @@ public abstract class GameImpl implements Game {
|
|||
public Map<MageObjectReference, Map<String, Object>> getPermanentCostsTags() {
|
||||
return state.getPermanentCostsTags();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePermanentCostsTags(MageObjectReference permanentMOR, Ability source){
|
||||
public void storePermanentCostsTags(MageObjectReference permanentMOR, Ability source) {
|
||||
state.storePermanentCostsTags(permanentMOR, source);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -567,6 +567,12 @@ public class GameEvent implements Serializable {
|
|||
playerId the player crafting
|
||||
*/
|
||||
EXILED_WHILE_CRAFTING,
|
||||
/* Become suspected
|
||||
targetId the permanent being suspected
|
||||
sourceId of the ability suspecting
|
||||
playerId the player suspecting
|
||||
*/
|
||||
BECOME_SUSPECTED,
|
||||
//custom events
|
||||
CUSTOM_EVENT
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,10 @@ public interface Permanent extends Card, Controllable {
|
|||
|
||||
void setRenowned(boolean value);
|
||||
|
||||
boolean isSuspected();
|
||||
|
||||
void setSuspected(boolean value, Game game, Ability source);
|
||||
|
||||
boolean isPrototyped();
|
||||
|
||||
void setPrototyped(boolean value);
|
||||
|
|
@ -222,6 +226,7 @@ public interface Permanent extends Card, Controllable {
|
|||
* @return can be null for exists abilities
|
||||
*/
|
||||
Ability addAbility(Ability ability, UUID sourceId, Game game);
|
||||
|
||||
Ability addAbility(Ability ability, UUID sourceId, Game game, boolean fromExistingObject);
|
||||
|
||||
void removeAllAbilities(UUID sourceId, Game game);
|
||||
|
|
@ -313,7 +318,7 @@ public interface Permanent extends Card, Controllable {
|
|||
/**
|
||||
* Fast check for attacking possibilities (is it possible to attack permanent/planeswalker/battle)
|
||||
*
|
||||
* @param attackerId creature to attack, can be null
|
||||
* @param attackerId creature to attack, can be null
|
||||
* @param defendingPlayerId defending player
|
||||
* @param game
|
||||
* @return
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
protected boolean transformed;
|
||||
protected boolean monstrous;
|
||||
protected boolean renowned;
|
||||
protected boolean suspected;
|
||||
protected boolean manifested = false;
|
||||
protected boolean morphed = false;
|
||||
protected boolean ringBearerFlag = false;
|
||||
|
|
@ -162,6 +163,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
this.transformed = permanent.transformed;
|
||||
this.monstrous = permanent.monstrous;
|
||||
this.renowned = permanent.renowned;
|
||||
this.suspected = permanent.suspected;
|
||||
this.ringBearerFlag = permanent.ringBearerFlag;
|
||||
this.classLevel = permanent.classLevel;
|
||||
this.goadingPlayers.addAll(permanent.goadingPlayers);
|
||||
|
|
@ -392,8 +394,9 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
/**
|
||||
* Add an ability to the permanent. When copying from an existing source
|
||||
* you should use the fromExistingObject variant of this function to prevent double-copying subabilities
|
||||
* @param ability The ability to be added
|
||||
* @param sourceId id of the source doing the added (for the effect created to add it)
|
||||
*
|
||||
* @param ability The ability to be added
|
||||
* @param sourceId id of the source doing the added (for the effect created to add it)
|
||||
* @param game
|
||||
* @return The newly added ability copy
|
||||
*/
|
||||
|
|
@ -403,11 +406,11 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param ability The ability to be added
|
||||
* @param sourceId id of the source doing the added (for the effect created to add it)
|
||||
* @param ability The ability to be added
|
||||
* @param sourceId id of the source doing the added (for the effect created to add it)
|
||||
* @param game
|
||||
* @param fromExistingObject if copying abilities from an existing source then must ignore sub-abilities because they're already on the source object
|
||||
* Otherwise sub-abilities will be added twice to the resulting object
|
||||
* Otherwise sub-abilities will be added twice to the resulting object
|
||||
* @return The newly added ability copy
|
||||
*/
|
||||
@Override
|
||||
|
|
@ -1490,7 +1493,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
|
||||
@Override
|
||||
public boolean canBlock(UUID attackerId, Game game) {
|
||||
if (tapped && game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, null, this.getControllerId(), game).isEmpty() || isBattle(game)) {
|
||||
if (tapped && game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, null, this.getControllerId(), game).isEmpty() || isBattle(game) || isSuspected()) {
|
||||
return false;
|
||||
}
|
||||
Permanent attacker = game.getPermanent(attackerId);
|
||||
|
|
@ -1671,6 +1674,28 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
this.renowned = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuspected() {
|
||||
return suspected;
|
||||
}
|
||||
|
||||
private static final String suspectedInfoKey = "IS_SUSPECTED";
|
||||
|
||||
@Override
|
||||
public void setSuspected(boolean value, Game game, Ability source) {
|
||||
if (!value || !game.replaceEvent(GameEvent.getEvent(
|
||||
EventType.BECOME_SUSPECTED, getId(),
|
||||
source, source.getControllerId()
|
||||
))) {
|
||||
this.suspected = value;
|
||||
}
|
||||
if (this.suspected) {
|
||||
addInfo(suspectedInfoKey, CardUtil.addToolTipMarkTags("Suspected (has menace and can't block)"), game);
|
||||
} else {
|
||||
addInfo(suspectedInfoKey, null, game);
|
||||
}
|
||||
}
|
||||
|
||||
// Used as key for the ring bearer info.
|
||||
private static final String ringbearerInfoKey = "IS_RINGBEARER";
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue