This commit is contained in:
Oleg Agafonov 2021-09-25 18:09:21 +04:00
parent b895611c4d
commit 95b782d519
5 changed files with 180 additions and 52 deletions

View file

@ -24,11 +24,8 @@ public final class PeaceTalks extends CardImpl {
public PeaceTalks(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{W}");
// This turn and next turn, creatures can't attack,
// and players and permanents can't be the targets
// of spells or activated abilities.
// This turn and next turn, creatures can't attack, and players and permanents can't be the targets of spells or activated abilities.
this.getSpellAbility().addEffect(new PeaceTalksEffect());
}
private PeaceTalks(final PeaceTalks card) {

View file

@ -3,13 +3,14 @@ package org.mage.test.cards.abilities.keywords;
import mage.abilities.keyword.HexproofAbility;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Assert;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps;
/**
* @author LevelX2
* @author LevelX2, JayDi85
*/
public class HexproofTest extends CardTestPlayerBase {
public class HexproofTest extends CardTestPlayerBaseWithAIHelps {
/**
* Tests one target gets hexproof
@ -103,4 +104,113 @@ public class HexproofTest extends CardTestPlayerBase {
assertPermanentCount(playerA, "Knight of Grace", 0);
}
@Test
public void test_Human_CanTargetValid() {
// +1: Target player discards a card.
addCard(Zone.BATTLEFIELD, playerA, "Liliana Vess", 1);
addCard(Zone.HAND, playerA, "Balduvian Bears", 1);
addCard(Zone.HAND, playerA, "Swamp", 1);
addCard(Zone.HAND, playerB, "Matter Reshaper", 1);
addCard(Zone.HAND, playerB, "Mountain", 1);
//
// You have hexproof. (You can't be the target of spells or abilities your opponents control.)
addCard(Zone.BATTLEFIELD, playerB, "Leyline of Sanctity", 1);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1:");
addTarget(playerA, playerA);
setChoice(playerA, "Swamp");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, "Swamp", 1);
}
@Test
public void test_Human_CantTargetInvalid() {
// +1: Target player discards a card.
addCard(Zone.BATTLEFIELD, playerA, "Liliana Vess", 1);
addCard(Zone.HAND, playerA, "Balduvian Bears", 1);
addCard(Zone.HAND, playerA, "Swamp", 1);
addCard(Zone.HAND, playerB, "Matter Reshaper", 1);
addCard(Zone.HAND, playerB, "Mountain", 1);
//
// You have hexproof. (You can't be the target of spells or abilities your opponents control.)
addCard(Zone.BATTLEFIELD, playerB, "Leyline of Sanctity", 1);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1:");
addTarget(playerA, playerB);
try {
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
Assert.fail("must throw exception on execute");
} catch (Throwable e) {
if (!e.getMessage().contains("setup good targets")) {
Assert.fail("must thow error about bad targets, but got:\n" + e.getMessage());
}
}
}
@Test
public void test_AI_MustTargetOnlyValid() {
// +1: Target player discards a card.
addCard(Zone.BATTLEFIELD, playerA, "Liliana Vess", 1);
addCard(Zone.HAND, playerA, "Balduvian Bears", 1);
addCard(Zone.HAND, playerA, "Swamp", 1);
addCard(Zone.HAND, playerB, "Matter Reshaper", 1);
addCard(Zone.HAND, playerB, "Mountain", 1);
//
// You have hexproof. (You can't be the target of spells or abilities your opponents control.)
addCard(Zone.BATTLEFIELD, playerB, "Leyline of Sanctity", 1);
// ai must not use +1 on itself (due bad score) and must not use on opponent (due hexproof)
aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
// no discarded cards
assertGraveyardCount(playerA, 0);
assertGraveyardCount(playerB, 0);
}
@Test
public void test_RulesModificationForPlayers() {
// This turn and next turn, creatures can't attack, and players and permanents can't be the targets
// of spells or activated abilities.
addCard(Zone.HAND, playerA, "Peace Talks", 1); // {1}{W}
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
//
addCard(Zone.HAND, playerA, "Lightning Bolt");
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Lightning Bolt", true);
// activate restriction
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {W}", 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Peace Talks");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
// playable doesn't check illegal targets, so it will be active
// ai can cast on turn 3 only
aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA);
checkLife("after 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, 20);
aiPlayStep(2, PhaseStep.PRECOMBAT_MAIN, playerA);
checkLife("after 2", 2, PhaseStep.POSTCOMBAT_MAIN, playerB, 20);
aiPlayStep(3, PhaseStep.PRECOMBAT_MAIN, playerA);
checkLife("after 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerB, 20 - 3);
setStrictChooseMode(true);
setStopAt(3, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
}
}

View file

@ -2,6 +2,7 @@ package mage.game.events;
import mage.abilities.Ability;
import mage.cards.Card;
import mage.players.Player;
import java.util.UUID;
@ -20,6 +21,11 @@ public class TargetEvent extends GameEvent {
this.setSourceId(sourceId);
}
public TargetEvent(Player target, UUID sourceId, UUID sourceControllerId) {
super(GameEvent.EventType.TARGET, target.getId(), null, sourceControllerId);
this.setSourceId(sourceId);
}
/**
* @param targetId
* @param source

View file

@ -1117,8 +1117,9 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
return false;
}
}
if (game.getPlayer(this.getControllerId()).hasOpponent(sourceControllerId, game)
&& game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) == null
&& null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game)
&& abilities.stream()
.filter(HexproofBaseAbility.class::isInstance)
.map(HexproofBaseAbility.class::cast)
@ -1129,9 +1130,14 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
if (hasProtectionFrom(source, game)) {
return false;
}
// needed to get the correct possible targets if target rule modification effects are active
// e.g. Fiendslayer Paladin tried to target with Ultimate Price
return !game.getContinuousEffects().preventedByRuleModification(new TargetEvent(this, source.getId(), sourceControllerId), null, game, true);
// example: Fiendslayer Paladin tried to target with Ultimate Price
return !game.getContinuousEffects().preventedByRuleModification(
new TargetEvent(this, source.getId(), sourceControllerId),
null,
game,
true
);
}
return true;

View file

@ -634,24 +634,32 @@ public abstract class PlayerImpl implements Player, Serializable {
return false;
}
if (source != null) {
// there is only variant of shroud, so check the instance and any asthougheffects that would ignore it
if (abilities.containsKey(ShroudAbility.getInstance().getId())
&& game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.SHROUD, null, sourceControllerId, game) == null) {
&& null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.SHROUD, null, sourceControllerId, game)) {
return false;
}
// check for all variants of hexproof and any asthougheffects that would ignore it
// TODO there may be "prevented by rule-modification" effects, so add them if known
if (sourceControllerId != null
&& this.hasOpponent(sourceControllerId, game)) {
for (Ability a : abilities) {
if (a instanceof HexproofBaseAbility
&& ((HexproofBaseAbility) a).checkObject(source, game)
&& game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) == null) {
&& this.hasOpponent(sourceControllerId, game)
&& null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game)
&& abilities.stream()
.filter(HexproofBaseAbility.class::isInstance)
.map(HexproofBaseAbility.class::cast)
.anyMatch(ability -> ability.checkObject(source, game))) {
return false;
}
if (hasProtectionFrom(source, game)) {
return false;
}
}
return !hasProtectionFrom(source, game);
// example: Peace Talks
return !game.getContinuousEffects().preventedByRuleModification(
new TargetEvent(this, source.getId(), sourceControllerId),
null,
game,
true
);
}
return true;
}
@ -3877,7 +3885,8 @@ public abstract class PlayerImpl implements Player, Serializable {
/**
* Returns a list of all available spells and abilities the player can
* currently cast/activate with his available resources
* currently cast/activate with his available resources.
* Without target validation.
*
* @param game
* @param hidden also from hidden objects (e.g. turned face down cards ?)