mirror of
https://github.com/correl/mage.git
synced 2024-12-25 03:00:15 +00:00
Reworking goad effects (ready for review) (#8034)
* changing goad to designation, refactored goad effects to be continuous * [AFC] Implemented Vengeful Ancestor * reworked effects which goad an attached creature * updated goading implementation * updated combat with new goad logic * some more changes, added a test * another fix * update to test, still fails * added more failing tests * more failing tests * added additional goad check * small fix to two tests (still failing * added a regular combat test (passes and fails randomly) * fixed bug in computer player random selection * some changes to how TargetDefender is handled * removed unnecessary class * more combat fixes, tests pass now * removed tests which no longer work due to combat changes * small merge fix * [NEC] Implemented Komainu Battle Armor * [NEC] Implemented Kaima, the Fractured Calm * [NEC] added all variants
This commit is contained in:
parent
5725873aeb
commit
4591ac07cc
30 changed files with 812 additions and 438 deletions
|
@ -976,24 +976,8 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
}
|
||||
|
||||
if (target.getOriginalTarget() instanceof TargetDefender) {
|
||||
// TODO: Improve, now planeswalker is always chosen if it exits
|
||||
List<Permanent> targets;
|
||||
targets = game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_PLANESWALKER, randomOpponentId, game);
|
||||
if (targets != null && !targets.isEmpty()) {
|
||||
for (Permanent planeswalker : targets) {
|
||||
if (target.canTarget(abilityControllerId, planeswalker.getId(), source, game)) {
|
||||
target.addTarget(planeswalker.getId(), source, game);
|
||||
}
|
||||
if (target.isChosen()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!target.isChosen()) {
|
||||
if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) {
|
||||
target.addTarget(randomOpponentId, source, game);
|
||||
}
|
||||
}
|
||||
UUID randomDefender = RandomUtil.randomFromCollection(possibleTargets);
|
||||
target.addTarget(randomDefender, source, game);
|
||||
return target.isChosen();
|
||||
}
|
||||
|
||||
|
@ -2997,21 +2981,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
* @return
|
||||
*/
|
||||
private UUID getRandomOpponent(UUID abilityControllerId, Game game) {
|
||||
UUID randomOpponentId = null;
|
||||
Set<UUID> opponents = game.getOpponents(abilityControllerId);
|
||||
if (opponents.size() > 1) {
|
||||
int rand = RandomUtil.nextInt(opponents.size());
|
||||
int count = 0;
|
||||
for (UUID currentId : opponents) {
|
||||
if (count == rand) {
|
||||
randomOpponentId = currentId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (opponents.size() == 1) {
|
||||
randomOpponentId = game.getOpponents(abilityControllerId).iterator().next();
|
||||
}
|
||||
return randomOpponentId;
|
||||
return RandomUtil.randomFromCollection(game.getOpponents(abilityControllerId));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1737,7 +1737,6 @@ public class HumanPlayer extends PlayerImpl {
|
|||
return true;
|
||||
} else {
|
||||
TargetDefender target = new TargetDefender(possibleDefender, attackerId);
|
||||
target.setNotTarget(true); // player or planswalker hexproof does not prevent attacking a player
|
||||
if (forcedToAttack) {
|
||||
StringBuilder sb = new StringBuilder(target.getTargetName());
|
||||
Permanent attacker = game.getPermanent(attackerId);
|
||||
|
@ -1757,7 +1756,6 @@ public class HumanPlayer extends PlayerImpl {
|
|||
|
||||
protected UUID selectDefenderForAllAttack(Set<UUID> defenders, Game game) {
|
||||
TargetDefender target = new TargetDefender(defenders, null);
|
||||
target.setNotTarget(true); // player or planswalker hexproof does not prevent attacking a player
|
||||
if (chooseTarget(Outcome.Damage, target, null, game)) {
|
||||
return getFixedResponseUUID(game);
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ class AgitatorAntEffect extends OneShotEffect {
|
|||
if (permanent == null || !permanent.addCounters(CounterType.P1P1.createInstance(2), player.getId(), source, game)) {
|
||||
continue;
|
||||
}
|
||||
new GoadTargetEffect().setTargetPointer(new FixedTarget(permanent, game)).apply(game, source);
|
||||
game.addEffect(new GoadTargetEffect().setTargetPointer(new FixedTarget(permanent, game)), source);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
package mage.cards.b;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.ContinuousEffect;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.UntapTargetEffect;
|
||||
import mage.abilities.effects.common.combat.GoadTargetEffect;
|
||||
|
@ -20,8 +17,9 @@ import mage.target.common.TargetCreaturePermanent;
|
|||
import mage.target.targetpointer.FixedTarget;
|
||||
import mage.target.targetpointer.TargetPointer;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class Besmirch extends CardImpl {
|
||||
|
@ -66,24 +64,19 @@ class BesmirchEffect extends OneShotEffect {
|
|||
TargetPointer target = new FixedTarget(source.getFirstTarget(), game);
|
||||
|
||||
// gain control
|
||||
ContinuousEffect effect = new GainControlTargetEffect(Duration.EndOfTurn);
|
||||
effect.setTargetPointer(target);
|
||||
game.addEffect(effect, source);
|
||||
game.addEffect(new GainControlTargetEffect(Duration.EndOfTurn)
|
||||
.setTargetPointer(target), source);
|
||||
|
||||
// haste
|
||||
effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn);
|
||||
effect.setTargetPointer(target);
|
||||
game.addEffect(effect, source);
|
||||
game.addEffect(new GainAbilityTargetEffect(
|
||||
HasteAbility.getInstance(), Duration.EndOfTurn
|
||||
).setTargetPointer(target), source);
|
||||
|
||||
// goad
|
||||
Effect effect2 = new GoadTargetEffect();
|
||||
effect2.setTargetPointer(target);
|
||||
effect2.apply(game, source);
|
||||
game.addEffect(new GoadTargetEffect().setTargetPointer(target), source);
|
||||
|
||||
// untap
|
||||
effect2 = new UntapTargetEffect();
|
||||
effect2.setTargetPointer(target);
|
||||
effect2.apply(game, source);
|
||||
new UntapTargetEffect().setTargetPointer(target).apply(game, source);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -2,9 +2,10 @@ package mage.cards.b;
|
|||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.ActivateAsSorceryActivatedAbility;
|
||||
import mage.abilities.common.GoadAttachedAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.effects.common.AttachEffect;
|
||||
import mage.abilities.effects.common.combat.GoadAttachedEffect;
|
||||
import mage.abilities.effects.common.continuous.BoostEquippedEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
|
@ -27,10 +28,12 @@ public final class BloodthirstyBlade extends CardImpl {
|
|||
this.subtype.add(SubType.EQUIPMENT);
|
||||
|
||||
// Equipped creature gets +2/+0 and is goaded.
|
||||
this.addAbility(new GoadAttachedAbility(new BoostEquippedEffect(2, 0)));
|
||||
Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(2, 0));
|
||||
ability.addEffect(new GoadAttachedEffect());
|
||||
this.addAbility(ability);
|
||||
|
||||
// {1}: Attach Bloodthirsty Blade to target creature an opponent controls. Active this ability only any time you could cast a sorcery.
|
||||
Ability ability = new ActivateAsSorceryActivatedAbility(
|
||||
ability = new ActivateAsSorceryActivatedAbility(
|
||||
Zone.BATTLEFIELD,
|
||||
new AttachEffect(
|
||||
Outcome.Detriment, "Attach {this} to target creature an opponent controls"
|
||||
|
|
|
@ -74,7 +74,7 @@ class GeodeRagerEffect extends OneShotEffect {
|
|||
if (permanent == null) {
|
||||
continue;
|
||||
}
|
||||
new GoadTargetEffect().setTargetPointer(new FixedTarget(permanent, game)).apply(game, source);
|
||||
game.addEffect(new GoadTargetEffect().setTargetPointer(new FixedTarget(permanent, game)), source);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
94
Mage.Sets/src/mage/cards/k/KaimaTheFracturedCalm.java
Normal file
94
Mage.Sets/src/mage/cards/k/KaimaTheFracturedCalm.java
Normal file
|
@ -0,0 +1,94 @@
|
|||
package mage.cards.k;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.combat.GoadTargetEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class KaimaTheFracturedCalm extends CardImpl {
|
||||
|
||||
public KaimaTheFracturedCalm(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{G}");
|
||||
|
||||
this.addSuperType(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.SPIRIT);
|
||||
this.power = new MageInt(3);
|
||||
this.toughness = new MageInt(3);
|
||||
|
||||
// At the beginning of your end step, goad each creature your opponents control that's enchanted by an Aura you control. Put a +1/+1 counter on Kaima, the Fractured Calm for each creature goaded this way.
|
||||
this.addAbility(new BeginningOfUpkeepTriggeredAbility(
|
||||
new KaimaTheFracturedCalmEffect(), TargetController.YOU, false
|
||||
));
|
||||
}
|
||||
|
||||
private KaimaTheFracturedCalm(final KaimaTheFracturedCalm card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KaimaTheFracturedCalm copy() {
|
||||
return new KaimaTheFracturedCalm(this);
|
||||
}
|
||||
}
|
||||
|
||||
class KaimaTheFracturedCalmEffect extends OneShotEffect {
|
||||
|
||||
KaimaTheFracturedCalmEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "goad each creature your opponents control that's enchanted by an Aura you control. " +
|
||||
"Put a +1/+1 counter on {this} for each creature goaded this way";
|
||||
}
|
||||
|
||||
private KaimaTheFracturedCalmEffect(final KaimaTheFracturedCalmEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KaimaTheFracturedCalmEffect copy() {
|
||||
return new KaimaTheFracturedCalmEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
int goaded = 0;
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(
|
||||
StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE,
|
||||
source.getControllerId(), source.getSourceId(), game
|
||||
)) {
|
||||
if (permanent
|
||||
.getAttachments()
|
||||
.stream()
|
||||
.map(game::getPermanent)
|
||||
.filter(Objects::nonNull)
|
||||
.noneMatch(p -> p.isControlledBy(source.getControllerId())
|
||||
&& p.hasSubtype(SubType.AURA, game))) {
|
||||
continue;
|
||||
}
|
||||
game.addEffect(new GoadTargetEffect().setTargetPointer(new FixedTarget(permanent, game)), source);
|
||||
goaded++;
|
||||
}
|
||||
if (goaded < 1) {
|
||||
return false;
|
||||
}
|
||||
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
|
||||
if (permanent != null) {
|
||||
permanent.addCounters(CounterType.P1P1.createInstance(goaded), source, game);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
137
Mage.Sets/src/mage/cards/k/KomainuBattleArmor.java
Normal file
137
Mage.Sets/src/mage/cards/k/KomainuBattleArmor.java
Normal file
|
@ -0,0 +1,137 @@
|
|||
package mage.cards.k;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.combat.GoadTargetEffect;
|
||||
import mage.abilities.effects.common.continuous.BoostEquippedEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
|
||||
import mage.abilities.keyword.MenaceAbility;
|
||||
import mage.abilities.keyword.ReconfigureAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.DamagedEvent;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class KomainuBattleArmor extends CardImpl {
|
||||
|
||||
public KomainuBattleArmor(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}{R}");
|
||||
|
||||
this.subtype.add(SubType.EQUIPMENT);
|
||||
this.subtype.add(SubType.DOG);
|
||||
this.power = new MageInt(2);
|
||||
this.toughness = new MageInt(2);
|
||||
|
||||
// Menace
|
||||
this.addAbility(new MenaceAbility(false));
|
||||
|
||||
// Equipped creature gets +2/+2 and has menace.
|
||||
Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(2, 2));
|
||||
ability.addEffect(new GainAbilityAttachedEffect(
|
||||
new MenaceAbility(false), AttachmentType.EQUIPMENT
|
||||
).setText("and has menace"));
|
||||
this.addAbility(ability);
|
||||
|
||||
// Whenever Komainu Battle Armor or equipped creature deals combat damage to a player, goad each creature that player controls.
|
||||
this.addAbility(new KomainuBattleArmorTriggeredAbility());
|
||||
|
||||
// Reconfigure {4}
|
||||
this.addAbility(new ReconfigureAbility("{4}"));
|
||||
}
|
||||
|
||||
private KomainuBattleArmor(final KomainuBattleArmor card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KomainuBattleArmor copy() {
|
||||
return new KomainuBattleArmor(this);
|
||||
}
|
||||
}
|
||||
|
||||
class KomainuBattleArmorTriggeredAbility extends TriggeredAbilityImpl {
|
||||
|
||||
KomainuBattleArmorTriggeredAbility() {
|
||||
super(Zone.BATTLEFIELD, new KomainuBattleArmorEffect());
|
||||
}
|
||||
|
||||
private KomainuBattleArmorTriggeredAbility(final KomainuBattleArmorTriggeredAbility ability) {
|
||||
super(ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KomainuBattleArmorTriggeredAbility copy() {
|
||||
return new KomainuBattleArmorTriggeredAbility(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkEventType(GameEvent event, Game game) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
if (!((DamagedEvent) event).isCombatDamage()) {
|
||||
return false;
|
||||
}
|
||||
if (getSourceId().equals(event.getSourceId())) {
|
||||
getEffects().setTargetPointer(new FixedTarget(event.getTargetId()));
|
||||
return true;
|
||||
}
|
||||
Permanent permanent = getSourcePermanentOrLKI(game);
|
||||
if (permanent != null && event.getSourceId().equals(permanent.getAttachedTo())) {
|
||||
getEffects().setTargetPointer(new FixedTarget(event.getTargetId()));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "Whenever {this} or equipped creature deals combat damage to a player, goad each creature that player controls.";
|
||||
}
|
||||
}
|
||||
|
||||
class KomainuBattleArmorEffect extends OneShotEffect {
|
||||
|
||||
KomainuBattleArmorEffect() {
|
||||
super(Outcome.Benefit);
|
||||
}
|
||||
|
||||
private KomainuBattleArmorEffect(final KomainuBattleArmorEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KomainuBattleArmorEffect copy() {
|
||||
return new KomainuBattleArmorEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
UUID playerId = getTargetPointer().getFirst(game, source);
|
||||
if (playerId == null) {
|
||||
return false;
|
||||
}
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(
|
||||
StaticFilters.FILTER_CONTROLLED_CREATURE,
|
||||
playerId, source.getSourceId(), game
|
||||
)) {
|
||||
game.addEffect(new GoadTargetEffect().setTargetPointer(new FixedTarget(permanent, game)), source);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,12 +1,10 @@
|
|||
package mage.cards.m;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.combat.GoadTargetEffect;
|
||||
import mage.cards.CardImpl;
|
||||
|
@ -15,8 +13,11 @@ import mage.constants.*;
|
|||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
|
@ -86,8 +87,6 @@ class MarisiBreakerOfTheCoilSpellEffect extends ContinuousRuleModifyingEffectImp
|
|||
|
||||
class MarisiBreakerOfTheCoilEffect extends OneShotEffect {
|
||||
|
||||
private static final Effect effect = new GoadTargetEffect();
|
||||
|
||||
MarisiBreakerOfTheCoilEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "goad each creature that player controls "
|
||||
|
@ -105,13 +104,12 @@ class MarisiBreakerOfTheCoilEffect extends OneShotEffect {
|
|||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
game.getBattlefield().getAllActivePermanents(
|
||||
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(
|
||||
StaticFilters.FILTER_PERMANENT_CREATURE,
|
||||
targetPointer.getFirst(game, source), game
|
||||
).stream().forEach(permanent -> {
|
||||
effect.setTargetPointer(new FixedTarget(permanent, game));
|
||||
effect.apply(game, source);
|
||||
});
|
||||
)) {
|
||||
game.addEffect(new GoadTargetEffect().setTargetPointer(new FixedTarget(permanent, game)), source);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,9 @@ package mage.cards.m;
|
|||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.AttacksAttachedTriggeredAbility;
|
||||
import mage.abilities.common.GoadAttachedAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.common.AttachEffect;
|
||||
import mage.abilities.effects.common.combat.GoadAttachedEffect;
|
||||
import mage.abilities.effects.common.continuous.BoostAllEffect;
|
||||
import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
|
||||
import mage.abilities.keyword.EnchantAbility;
|
||||
|
@ -44,7 +45,9 @@ public final class MartialImpetus extends CardImpl {
|
|||
this.addAbility(ability);
|
||||
|
||||
// Enchanted creature gets +1/+1 and is goaded.
|
||||
this.addAbility(new GoadAttachedAbility(new BoostEnchantedEffect(1, 1)));
|
||||
ability = new SimpleStaticAbility(new BoostEnchantedEffect(1, 1));
|
||||
ability.addEffect(new GoadAttachedEffect());
|
||||
this.addAbility(ability);
|
||||
|
||||
// Whenever enchanted creature attacks, each other creature that's attacking one of your opponents gets +1/+1 until end of turn.
|
||||
this.addAbility(new AttacksAttachedTriggeredAbility(
|
||||
|
|
|
@ -2,10 +2,11 @@ package mage.cards.p;
|
|||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.AttacksAttachedTriggeredAbility;
|
||||
import mage.abilities.common.GoadAttachedAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.common.AttachEffect;
|
||||
import mage.abilities.effects.common.GainLifeEffect;
|
||||
import mage.abilities.effects.common.LoseLifeControllerAttachedEffect;
|
||||
import mage.abilities.effects.common.combat.GoadAttachedEffect;
|
||||
import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
|
||||
import mage.abilities.keyword.EnchantAbility;
|
||||
import mage.cards.CardImpl;
|
||||
|
@ -37,7 +38,9 @@ public final class ParasiticImpetus extends CardImpl {
|
|||
this.addAbility(ability);
|
||||
|
||||
// Enchanted creature gets +2/+2 and is goaded.
|
||||
this.addAbility(new GoadAttachedAbility(new BoostEnchantedEffect(2, 2)));
|
||||
ability = new SimpleStaticAbility(new BoostEnchantedEffect(2, 2));
|
||||
ability.addEffect(new GoadAttachedEffect());
|
||||
this.addAbility(ability);
|
||||
|
||||
// Whenever enchanted creature attacks, its controller loses 2 life and you gain 2 life.
|
||||
ability = new AttacksAttachedTriggeredAbility(
|
||||
|
|
|
@ -109,7 +109,6 @@ class PortalMageEffect extends OneShotEffect {
|
|||
}
|
||||
// Select the new defender
|
||||
TargetDefender target = new TargetDefender(defenders, null);
|
||||
target.setNotTarget(true); // player or planswalker hexproof does not prevent attacking a player
|
||||
if (controller.chooseTarget(Outcome.Damage, target, source, game)) {
|
||||
if (!combatGroupTarget.getDefenderId().equals(target.getFirstTarget())) {
|
||||
if (combatGroupTarget.changeDefenderPostDeclaration(target.getFirstTarget(), game)) {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package mage.cards.p;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.GoadAttachedAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.RequirementEffect;
|
||||
import mage.abilities.effects.common.AttachEffect;
|
||||
import mage.abilities.effects.common.combat.GoadAttachedEffect;
|
||||
import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
|
||||
import mage.abilities.keyword.EnchantAbility;
|
||||
import mage.cards.CardImpl;
|
||||
|
@ -37,11 +38,10 @@ public final class PredatoryImpetus extends CardImpl {
|
|||
this.addAbility(ability);
|
||||
|
||||
// Enchanted creature gets +3/+3, must be blocked if able, and is goaded.
|
||||
this.addAbility(new GoadAttachedAbility(
|
||||
new BoostEnchantedEffect(3, 3)
|
||||
.setText("Enchanted creature gets +3/+3"),
|
||||
new PredatoryImpetusEffect()
|
||||
));
|
||||
ability = new SimpleStaticAbility(new BoostEnchantedEffect(3, 3));
|
||||
ability.addEffect(new PredatoryImpetusEffect());
|
||||
ability.addEffect(new GoadAttachedEffect().concatBy(","));
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
private PredatoryImpetus(final PredatoryImpetus card) {
|
||||
|
|
|
@ -2,8 +2,9 @@ package mage.cards.p;
|
|||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.AttacksAttachedTriggeredAbility;
|
||||
import mage.abilities.common.GoadAttachedAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.common.AttachEffect;
|
||||
import mage.abilities.effects.common.combat.GoadAttachedEffect;
|
||||
import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
|
||||
import mage.abilities.effects.keyword.ScryEffect;
|
||||
import mage.abilities.keyword.EnchantAbility;
|
||||
|
@ -36,7 +37,9 @@ public final class PsychicImpetus extends CardImpl {
|
|||
this.addAbility(ability);
|
||||
|
||||
// Enchanted creature gets +2/+2 and is goaded.
|
||||
this.addAbility(new GoadAttachedAbility(new BoostEnchantedEffect(2, 2)));
|
||||
ability = new SimpleStaticAbility(new BoostEnchantedEffect(2, 2));
|
||||
ability.addEffect(new GoadAttachedEffect());
|
||||
this.addAbility(ability);
|
||||
|
||||
// Whenever enchanted creature attacks, you scry 2.
|
||||
this.addAbility(new AttacksAttachedTriggeredAbility(
|
||||
|
|
|
@ -2,9 +2,10 @@ package mage.cards.s;
|
|||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.AttacksAttachedTriggeredAbility;
|
||||
import mage.abilities.common.GoadAttachedAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.common.AttachEffect;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.effects.common.combat.GoadAttachedEffect;
|
||||
import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
|
||||
import mage.abilities.keyword.EnchantAbility;
|
||||
import mage.cards.CardImpl;
|
||||
|
@ -37,7 +38,9 @@ public final class ShinyImpetus extends CardImpl {
|
|||
this.addAbility(ability);
|
||||
|
||||
// Enchanted creature gets +2/+2 and is goaded.
|
||||
this.addAbility(new GoadAttachedAbility(new BoostEnchantedEffect(2, 2)));
|
||||
ability = new SimpleStaticAbility(new BoostEnchantedEffect(2, 2));
|
||||
ability.addEffect(new GoadAttachedEffect());
|
||||
this.addAbility(ability);
|
||||
|
||||
// Whenever enchanted creature attacks, you create a Treasure token.
|
||||
this.addAbility(new AttacksAttachedTriggeredAbility(
|
||||
|
|
106
Mage.Sets/src/mage/cards/v/VengefulAncestor.java
Normal file
106
Mage.Sets/src/mage/cards/v/VengefulAncestor.java
Normal file
|
@ -0,0 +1,106 @@
|
|||
package mage.cards.v;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.AttacksAllTriggeredAbility;
|
||||
import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.combat.GoadTargetEffect;
|
||||
import mage.abilities.keyword.FlyingAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SetTargetPointer;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.filter.predicate.Predicate;
|
||||
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 VengefulAncestor extends CardImpl {
|
||||
|
||||
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("a goaded creature");
|
||||
|
||||
static {
|
||||
filter.add(VengefulAncestorPredicate.instance);
|
||||
}
|
||||
|
||||
public VengefulAncestor(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{R}");
|
||||
|
||||
this.subtype.add(SubType.SPIRIT);
|
||||
this.subtype.add(SubType.DRAGON);
|
||||
this.power = new MageInt(3);
|
||||
this.toughness = new MageInt(4);
|
||||
|
||||
// Flying
|
||||
this.addAbility(FlyingAbility.getInstance());
|
||||
|
||||
// Whenever Vengeful Ancestor enters the battlefield or attacks, goad target creature.
|
||||
Ability ability = new EntersBattlefieldOrAttacksSourceTriggeredAbility(new GoadTargetEffect());
|
||||
ability.addTarget(new TargetCreaturePermanent());
|
||||
this.addAbility(ability);
|
||||
|
||||
// Whenever a goaded creature attacks, it deals 1 damage to its controller.
|
||||
this.addAbility(new AttacksAllTriggeredAbility(
|
||||
new VengefulAncestorEffect(), false, filter,
|
||||
SetTargetPointer.NONE, false
|
||||
));
|
||||
}
|
||||
|
||||
private VengefulAncestor(final VengefulAncestor card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VengefulAncestor copy() {
|
||||
return new VengefulAncestor(this);
|
||||
}
|
||||
}
|
||||
|
||||
enum VengefulAncestorPredicate implements Predicate<Permanent> {
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public boolean apply(Permanent input, Game game) {
|
||||
return !input.getGoadingPlayers().isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
class VengefulAncestorEffect extends OneShotEffect {
|
||||
|
||||
VengefulAncestorEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "it deals 1 damage to its controller";
|
||||
}
|
||||
|
||||
private VengefulAncestorEffect(final VengefulAncestorEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VengefulAncestorEffect copy() {
|
||||
return new VengefulAncestorEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = (Permanent) getValue("attacker");
|
||||
if (permanent == null) {
|
||||
return false;
|
||||
}
|
||||
Player player = game.getPlayer(permanent.getControllerId());
|
||||
if (player == null) {
|
||||
return false;
|
||||
}
|
||||
return player.damage(1, permanent.getId(), source, game) > 0;
|
||||
}
|
||||
}
|
|
@ -273,6 +273,7 @@ public final class ForgottenRealmsCommander extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Valorous Stance", 76, Rarity.UNCOMMON, mage.cards.v.ValorousStance.class));
|
||||
cards.add(new SetCardInfo("Vandalblast", 148, Rarity.UNCOMMON, mage.cards.v.Vandalblast.class));
|
||||
cards.add(new SetCardInfo("Vanish into Memory", 196, Rarity.UNCOMMON, mage.cards.v.VanishIntoMemory.class));
|
||||
cards.add(new SetCardInfo("Vengeful Ancestor", 35, Rarity.RARE, mage.cards.v.VengefulAncestor.class));
|
||||
cards.add(new SetCardInfo("Verdant Embrace", 173, Rarity.RARE, mage.cards.v.VerdantEmbrace.class));
|
||||
cards.add(new SetCardInfo("Victimize", 112, Rarity.UNCOMMON, mage.cards.v.Victimize.class));
|
||||
cards.add(new SetCardInfo("Viridian Longbow", 221, Rarity.COMMON, mage.cards.v.ViridianLongbow.class));
|
||||
|
|
|
@ -19,16 +19,20 @@ public final class NeonDynastyCommander extends ExpansionSet {
|
|||
super("Neon Dynasty Commander", "NEC", ExpansionSet.buildDate(2022, 2, 18), SetType.SUPPLEMENTAL);
|
||||
this.hasBasicLands = false;
|
||||
|
||||
cards.add(new SetCardInfo("Access Denied", 11, Rarity.RARE, mage.cards.a.AccessDenied.class));
|
||||
cards.add(new SetCardInfo("Access Denied", 11, Rarity.RARE, mage.cards.a.AccessDenied.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Access Denied", 47, Rarity.RARE, mage.cards.a.AccessDenied.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Acidic Slime", 112, Rarity.UNCOMMON, mage.cards.a.AcidicSlime.class));
|
||||
cards.add(new SetCardInfo("Aerial Surveyor", 5, Rarity.RARE, mage.cards.a.AerialSurveyor.class));
|
||||
cards.add(new SetCardInfo("Aerial Surveyor", 39, Rarity.RARE, mage.cards.a.AerialSurveyor.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Aerial Surveyor", 5, Rarity.RARE, mage.cards.a.AerialSurveyor.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Aeronaut Admiral", 79, Rarity.UNCOMMON, mage.cards.a.AeronautAdmiral.class));
|
||||
cards.add(new SetCardInfo("Agitator Ant", 102, Rarity.RARE, mage.cards.a.AgitatorAnt.class));
|
||||
cards.add(new SetCardInfo("Akki Battle Squad", 18, Rarity.RARE, mage.cards.a.AkkiBattleSquad.class));
|
||||
cards.add(new SetCardInfo("Akki Battle Squad", 18, Rarity.RARE, mage.cards.a.AkkiBattleSquad.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Akki Battle Squad", 57, Rarity.RARE, mage.cards.a.AkkiBattleSquad.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Arcane Signet", 144, Rarity.COMMON, mage.cards.a.ArcaneSignet.class));
|
||||
cards.add(new SetCardInfo("Arcanist's Owl", 135, Rarity.UNCOMMON, mage.cards.a.ArcanistsOwl.class));
|
||||
cards.add(new SetCardInfo("Armed and Armored", 80, Rarity.UNCOMMON, mage.cards.a.ArmedAndArmored.class));
|
||||
cards.add(new SetCardInfo("Ascendant Acolyte", 24, Rarity.RARE, mage.cards.a.AscendantAcolyte.class));
|
||||
cards.add(new SetCardInfo("Ascendant Acolyte", 24, Rarity.RARE, mage.cards.a.AscendantAcolyte.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Ascendant Acolyte", 64, Rarity.RARE, mage.cards.a.AscendantAcolyte.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Azorius Signet", 145, Rarity.UNCOMMON, mage.cards.a.AzoriusSignet.class));
|
||||
cards.add(new SetCardInfo("Bear Umbra", 113, Rarity.RARE, mage.cards.b.BearUmbra.class));
|
||||
cards.add(new SetCardInfo("Beast Within", 114, Rarity.UNCOMMON, mage.cards.b.BeastWithin.class));
|
||||
|
@ -38,19 +42,25 @@ public final class NeonDynastyCommander extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Chain Reaction", 103, Rarity.RARE, mage.cards.c.ChainReaction.class));
|
||||
cards.add(new SetCardInfo("Champion of Lambholt", 115, Rarity.RARE, mage.cards.c.ChampionOfLambholt.class));
|
||||
cards.add(new SetCardInfo("Chaos Warp", 104, Rarity.RARE, mage.cards.c.ChaosWarp.class));
|
||||
cards.add(new SetCardInfo("Chishiro, the Shattered Blade", 1, Rarity.MYTHIC, mage.cards.c.ChishiroTheShatteredBlade.class));
|
||||
cards.add(new SetCardInfo("Chishiro, the Shattered Blade", 1, Rarity.MYTHIC, mage.cards.c.ChishiroTheShatteredBlade.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Chishiro, the Shattered Blade", 73, Rarity.MYTHIC, mage.cards.c.ChishiroTheShatteredBlade.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Chishiro, the Shattered Blade", 77, Rarity.MYTHIC, mage.cards.c.ChishiroTheShatteredBlade.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Cinder Glade", 166, Rarity.RARE, mage.cards.c.CinderGlade.class));
|
||||
cards.add(new SetCardInfo("Collision of Realms", 19, Rarity.RARE, mage.cards.c.CollisionOfRealms.class));
|
||||
cards.add(new SetCardInfo("Collision of Realms", 19, Rarity.RARE, mage.cards.c.CollisionOfRealms.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Collision of Realms", 58, Rarity.RARE, mage.cards.c.CollisionOfRealms.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Colossal Plow", 148, Rarity.UNCOMMON, mage.cards.c.ColossalPlow.class));
|
||||
cards.add(new SetCardInfo("Command Tower", 167, Rarity.COMMON, mage.cards.c.CommandTower.class));
|
||||
cards.add(new SetCardInfo("Concord with the Kami", 25, Rarity.RARE, mage.cards.c.ConcordWithTheKami.class));
|
||||
cards.add(new SetCardInfo("Concord with the Kami", 25, Rarity.RARE, mage.cards.c.ConcordWithTheKami.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Concord with the Kami", 65, Rarity.RARE, mage.cards.c.ConcordWithTheKami.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Crush Contraband", 82, Rarity.UNCOMMON, mage.cards.c.CrushContraband.class));
|
||||
cards.add(new SetCardInfo("Cultivator's Caravan", 149, Rarity.RARE, mage.cards.c.CultivatorsCaravan.class));
|
||||
cards.add(new SetCardInfo("Cyberdrive Awakener", 12, Rarity.RARE, mage.cards.c.CyberdriveAwakener.class));
|
||||
cards.add(new SetCardInfo("Cyberdrive Awakener", 12, Rarity.RARE, mage.cards.c.CyberdriveAwakener.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Cyberdrive Awakener", 48, Rarity.RARE, mage.cards.c.CyberdriveAwakener.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Dance of the Manse", 136, Rarity.RARE, mage.cards.d.DanceOfTheManse.class));
|
||||
cards.add(new SetCardInfo("Decimate", 137, Rarity.RARE, mage.cards.d.Decimate.class));
|
||||
cards.add(new SetCardInfo("Dispatch", 83, Rarity.UNCOMMON, mage.cards.d.Dispatch.class));
|
||||
cards.add(new SetCardInfo("Drumbellower", 6, Rarity.RARE, mage.cards.d.Drumbellower.class));
|
||||
cards.add(new SetCardInfo("Drumbellower", 40, Rarity.RARE, mage.cards.d.Drumbellower.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Drumbellower", 6, Rarity.RARE, mage.cards.d.Drumbellower.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Elemental Mastery", 105, Rarity.RARE, mage.cards.e.ElementalMastery.class));
|
||||
cards.add(new SetCardInfo("Emry, Lurker of the Loch", 91, Rarity.RARE, mage.cards.e.EmryLurkerOfTheLoch.class));
|
||||
cards.add(new SetCardInfo("Etherium Sculptor", 92, Rarity.COMMON, mage.cards.e.EtheriumSculptor.class));
|
||||
|
@ -63,41 +73,61 @@ public final class NeonDynastyCommander extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Game Trail", 169, Rarity.RARE, mage.cards.g.GameTrail.class));
|
||||
cards.add(new SetCardInfo("Generous Gift", 84, Rarity.UNCOMMON, mage.cards.g.GenerousGift.class));
|
||||
cards.add(new SetCardInfo("Genesis Hydra", 118, Rarity.RARE, mage.cards.g.GenesisHydra.class));
|
||||
cards.add(new SetCardInfo("Go-Shintai of Life's Origin", 37, Rarity.MYTHIC, mage.cards.g.GoShintaiOfLifesOrigin.class));
|
||||
cards.add(new SetCardInfo("Go-Shintai of Life's Origin", 37, Rarity.MYTHIC, mage.cards.g.GoShintaiOfLifesOrigin.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Go-Shintai of Life's Origin", 66, Rarity.MYTHIC, mage.cards.g.GoShintaiOfLifesOrigin.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Goblin Razerunners", 106, Rarity.RARE, mage.cards.g.GoblinRazerunners.class));
|
||||
cards.add(new SetCardInfo("Gold Myr", 153, Rarity.COMMON, mage.cards.g.GoldMyr.class));
|
||||
cards.add(new SetCardInfo("Grumgully, the Generous", 138, Rarity.UNCOMMON, mage.cards.g.GrumgullyTheGenerous.class));
|
||||
cards.add(new SetCardInfo("Gruul Turf", 170, Rarity.UNCOMMON, mage.cards.g.GruulTurf.class));
|
||||
cards.add(new SetCardInfo("Hanna, Ship's Navigator", 139, Rarity.RARE, mage.cards.h.HannaShipsNavigator.class));
|
||||
cards.add(new SetCardInfo("Hunter's Insight", 119, Rarity.UNCOMMON, mage.cards.h.HuntersInsight.class));
|
||||
cards.add(new SetCardInfo("Imposter Mech", 13, Rarity.RARE, mage.cards.i.ImposterMech.class));
|
||||
cards.add(new SetCardInfo("Imposter Mech", 13, Rarity.RARE, mage.cards.i.ImposterMech.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Imposter Mech", 49, Rarity.RARE, mage.cards.i.ImposterMech.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Indomitable Archangel", 85, Rarity.MYTHIC, mage.cards.i.IndomitableArchangel.class));
|
||||
cards.add(new SetCardInfo("Ironsoul Enforcer", 7, Rarity.RARE, mage.cards.i.IronsoulEnforcer.class));
|
||||
cards.add(new SetCardInfo("Ironsoul Enforcer", 41, Rarity.RARE, mage.cards.i.IronsoulEnforcer.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Ironsoul Enforcer", 7, Rarity.RARE, mage.cards.i.IronsoulEnforcer.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Jace, Architect of Thought", 93, Rarity.MYTHIC, mage.cards.j.JaceArchitectOfThought.class));
|
||||
cards.add(new SetCardInfo("Kami of Celebration", 20, Rarity.RARE, mage.cards.k.KamiOfCelebration.class));
|
||||
cards.add(new SetCardInfo("Kappa Cannoneer", 14, Rarity.RARE, mage.cards.k.KappaCannoneer.class));
|
||||
cards.add(new SetCardInfo("Katsumasa, the Animator", 15, Rarity.RARE, mage.cards.k.KatsumasaTheAnimator.class));
|
||||
cards.add(new SetCardInfo("Kaima, the Fractured Calm", 3, Rarity.MYTHIC, mage.cards.k.KaimaTheFracturedCalm.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Kaima, the Fractured Calm", 74, Rarity.MYTHIC, mage.cards.k.KaimaTheFracturedCalm.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Kami of Celebration", 20, Rarity.RARE, mage.cards.k.KamiOfCelebration.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Kami of Celebration", 59, Rarity.RARE, mage.cards.k.KamiOfCelebration.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Kappa Cannoneer", 14, Rarity.RARE, mage.cards.k.KappaCannoneer.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Kappa Cannoneer", 50, Rarity.RARE, mage.cards.k.KappaCannoneer.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Katsumasa, the Animator", 15, Rarity.RARE, mage.cards.k.KatsumasaTheAnimator.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Katsumasa, the Animator", 51, Rarity.RARE, mage.cards.k.KatsumasaTheAnimator.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Kodama's Reach", 120, Rarity.COMMON, mage.cards.k.KodamasReach.class));
|
||||
cards.add(new SetCardInfo("Kosei, Penitent Warlord", 26, Rarity.RARE, mage.cards.k.KoseiPenitentWarlord.class));
|
||||
cards.add(new SetCardInfo("Kotori, Pilot Prodigy", 2, Rarity.MYTHIC, mage.cards.k.KotoriPilotProdigy.class));
|
||||
cards.add(new SetCardInfo("Komainu Battle Armor", 21, Rarity.RARE, mage.cards.k.KomainuBattleArmor.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Komainu Battle Armor", 60, Rarity.RARE, mage.cards.k.KomainuBattleArmor.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Kosei, Penitent Warlord", 26, Rarity.RARE, mage.cards.k.KoseiPenitentWarlord.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Kosei, Penitent Warlord", 67, Rarity.RARE, mage.cards.k.KoseiPenitentWarlord.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Kotori, Pilot Prodigy", 2, Rarity.MYTHIC, mage.cards.k.KotoriPilotProdigy.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Kotori, Pilot Prodigy", 75, Rarity.MYTHIC, mage.cards.k.KotoriPilotProdigy.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Kotori, Pilot Prodigy", 78, Rarity.MYTHIC, mage.cards.k.KotoriPilotProdigy.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Krenko, Tin Street Kingpin", 107, Rarity.RARE, mage.cards.k.KrenkoTinStreetKingpin.class));
|
||||
cards.add(new SetCardInfo("Loyal Guardian", 121, Rarity.UNCOMMON, mage.cards.l.LoyalGuardian.class));
|
||||
cards.add(new SetCardInfo("Mage Slayer", 140, Rarity.UNCOMMON, mage.cards.m.MageSlayer.class));
|
||||
cards.add(new SetCardInfo("Master of Etherium", 94, Rarity.RARE, mage.cards.m.MasterOfEtherium.class));
|
||||
cards.add(new SetCardInfo("Mirage Mirror", 154, Rarity.RARE, mage.cards.m.MirageMirror.class));
|
||||
cards.add(new SetCardInfo("Mossfire Valley", 171, Rarity.RARE, mage.cards.m.MossfireValley.class));
|
||||
cards.add(new SetCardInfo("Myojin of Blooming Dawn", 31, Rarity.RARE, mage.cards.m.MyojinOfBloomingDawn.class));
|
||||
cards.add(new SetCardInfo("Myojin of Cryptic Dreams", 33, Rarity.RARE, mage.cards.m.MyojinOfCrypticDreams.class));
|
||||
cards.add(new SetCardInfo("Myojin of Grim Betrayal", 34, Rarity.RARE, mage.cards.m.MyojinOfGrimBetrayal.class));
|
||||
cards.add(new SetCardInfo("Myojin of Roaring Blades", 36, Rarity.RARE, mage.cards.m.MyojinOfRoaringBlades.class));
|
||||
cards.add(new SetCardInfo("Myojin of Towering Might", 38, Rarity.RARE, mage.cards.m.MyojinOfToweringMight.class));
|
||||
cards.add(new SetCardInfo("Myojin of Blooming Dawn", 31, Rarity.RARE, mage.cards.m.MyojinOfBloomingDawn.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Myojin of Blooming Dawn", 42, Rarity.RARE, mage.cards.m.MyojinOfBloomingDawn.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Myojin of Cryptic Dreams", 33, Rarity.RARE, mage.cards.m.MyojinOfCrypticDreams.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Myojin of Cryptic Dreams", 52, Rarity.RARE, mage.cards.m.MyojinOfCrypticDreams.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Myojin of Grim Betrayal", 34, Rarity.RARE, mage.cards.m.MyojinOfGrimBetrayal.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Myojin of Grim Betrayal", 55, Rarity.RARE, mage.cards.m.MyojinOfGrimBetrayal.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Myojin of Roaring Blades", 36, Rarity.RARE, mage.cards.m.MyojinOfRoaringBlades.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Myojin of Roaring Blades", 61, Rarity.RARE, mage.cards.m.MyojinOfRoaringBlades.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Myojin of Towering Might", 38, Rarity.RARE, mage.cards.m.MyojinOfToweringMight.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Myojin of Towering Might", 68, Rarity.RARE, mage.cards.m.MyojinOfToweringMight.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Myrsmith", 86, Rarity.UNCOMMON, mage.cards.m.Myrsmith.class));
|
||||
cards.add(new SetCardInfo("Nissa, Voice of Zendikar", 122, Rarity.MYTHIC, mage.cards.n.NissaVoiceOfZendikar.class));
|
||||
cards.add(new SetCardInfo("One with the Kami", 27, Rarity.RARE, mage.cards.o.OneWithTheKami.class));
|
||||
cards.add(new SetCardInfo("One with the Kami", 27, Rarity.RARE, mage.cards.o.OneWithTheKami.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("One with the Kami", 69, Rarity.RARE, mage.cards.o.OneWithTheKami.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Opal Palace", 172, Rarity.COMMON, mage.cards.o.OpalPalace.class));
|
||||
cards.add(new SetCardInfo("Oran-Rief, the Vastwood", 173, Rarity.RARE, mage.cards.o.OranRiefTheVastwood.class));
|
||||
cards.add(new SetCardInfo("Ordeal of Nylea", 123, Rarity.UNCOMMON, mage.cards.o.OrdealOfNylea.class));
|
||||
cards.add(new SetCardInfo("Organic Extinction", 8, Rarity.RARE, mage.cards.o.OrganicExtinction.class));
|
||||
cards.add(new SetCardInfo("Organic Extinction", 43, Rarity.RARE, mage.cards.o.OrganicExtinction.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Organic Extinction", 8, Rarity.RARE, mage.cards.o.OrganicExtinction.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Ox of Agonas", 108, Rarity.MYTHIC, mage.cards.o.OxOfAgonas.class));
|
||||
cards.add(new SetCardInfo("Parhelion II", 87, Rarity.RARE, mage.cards.p.ParhelionII.class));
|
||||
cards.add(new SetCardInfo("Peacewalker Colossus", 155, Rarity.RARE, mage.cards.p.PeacewalkerColossus.class));
|
||||
|
@ -108,26 +138,33 @@ public final class NeonDynastyCommander extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Raging Ravine", 176, Rarity.RARE, mage.cards.r.RagingRavine.class));
|
||||
cards.add(new SetCardInfo("Raiders' Karve", 156, Rarity.COMMON, mage.cards.r.RaidersKarve.class));
|
||||
cards.add(new SetCardInfo("Rampant Growth", 125, Rarity.COMMON, mage.cards.r.RampantGrowth.class));
|
||||
cards.add(new SetCardInfo("Rampant Rejuvenator", 28, Rarity.RARE, mage.cards.r.RampantRejuvenator.class));
|
||||
cards.add(new SetCardInfo("Rampant Rejuvenator", 28, Rarity.RARE, mage.cards.r.RampantRejuvenator.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Rampant Rejuvenator", 70, Rarity.RARE, mage.cards.r.RampantRejuvenator.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Reality Shift", 95, Rarity.UNCOMMON, mage.cards.r.RealityShift.class));
|
||||
cards.add(new SetCardInfo("Release to Memory", 9, Rarity.RARE, mage.cards.r.ReleaseToMemory.class));
|
||||
cards.add(new SetCardInfo("Research Thief", 16, Rarity.RARE, mage.cards.r.ResearchThief.class));
|
||||
cards.add(new SetCardInfo("Release to Memory", 44, Rarity.RARE, mage.cards.r.ReleaseToMemory.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Release to Memory", 9, Rarity.RARE, mage.cards.r.ReleaseToMemory.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Research Thief", 16, Rarity.RARE, mage.cards.r.ResearchThief.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Research Thief", 53, Rarity.RARE, mage.cards.r.ResearchThief.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Rhythm of the Wild", 142, Rarity.UNCOMMON, mage.cards.r.RhythmOfTheWild.class));
|
||||
cards.add(new SetCardInfo("Riddlesmith", 96, Rarity.UNCOMMON, mage.cards.r.Riddlesmith.class));
|
||||
cards.add(new SetCardInfo("Rishkar's Expertise", 127, Rarity.RARE, mage.cards.r.RishkarsExpertise.class));
|
||||
cards.add(new SetCardInfo("Rishkar, Peema Renegade", 126, Rarity.RARE, mage.cards.r.RishkarPeemaRenegade.class));
|
||||
cards.add(new SetCardInfo("Ruthless Technomancer", 35, Rarity.RARE, mage.cards.r.RuthlessTechnomancer.class));
|
||||
cards.add(new SetCardInfo("Ruthless Technomancer", 35, Rarity.RARE, mage.cards.r.RuthlessTechnomancer.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Ruthless Technomancer", 56, Rarity.RARE, mage.cards.r.RuthlessTechnomancer.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Sai, Master Thopterist", 97, Rarity.RARE, mage.cards.s.SaiMasterThopterist.class));
|
||||
cards.add(new SetCardInfo("Sakura-Tribe Elder", 128, Rarity.COMMON, mage.cards.s.SakuraTribeElder.class));
|
||||
cards.add(new SetCardInfo("Shamanic Revelation", 129, Rarity.RARE, mage.cards.s.ShamanicRevelation.class));
|
||||
cards.add(new SetCardInfo("Shifting Shadow", 109, Rarity.RARE, mage.cards.s.ShiftingShadow.class));
|
||||
cards.add(new SetCardInfo("Shimmer Myr", 157, Rarity.UNCOMMON, mage.cards.s.ShimmerMyr.class));
|
||||
cards.add(new SetCardInfo("Shorikai, Genesis Engine", 4, Rarity.MYTHIC, mage.cards.s.ShorikaiGenesisEngine.class));
|
||||
cards.add(new SetCardInfo("Silkguard", 29, Rarity.RARE, mage.cards.s.Silkguard.class));
|
||||
cards.add(new SetCardInfo("Shorikai, Genesis Engine", 4, Rarity.MYTHIC, mage.cards.s.ShorikaiGenesisEngine.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Shorikai, Genesis Engine", 76, Rarity.MYTHIC, mage.cards.s.ShorikaiGenesisEngine.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Silkguard", 29, Rarity.RARE, mage.cards.s.Silkguard.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Silkguard", 71, Rarity.RARE, mage.cards.s.Silkguard.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Silver Myr", 158, Rarity.COMMON, mage.cards.s.SilverMyr.class));
|
||||
cards.add(new SetCardInfo("Skycloud Expanse", 177, Rarity.RARE, mage.cards.s.SkycloudExpanse.class));
|
||||
cards.add(new SetCardInfo("Skysovereign, Consul Flagship", 159, Rarity.MYTHIC, mage.cards.s.SkysovereignConsulFlagship.class));
|
||||
cards.add(new SetCardInfo("Smoke Spirits' Aid", 22, Rarity.RARE, mage.cards.s.SmokeSpiritsAid.class));
|
||||
cards.add(new SetCardInfo("Smoke Spirits' Aid", 22, Rarity.RARE, mage.cards.s.SmokeSpiritsAid.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Smoke Spirits' Aid", 62, Rarity.RARE, mage.cards.s.SmokeSpiritsAid.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Smuggler's Copter", 160, Rarity.RARE, mage.cards.s.SmugglersCopter.class));
|
||||
cards.add(new SetCardInfo("Snake Umbra", 130, Rarity.UNCOMMON, mage.cards.s.SnakeUmbra.class));
|
||||
cards.add(new SetCardInfo("Sol Ring", 161, Rarity.UNCOMMON, mage.cards.s.SolRing.class));
|
||||
|
@ -137,11 +174,13 @@ public final class NeonDynastyCommander extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Spire of Industry", 178, Rarity.RARE, mage.cards.s.SpireOfIndustry.class));
|
||||
cards.add(new SetCardInfo("Sram, Senior Edificer", 88, Rarity.RARE, mage.cards.s.SramSeniorEdificer.class));
|
||||
cards.add(new SetCardInfo("Starstorm", 110, Rarity.RARE, mage.cards.s.Starstorm.class));
|
||||
cards.add(new SetCardInfo("Swift Reconfiguration", 10, Rarity.RARE, mage.cards.s.SwiftReconfiguration.class));
|
||||
cards.add(new SetCardInfo("Swift Reconfiguration", 10, Rarity.RARE, mage.cards.s.SwiftReconfiguration.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Swift Reconfiguration", 45, Rarity.RARE, mage.cards.s.SwiftReconfiguration.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Swiftfoot Boots", 163, Rarity.UNCOMMON, mage.cards.s.SwiftfootBoots.class));
|
||||
cards.add(new SetCardInfo("Sword of Vengeance", 164, Rarity.RARE, mage.cards.s.SwordOfVengeance.class));
|
||||
cards.add(new SetCardInfo("Swords to Plowshares", 89, Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class));
|
||||
cards.add(new SetCardInfo("Tanuki Transplanter", 30, Rarity.RARE, mage.cards.t.TanukiTransplanter.class));
|
||||
cards.add(new SetCardInfo("Tanuki Transplanter", 30, Rarity.RARE, mage.cards.t.TanukiTransplanter.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Tanuki Transplanter", 72, Rarity.RARE, mage.cards.t.TanukiTransplanter.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Taurean Mauler", 111, Rarity.RARE, mage.cards.t.TaureanMauler.class));
|
||||
cards.add(new SetCardInfo("Temple of Abandon", 179, Rarity.RARE, mage.cards.t.TempleOfAbandon.class));
|
||||
cards.add(new SetCardInfo("Temple of Enlightenment", 180, Rarity.RARE, mage.cards.t.TempleOfEnlightenment.class));
|
||||
|
@ -149,13 +188,16 @@ public final class NeonDynastyCommander extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Thopter Spy Network", 98, Rarity.RARE, mage.cards.t.ThopterSpyNetwork.class));
|
||||
cards.add(new SetCardInfo("Thoughtcast", 99, Rarity.COMMON, mage.cards.t.Thoughtcast.class));
|
||||
cards.add(new SetCardInfo("Ulasht, the Hate Seed", 143, Rarity.RARE, mage.cards.u.UlashtTheHateSeed.class));
|
||||
cards.add(new SetCardInfo("Universal Surveillance", 17, Rarity.RARE, mage.cards.u.UniversalSurveillance.class));
|
||||
cards.add(new SetCardInfo("Unquenchable Fury", 23, Rarity.RARE, mage.cards.u.UnquenchableFury.class));
|
||||
cards.add(new SetCardInfo("Universal Surveillance", 17, Rarity.RARE, mage.cards.u.UniversalSurveillance.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Universal Surveillance", 54, Rarity.RARE, mage.cards.u.UniversalSurveillance.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Unquenchable Fury", 23, Rarity.RARE, mage.cards.u.UnquenchableFury.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Unquenchable Fury", 63, Rarity.RARE, mage.cards.u.UnquenchableFury.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Vastwood Surge", 133, Rarity.UNCOMMON, mage.cards.v.VastwoodSurge.class));
|
||||
cards.add(new SetCardInfo("Vedalken Engineer", 100, Rarity.COMMON, mage.cards.v.VedalkenEngineer.class));
|
||||
cards.add(new SetCardInfo("Weatherlight", 165, Rarity.MYTHIC, mage.cards.w.Weatherlight.class));
|
||||
cards.add(new SetCardInfo("Whiptongue Hydra", 134, Rarity.RARE, mage.cards.w.WhiptongueHydra.class));
|
||||
cards.add(new SetCardInfo("Whirler Rogue", 101, Rarity.UNCOMMON, mage.cards.w.WhirlerRogue.class));
|
||||
cards.add(new SetCardInfo("Yoshimaru, Ever Faithful", 32, Rarity.MYTHIC, mage.cards.y.YoshimaruEverFaithful.class));
|
||||
cards.add(new SetCardInfo("Yoshimaru, Ever Faithful", 32, Rarity.MYTHIC, mage.cards.y.YoshimaruEverFaithful.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Yoshimaru, Ever Faithful", 46, Rarity.MYTHIC, mage.cards.y.YoshimaruEverFaithful.class, NON_FULL_USE_VARIOUS));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.mage.test.cards.abilities.keywords;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import mage.constants.MultiplayerAttackOption;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.RangeOfInfluence;
|
||||
|
@ -10,16 +9,29 @@ import mage.game.Game;
|
|||
import mage.game.GameException;
|
||||
import mage.game.mulligan.MulliganType;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.player.TestPlayer;
|
||||
import org.mage.test.serverside.base.CardTestMultiPlayerBase;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class GoadTest extends CardTestMultiPlayerBase {
|
||||
|
||||
private static final String marisi = "Marisi, Breaker of the Coil";
|
||||
private static final String griffin = "Abbey Griffin";
|
||||
private static final String ray = "Ray of Command";
|
||||
private static final String homunculus = "Jeering Homunculus";
|
||||
private static final String lion = "Silvercoat Lion";
|
||||
private static final String archon = "Blazing Archon";
|
||||
|
||||
@Override
|
||||
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
|
||||
Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 40);
|
||||
|
@ -31,85 +43,155 @@ public class GoadTest extends CardTestMultiPlayerBase {
|
|||
return game;
|
||||
}
|
||||
|
||||
private void assertAttacking(String attacker, TestPlayer... players) {
|
||||
Assert.assertTrue("At least one player should be provided", players.length > 0);
|
||||
Permanent permanent = getPermanent(attacker);
|
||||
Assert.assertTrue("Creature should be tapped", permanent.isTapped());
|
||||
Assert.assertTrue("Creature should be attacking", permanent.isAttacking());
|
||||
UUID defenderId = currentGame.getCombat().getDefenderId(permanent.getId());
|
||||
Assert.assertTrue(
|
||||
"Creature should be attacking one the following players: "
|
||||
+ Arrays
|
||||
.stream(players)
|
||||
.map(Player::getName)
|
||||
.reduce((a, b) -> a + ", " + b)
|
||||
.orElse(""),
|
||||
Arrays.stream(players)
|
||||
.map(TestPlayer::getId)
|
||||
.anyMatch(defenderId::equals)
|
||||
);
|
||||
}
|
||||
|
||||
private void assertGoaded(String attacker, TestPlayer... players) {
|
||||
Assert.assertTrue("At least one player should be provided", players.length > 0);
|
||||
Permanent permanent = getPermanent(attacker);
|
||||
Assert.assertEquals(
|
||||
"Creature should be goaded by "
|
||||
+ Arrays
|
||||
.stream(players)
|
||||
.map(Player::getName)
|
||||
.reduce((a, b) -> a + ", " + b).orElse(""),
|
||||
permanent.getGoadingPlayers(),
|
||||
Arrays.stream(players)
|
||||
.map(TestPlayer::getId)
|
||||
.collect(Collectors.toSet())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void goadWithOwnedCreatureTest() {
|
||||
// Your opponents can't cast spells during combat.
|
||||
// Whenever a creature you control deals combat damage to a player, goad each creature that player controls
|
||||
// (Until your next turn, that creature attacks each combat if able and attacks a player other than you if able.)
|
||||
addCard(Zone.BATTLEFIELD, playerD, "Marisi, Breaker of the Coil", 1); // Creature 5/4
|
||||
public void testCantAttackGoadingPlayer() {
|
||||
addCard(Zone.HAND, playerA, homunculus);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
|
||||
addCard(Zone.BATTLEFIELD, playerD, lion);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerC, "Abbey Griffin", 3); // Creature 2/2
|
||||
addTarget(playerA, lion);
|
||||
setChoice(playerA, "Yes");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, homunculus);
|
||||
|
||||
attack(2, playerD, "Marisi, Breaker of the Coil", playerC);
|
||||
|
||||
setStopAt(3, PhaseStep.BEGIN_COMBAT);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(2, PhaseStep.DECLARE_BLOCKERS);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
Permanent griffinPermanent = getPermanent("Abbey Griffin");
|
||||
|
||||
Assert.assertFalse("Griffin can attack playerD but should not be able",
|
||||
griffinPermanent.canAttack(playerD.getId(), currentGame));
|
||||
Assert.assertTrue("Griffin can't attack playerA but should be able",
|
||||
griffinPermanent.canAttack(playerA.getId(), currentGame));
|
||||
Assert.assertTrue("Griffin can't attack playerB but should be able",
|
||||
griffinPermanent.canAttack(playerB.getId(), currentGame));
|
||||
|
||||
assertLife(playerC, 35);
|
||||
assertLife(playerD, 40); // player D can not be attacked from C because the creatures are goaded
|
||||
assertLife(playerA, 40);
|
||||
assertLife(playerB, 40);
|
||||
|
||||
assertGoaded(lion, playerA);
|
||||
assertAttacking(lion, playerB, playerC);
|
||||
}
|
||||
|
||||
/**
|
||||
* In a game of commander, my opponent gained control of Marisi, Breaker of
|
||||
* Coils (until end of turn) and did combat damage to another player. This
|
||||
* caused the creatures damaged by Marisi's controller to be goaded.
|
||||
* However, when the goaded creatures went to attack, they could not attack
|
||||
* me but could attack the (former) controller of Marisi.
|
||||
*/
|
||||
@Test
|
||||
public void goadWithNotOwnedCreatureTest() {
|
||||
// Your opponents can't cast spells during combat.
|
||||
// Whenever a creature you control deals combat damage to a player, goad each creature that player controls
|
||||
// (Until your next turn, that creature attacks each combat if able and attacks a player other than you if able.)
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Marisi, Breaker of the Coil", 1); // Creature 5/4
|
||||
public void testCanOnlyAttackOnePlayer() {
|
||||
addCard(Zone.HAND, playerA, homunculus);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
|
||||
addCard(Zone.BATTLEFIELD, playerD, lion);
|
||||
addCard(Zone.BATTLEFIELD, playerB, archon);
|
||||
|
||||
// Untap target creature an opponent controls and gain control of it until end of turn.
|
||||
// That creature gains haste until end of turn.
|
||||
// When you lose control of the creature, tap it.
|
||||
addCard(Zone.HAND, playerD, "Ray of Command"); // Instant {3}{U}
|
||||
addCard(Zone.BATTLEFIELD, playerD, "Island", 4);
|
||||
addTarget(playerA, lion);
|
||||
setChoice(playerA, "Yes");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, homunculus);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion", 1); // Creature 2/2
|
||||
|
||||
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, "Ray of Command", "Marisi, Breaker of the Coil");
|
||||
|
||||
attack(2, playerD, "Marisi, Breaker of the Coil", playerC);
|
||||
|
||||
setStopAt(3, PhaseStep.BEGIN_COMBAT);
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(2, PhaseStep.DECLARE_BLOCKERS);
|
||||
execute();
|
||||
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertGraveyardCount(playerD, "Ray of Command", 1);
|
||||
assertPermanentCount(playerA, "Marisi, Breaker of the Coil", 1);
|
||||
|
||||
Permanent lion = getPermanent("Silvercoat Lion", playerC);
|
||||
|
||||
Assert.assertFalse("Silvercoat lion shouldn't be able to attack player D but can", lion.canAttack(playerD.getId(), currentGame));
|
||||
Assert.assertTrue("Silvercoat lion should be able to attack player A but can't", lion.canAttack(playerA.getId(), currentGame));
|
||||
Assert.assertTrue("Silvercoat lion should be able to attack player B but can't", lion.canAttack(playerB.getId(), currentGame));
|
||||
|
||||
assertLife(playerD, 40);
|
||||
assertLife(playerC, 35);
|
||||
assertLife(playerA, 40);
|
||||
assertLife(playerB, 40);
|
||||
|
||||
assertGoaded(lion, playerA);
|
||||
assertAttacking(lion, playerC);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMustAttackGoader() {
|
||||
addCard(Zone.HAND, playerA, homunculus);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
|
||||
addCard(Zone.BATTLEFIELD, playerD, lion);
|
||||
addCard(Zone.BATTLEFIELD, playerB, archon);
|
||||
addCard(Zone.BATTLEFIELD, playerC, archon);
|
||||
|
||||
addTarget(playerA, lion);
|
||||
setChoice(playerA, "Yes");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, homunculus);
|
||||
|
||||
setStopAt(2, PhaseStep.DECLARE_BLOCKERS);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertGoaded(lion, playerA);
|
||||
assertAttacking(lion, playerA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleGoad() {
|
||||
addCard(Zone.HAND, playerA, homunculus);
|
||||
addCard(Zone.HAND, playerD, homunculus);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
|
||||
addCard(Zone.BATTLEFIELD, playerD, "Island", 2);
|
||||
addCard(Zone.BATTLEFIELD, playerC, lion);
|
||||
|
||||
addTarget(playerA, lion);
|
||||
setChoice(playerA, "Yes");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, homunculus);
|
||||
|
||||
addTarget(playerD, lion);
|
||||
setChoice(playerD, "Yes");
|
||||
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, homunculus);
|
||||
|
||||
setStopAt(3, PhaseStep.DECLARE_BLOCKERS);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertGoaded(lion, playerA, playerD);
|
||||
assertAttacking(lion, playerB);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleGoadRestriction() {
|
||||
addCard(Zone.HAND, playerA, homunculus);
|
||||
addCard(Zone.HAND, playerD, homunculus);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
|
||||
addCard(Zone.BATTLEFIELD, playerD, "Island", 2);
|
||||
addCard(Zone.BATTLEFIELD, playerB, archon);
|
||||
addCard(Zone.BATTLEFIELD, playerC, lion);
|
||||
|
||||
addTarget(playerA, lion);
|
||||
setChoice(playerA, "Yes");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, homunculus);
|
||||
|
||||
addTarget(playerD, lion);
|
||||
setChoice(playerD, "Yes");
|
||||
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, homunculus);
|
||||
|
||||
setStopAt(3, PhaseStep.DECLARE_BLOCKERS);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertGoaded(lion, playerA, playerD);
|
||||
assertAttacking(lion, playerA, playerD);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegularCombatRequirement() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Berserkers of Blood Ridge");
|
||||
|
||||
setStopAt(1, PhaseStep.DECLARE_BLOCKERS);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertAttacking("Berserkers of Blood Ridge", playerB, playerC, playerD);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package mage.abilities.common;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.constants.SetTargetPointer;
|
||||
|
@ -11,9 +10,11 @@ import mage.game.Game;
|
|||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class AttacksAllTriggeredAbility extends TriggeredAbilityImpl {
|
||||
|
@ -74,18 +75,15 @@ public class AttacksAllTriggeredAbility extends TriggeredAbilityImpl {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
getEffects().setValue("attacker", permanent);
|
||||
switch (setTargetPointer) {
|
||||
case PERMANENT:
|
||||
for (Effect effect : getEffects()) {
|
||||
effect.setTargetPointer(new FixedTarget(permanent, game));
|
||||
}
|
||||
getEffects().setTargetPointer(new FixedTarget(permanent, game));
|
||||
break;
|
||||
case PLAYER:
|
||||
UUID playerId = controller ? permanent.getControllerId() : game.getCombat().getDefendingPlayerId(permanent.getId(), game);
|
||||
if (playerId != null) {
|
||||
for (Effect effect : getEffects()) {
|
||||
effect.setTargetPointer(new FixedTarget(playerId));
|
||||
}
|
||||
getEffects().setTargetPointer(new FixedTarget(playerId));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -101,10 +99,8 @@ public class AttacksAllTriggeredAbility extends TriggeredAbilityImpl {
|
|||
|
||||
@Override
|
||||
public String getTriggerPhrase() {
|
||||
return "Whenever " + (filter.getMessage().startsWith("an") ? "" : "a ")
|
||||
+ filter.getMessage() + " attacks"
|
||||
+ (attacksYouOrYourPlaneswalker ? " you or a planeswalker you control" : "")
|
||||
+ ", " ;
|
||||
return "Whenever " + CardUtil.addArticle(filter.getMessage()) + " attacks"
|
||||
+ (attacksYouOrYourPlaneswalker ? " you or a planeswalker you control" : "") + ", ";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
package mage.abilities.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.StaticAbility;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.RestrictionEffect;
|
||||
import mage.abilities.effects.common.combat.AttacksIfAbleAttachedEffect;
|
||||
import mage.constants.AttachmentType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class GoadAttachedAbility extends StaticAbility {
|
||||
|
||||
public GoadAttachedAbility(Effect... effects) {
|
||||
super(Zone.BATTLEFIELD, null);
|
||||
for (Effect effect : effects) {
|
||||
this.addEffect(effect);
|
||||
}
|
||||
this.addEffect(new AttacksIfAbleAttachedEffect(
|
||||
Duration.WhileOnBattlefield, AttachmentType.AURA
|
||||
).setText((getEffects().size() > 1 ? ", " : " ") + "and is goaded. <i>(It attacks each combat if able"));
|
||||
this.addEffect(new GoadAttackEffect());
|
||||
}
|
||||
|
||||
private GoadAttachedAbility(final GoadAttachedAbility ability) {
|
||||
super(ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GoadAttachedAbility copy() {
|
||||
return new GoadAttachedAbility(this);
|
||||
}
|
||||
}
|
||||
|
||||
class GoadAttackEffect extends RestrictionEffect {
|
||||
|
||||
GoadAttackEffect() {
|
||||
super(Duration.WhileOnBattlefield);
|
||||
staticText = "and attacks a player other than you if able.)</i>";
|
||||
}
|
||||
|
||||
private GoadAttackEffect(final GoadAttackEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GoadAttackEffect copy() {
|
||||
return new GoadAttackEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(Permanent permanent, Ability source, Game game) {
|
||||
Permanent attachment = game.getPermanent(source.getSourceId());
|
||||
return attachment != null && attachment.getAttachedTo() != null
|
||||
&& permanent.getId().equals(attachment.getAttachedTo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game, boolean canUseChooseDialogs) {
|
||||
if (defenderId == null
|
||||
|| game.getState().getPlayersInRange(attacker.getControllerId(), game).size() == 2) { // just 2 players left, so it may attack you
|
||||
return true;
|
||||
}
|
||||
// A planeswalker controlled by the controller is the defender
|
||||
if (game.getPermanent(defenderId) != null) {
|
||||
return !game.getPermanent(defenderId).getControllerId().equals(source.getControllerId());
|
||||
}
|
||||
// The controller is the defender
|
||||
return !defenderId.equals(source.getControllerId());
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package mage.abilities.effects.common.combat;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.RestrictionEffect;
|
||||
import mage.constants.Duration;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class CantAttackControllerDueToGoadEffect extends RestrictionEffect {
|
||||
|
||||
public CantAttackControllerDueToGoadEffect(Duration duration) {
|
||||
super(duration);
|
||||
}
|
||||
|
||||
public CantAttackControllerDueToGoadEffect(final CantAttackControllerDueToGoadEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CantAttackControllerDueToGoadEffect copy() {
|
||||
return new CantAttackControllerDueToGoadEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(Permanent permanent, Ability source, Game game) {
|
||||
return this.getTargetPointer().getTargets(game, source).contains(permanent.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game, boolean canUseChooseDialogs) {
|
||||
if (defenderId == null
|
||||
|| game.getState().getPlayersInRange(attacker.getControllerId(), game).size() == 2) { // just 2 players left, so it may attack you
|
||||
return true;
|
||||
}
|
||||
// A planeswalker controlled by the controller is the defender
|
||||
if (game.getPermanent(defenderId) != null) {
|
||||
return !game.getPermanent(defenderId).getControllerId().equals(source.getControllerId());
|
||||
}
|
||||
// The controller is the defender
|
||||
return !defenderId.equals(source.getControllerId());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package mage.abilities.effects.common.combat;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Layer;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubLayer;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class GoadAttachedEffect extends ContinuousEffectImpl {
|
||||
|
||||
public GoadAttachedEffect() {
|
||||
super(Duration.WhileOnBattlefield, Layer.RulesEffects, SubLayer.NA, Outcome.Detriment);
|
||||
staticText = "and is goaded";
|
||||
}
|
||||
|
||||
private GoadAttachedEffect(final GoadAttachedEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
|
||||
if (permanent == null) {
|
||||
return false;
|
||||
}
|
||||
Permanent attached = game.getPermanent(permanent.getAttachedTo());
|
||||
if (attached == null) {
|
||||
return false;
|
||||
}
|
||||
attached.addGoadingPlayer(source.getControllerId());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GoadAttachedEffect copy() {
|
||||
return new GoadAttachedEffect(this);
|
||||
}
|
||||
}
|
|
@ -2,19 +2,19 @@ package mage.abilities.effects.common.combat;
|
|||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.effects.ContinuousEffect;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Layer;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubLayer;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class GoadTargetEffect extends OneShotEffect {
|
||||
public class GoadTargetEffect extends ContinuousEffectImpl {
|
||||
|
||||
/**
|
||||
* 701.36. Goad
|
||||
|
@ -24,10 +24,10 @@ public class GoadTargetEffect extends OneShotEffect {
|
|||
* each combat if able and attacks a player other than that player if able.
|
||||
*/
|
||||
public GoadTargetEffect() {
|
||||
super(Outcome.Detriment);
|
||||
super(Duration.UntilYourNextTurn, Layer.RulesEffects, SubLayer.NA, Outcome.Detriment);
|
||||
}
|
||||
|
||||
public GoadTargetEffect(final GoadTargetEffect effect) {
|
||||
private GoadTargetEffect(final GoadTargetEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
|
@ -37,26 +37,22 @@ public class GoadTargetEffect extends OneShotEffect {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
public void init(Ability source, Game game) {
|
||||
super.init(source, game);
|
||||
Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source));
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (targetCreature != null && controller != null) {
|
||||
// TODO: Allow goad to target controller, current AttacksIfAbleTargetEffect does not support it
|
||||
// https://github.com/magefree/mage/issues/5283
|
||||
/*
|
||||
If the creature doesn’t meet any of the above exceptions and can attack, it must attack a player other than
|
||||
the controller of the spell or ability that goaded it if able. If the creature can’t attack any of those
|
||||
players but could otherwise attack, it must attack an opposing planeswalker (controlled by any opponent)
|
||||
or the player that goaded it. (2016-08-23)
|
||||
*/
|
||||
ContinuousEffect effect = new AttacksIfAbleTargetEffect(Duration.UntilYourNextTurn);
|
||||
effect.setTargetPointer(new FixedTarget(getTargetPointer().getFirst(game, source), game));
|
||||
game.addEffect(effect, source);
|
||||
effect = new CantAttackControllerDueToGoadEffect(Duration.UntilYourNextTurn); // remember current controller
|
||||
effect.setTargetPointer(new FixedTarget(getTargetPointer().getFirst(game, source), game));
|
||||
game.addEffect(effect, source);
|
||||
game.informPlayers(controller.getLogName() + " is goading " + targetCreature.getLogName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source));
|
||||
if (targetCreature == null) {
|
||||
return false;
|
||||
}
|
||||
targetCreature.addGoadingPlayer(source.getControllerId());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -440,66 +440,77 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
for (Permanent creature : player.getAvailableAttackers(game)) {
|
||||
boolean mustAttack = false;
|
||||
Set<UUID> defendersForcedToAttack = new HashSet<>();
|
||||
|
||||
if (creature.getGoadingPlayers().isEmpty()) {
|
||||
// check if a creature has to attack
|
||||
for (Map.Entry<RequirementEffect, Set<Ability>> entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, false, game).entrySet()) {
|
||||
RequirementEffect effect = entry.getKey();
|
||||
if (effect.mustAttack(game)
|
||||
&& checkAttackRestrictions(player, game)) { // needed for Goad Effect
|
||||
if (!effect.mustAttack(game)) {
|
||||
continue;
|
||||
}
|
||||
mustAttack = true;
|
||||
for (Ability ability : entry.getValue()) {
|
||||
UUID defenderId = effect.mustAttackDefender(ability, game);
|
||||
if (defenderId != null) {
|
||||
if (defenders.contains(defenderId)) {
|
||||
if (defenderId != null && defenders.contains(defenderId)) {
|
||||
defendersForcedToAttack.add(defenderId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if creature is goaded then we start with assumption that it needs to attack any player
|
||||
mustAttack = true;
|
||||
defendersForcedToAttack.addAll(defenders);
|
||||
}
|
||||
if (!mustAttack) {
|
||||
continue;
|
||||
}
|
||||
if (mustAttack) {
|
||||
// check which defenders the forced to attack creature can attack without paying a cost
|
||||
Set<UUID> defendersCostlessAttackable = new HashSet<>(defenders);
|
||||
for (UUID defenderId : defenders) {
|
||||
if (game.getContinuousEffects().checkIfThereArePayCostToAttackBlockEffects(
|
||||
new DeclareAttackerEvent(defenderId, creature.getId(), creature.getControllerId()), game)) {
|
||||
new DeclareAttackerEvent(defenderId, creature.getId(), creature.getControllerId()), game
|
||||
)) {
|
||||
defendersCostlessAttackable.remove(defenderId);
|
||||
defendersForcedToAttack.remove(defenderId);
|
||||
continue;
|
||||
}
|
||||
for (Map.Entry<RestrictionEffect, Set<Ability>> entry : game.getContinuousEffects().getApplicableRestrictionEffects(creature, game).entrySet()) {
|
||||
if (entry
|
||||
.getValue()
|
||||
.stream()
|
||||
.anyMatch(ability -> entry.getKey().canAttack(
|
||||
creature, defenderId, ability, game, false
|
||||
))) {
|
||||
continue;
|
||||
}
|
||||
defendersCostlessAttackable.remove(defenderId);
|
||||
defendersForcedToAttack.remove(defenderId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if creature can attack someone other than a player that goaded them
|
||||
// then they attack one of those players, otherwise they attack any player
|
||||
if (!defendersForcedToAttack.stream().allMatch(creature.getGoadingPlayers()::contains)) {
|
||||
defendersForcedToAttack.removeAll(creature.getGoadingPlayers());
|
||||
}
|
||||
// force attack only if a defender can be attacked without paying a cost
|
||||
if (!defendersCostlessAttackable.isEmpty()) {
|
||||
if (defendersCostlessAttackable.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
creaturesForcedToAttack.put(creature.getId(), defendersForcedToAttack);
|
||||
// No need to attack a special defender
|
||||
if (defendersForcedToAttack.isEmpty()) {
|
||||
if (defendersCostlessAttackable.size() == 1) {
|
||||
player.declareAttacker(creature.getId(), defendersCostlessAttackable.iterator().next(), game, false);
|
||||
} else {
|
||||
TargetDefender target = new TargetDefender(defendersCostlessAttackable, creature.getId());
|
||||
Set<UUID> defendersToChooseFrom = defendersForcedToAttack.isEmpty() ? defendersCostlessAttackable : defendersForcedToAttack;
|
||||
if (defendersToChooseFrom.size() == 1) {
|
||||
player.declareAttacker(creature.getId(), defendersToChooseFrom.iterator().next(), game, false);
|
||||
continue;
|
||||
}
|
||||
TargetDefender target = new TargetDefender(defendersToChooseFrom, creature.getId());
|
||||
target.setRequired(true);
|
||||
target.setTargetName("planeswalker or player for " + creature.getLogName() + " to attack (must attack effect)");
|
||||
if (player.chooseTarget(Outcome.Damage, target, null, game)) {
|
||||
player.declareAttacker(creature.getId(), target.getFirstTarget(), game, false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (defendersForcedToAttack.size() == 1) {
|
||||
player.declareAttacker(creature.getId(), defendersForcedToAttack.iterator().next(), game, false);
|
||||
} else {
|
||||
TargetDefender target = new TargetDefender(defendersForcedToAttack, creature.getId());
|
||||
target.setRequired(true);
|
||||
target.setTargetName("planeswalker or player for " + creature.getLogName() + " to attack (must attack effect)");
|
||||
if (player.chooseTarget(Outcome.Damage, target, null, game)) {
|
||||
player.declareAttacker(creature.getId(), target.getFirstTarget(), game, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -529,20 +540,24 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
for (Map.Entry<RestrictionEffect, Set<Ability>> entry : game.getContinuousEffects().getApplicableRestrictionEffects(attackingCreature, game).entrySet()) {
|
||||
RestrictionEffect effect = entry.getKey();
|
||||
for (Ability ability : entry.getValue()) {
|
||||
if (!effect.canAttackCheckAfter(numberAttackers, ability, game, true)) {
|
||||
if (effect.canAttackCheckAfter(numberAttackers, ability, game, true)) {
|
||||
continue;
|
||||
}
|
||||
MageObject sourceObject = ability.getSourceObject(game);
|
||||
if (attackingPlayer.isHuman()) {
|
||||
attackingPlayer.resetPlayerPassedActions();
|
||||
game.informPlayer(attackingPlayer, attackingCreature.getIdName() + " can't attack this way (" + (sourceObject == null ? "null" : sourceObject.getIdName()) + ')');
|
||||
return false;
|
||||
} else {
|
||||
}
|
||||
// remove attacking creatures for AI that are not allowed to attack
|
||||
// can create possible not allowed attack scenarios, but not sure how to solve this
|
||||
for (CombatGroup combatGroup : this.getGroups()) {
|
||||
if (combatGroup.getAttackers().contains(attackingCreatureId)) {
|
||||
if (this.getGroups()
|
||||
.stream()
|
||||
.map(CombatGroup::getAttackers)
|
||||
.flatMap(Collection::stream)
|
||||
.anyMatch(attackingCreatureId::equals)) {
|
||||
attackerToRemove = attackingCreatureId;
|
||||
}
|
||||
}
|
||||
check = true; // do the check again
|
||||
if (numberOfChecks > 50) {
|
||||
logger.error("Seems to be an AI declare attacker lock (reached 50 check iterations) " + (sourceObject == null ? "null" : sourceObject.getIdName()));
|
||||
|
@ -553,8 +568,6 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1300,22 +1313,21 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
@SuppressWarnings("deprecation")
|
||||
public boolean declareAttacker(UUID creatureId, UUID defenderId, UUID playerId, Game game) {
|
||||
Permanent attacker = game.getPermanent(creatureId);
|
||||
if (attacker != null) {
|
||||
if (!game.replaceEvent(new DeclareAttackerEvent(defenderId, creatureId, playerId))) {
|
||||
if (addAttackerToCombat(creatureId, defenderId, game)) {
|
||||
if (!attacker.hasAbility(VigilanceAbility.getInstance(), game)
|
||||
&& !attacker.hasAbility(JohanVigilanceAbility.getInstance(), game)) {
|
||||
if (attacker == null
|
||||
|| game.replaceEvent(new DeclareAttackerEvent(defenderId, creatureId, playerId))
|
||||
|| !addAttackerToCombat(creatureId, defenderId, game)) {
|
||||
return false;
|
||||
}
|
||||
if (attacker.hasAbility(VigilanceAbility.getInstance(), game)
|
||||
|| attacker.hasAbility(JohanVigilanceAbility.getInstance(), game)) {
|
||||
return true;
|
||||
}
|
||||
if (!attacker.isTapped()) {
|
||||
attacker.setTapped(true);
|
||||
attackersTappedByAttack.add(attacker.getId());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean addAttackerToCombat(UUID attackerId, UUID defenderId, Game game) {
|
||||
if (!defenders.contains(defenderId)) {
|
||||
|
|
|
@ -87,6 +87,10 @@ public interface Permanent extends Card, Controllable {
|
|||
*/
|
||||
boolean setClassLevel(int classLevel);
|
||||
|
||||
void addGoadingPlayer(UUID playerId);
|
||||
|
||||
Set<UUID> getGoadingPlayers();
|
||||
|
||||
void setCardNumber(String cid);
|
||||
|
||||
void setExpansionSetCode(String expansionSetCode);
|
||||
|
|
|
@ -72,6 +72,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
protected boolean manifested = false;
|
||||
protected boolean morphed = false;
|
||||
protected int classLevel = 1;
|
||||
protected final Set<UUID> goadingPlayers = new HashSet<>();
|
||||
protected UUID originalControllerId;
|
||||
protected UUID controllerId;
|
||||
protected UUID beforeResetControllerId;
|
||||
|
@ -165,6 +166,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
this.monstrous = permanent.monstrous;
|
||||
this.renowned = permanent.renowned;
|
||||
this.classLevel = permanent.classLevel;
|
||||
this.goadingPlayers.addAll(permanent.goadingPlayers);
|
||||
this.pairedPermanent = permanent.pairedPermanent;
|
||||
this.bandedCards.addAll(permanent.bandedCards);
|
||||
this.timesLoyaltyUsed = permanent.timesLoyaltyUsed;
|
||||
|
@ -209,6 +211,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
this.minBlockedBy = 1;
|
||||
this.maxBlockedBy = 0;
|
||||
this.copy = false;
|
||||
this.goadingPlayers.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1345,14 +1348,10 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
}
|
||||
//20101001 - 508.1c
|
||||
if (defenderId == null) {
|
||||
boolean oneCanBeAttacked = false;
|
||||
for (UUID defenderToCheckId : game.getCombat().getDefenders()) {
|
||||
if (canAttackCheckRestrictionEffects(defenderToCheckId, game)) {
|
||||
oneCanBeAttacked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!oneCanBeAttacked) {
|
||||
if (game.getCombat()
|
||||
.getDefenders()
|
||||
.stream()
|
||||
.noneMatch(defenderToCheckId -> canAttackCheckRestrictionEffects(defenderToCheckId, game))) {
|
||||
return false;
|
||||
}
|
||||
} else if (!canAttackCheckRestrictionEffects(defenderId, game)) {
|
||||
|
@ -1582,6 +1581,16 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGoadingPlayer(UUID playerId) {
|
||||
this.goadingPlayers.add(playerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getGoadingPlayers() {
|
||||
return goadingPlayers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPairedCard(MageObjectReference pairedCard) {
|
||||
this.pairedPermanent = pairedCard;
|
||||
|
|
|
@ -2625,12 +2625,11 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
Permanent attacker = game.getPermanent(attackerId);
|
||||
if (attacker != null
|
||||
&& attacker.canAttack(defenderId, game)
|
||||
&& attacker.isControlledBy(playerId)) {
|
||||
if (!game.getCombat().declareAttacker(attackerId, defenderId, playerId, game)) {
|
||||
&& attacker.isControlledBy(playerId)
|
||||
&& !game.getCombat().declareAttacker(attackerId, defenderId, playerId, game)) {
|
||||
game.undo(playerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void declareBlocker(UUID defenderId, UUID blockerId, UUID attackerId, Game game) {
|
||||
|
|
|
@ -38,6 +38,7 @@ public class TargetDefender extends TargetImpl {
|
|||
this.filter = new FilterPlaneswalkerOrPlayer(defenders);
|
||||
this.targetName = filter.getMessage();
|
||||
this.attackerId = attackerId;
|
||||
this.notTarget = true;
|
||||
}
|
||||
|
||||
public TargetDefender(final TargetDefender target) {
|
||||
|
|
|
@ -3,6 +3,8 @@ package mage.util;
|
|||
import java.awt.*;
|
||||
import java.util.Collection;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Created by IGOUDT on 5-9-2016.
|
||||
|
|
Loading…
Reference in a new issue