diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java index b24b302109e..172485b99ff 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java @@ -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 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 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 diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index d2528ce6c2b..9b041f95b19 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -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 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); } diff --git a/Mage.Sets/src/mage/cards/a/AgitatorAnt.java b/Mage.Sets/src/mage/cards/a/AgitatorAnt.java index cda362b3783..71d914889e0 100644 --- a/Mage.Sets/src/mage/cards/a/AgitatorAnt.java +++ b/Mage.Sets/src/mage/cards/a/AgitatorAnt.java @@ -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; } diff --git a/Mage.Sets/src/mage/cards/b/Besmirch.java b/Mage.Sets/src/mage/cards/b/Besmirch.java index ff160ad483e..f510600418d 100644 --- a/Mage.Sets/src/mage/cards/b/Besmirch.java +++ b/Mage.Sets/src/mage/cards/b/Besmirch.java @@ -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; } diff --git a/Mage.Sets/src/mage/cards/b/BloodthirstyBlade.java b/Mage.Sets/src/mage/cards/b/BloodthirstyBlade.java index 164776fc09f..98961f8e187 100644 --- a/Mage.Sets/src/mage/cards/b/BloodthirstyBlade.java +++ b/Mage.Sets/src/mage/cards/b/BloodthirstyBlade.java @@ -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" diff --git a/Mage.Sets/src/mage/cards/g/GeodeRager.java b/Mage.Sets/src/mage/cards/g/GeodeRager.java index 049b0f0fdcb..f88c93825e0 100644 --- a/Mage.Sets/src/mage/cards/g/GeodeRager.java +++ b/Mage.Sets/src/mage/cards/g/GeodeRager.java @@ -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; } diff --git a/Mage.Sets/src/mage/cards/k/KaimaTheFracturedCalm.java b/Mage.Sets/src/mage/cards/k/KaimaTheFracturedCalm.java new file mode 100644 index 00000000000..9f690e1b434 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KaimaTheFracturedCalm.java @@ -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; + } +} diff --git a/Mage.Sets/src/mage/cards/k/KomainuBattleArmor.java b/Mage.Sets/src/mage/cards/k/KomainuBattleArmor.java new file mode 100644 index 00000000000..61da72ca954 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KomainuBattleArmor.java @@ -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; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MarisiBreakerOfTheCoil.java b/Mage.Sets/src/mage/cards/m/MarisiBreakerOfTheCoil.java index 4c0d98ee1eb..f11c5eda0b5 100644 --- a/Mage.Sets/src/mage/cards/m/MarisiBreakerOfTheCoil.java +++ b/Mage.Sets/src/mage/cards/m/MarisiBreakerOfTheCoil.java @@ -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; } } diff --git a/Mage.Sets/src/mage/cards/m/MartialImpetus.java b/Mage.Sets/src/mage/cards/m/MartialImpetus.java index a57193f549a..7770b595339 100644 --- a/Mage.Sets/src/mage/cards/m/MartialImpetus.java +++ b/Mage.Sets/src/mage/cards/m/MartialImpetus.java @@ -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( @@ -78,7 +81,7 @@ enum MartialImpetusPredicate implements ObjectSourcePlayerPredicate { && input.getObject() != attachedTo // must be other creature && input.getObject().isAttacking() // attacking && game.getOpponents(auraControllerId) // check for opponents of aura's controller - .contains(game.getCombat().getDefendingPlayerId(input.getObject().getId(), game))) { + .contains(game.getCombat().getDefendingPlayerId(input.getObject().getId(), game))) { return true; } } diff --git a/Mage.Sets/src/mage/cards/p/ParasiticImpetus.java b/Mage.Sets/src/mage/cards/p/ParasiticImpetus.java index 9a0dad6d1e8..1967a6ffcc4 100644 --- a/Mage.Sets/src/mage/cards/p/ParasiticImpetus.java +++ b/Mage.Sets/src/mage/cards/p/ParasiticImpetus.java @@ -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( diff --git a/Mage.Sets/src/mage/cards/p/PortalMage.java b/Mage.Sets/src/mage/cards/p/PortalMage.java index 1dc20e2c301..2ba54f086a7 100644 --- a/Mage.Sets/src/mage/cards/p/PortalMage.java +++ b/Mage.Sets/src/mage/cards/p/PortalMage.java @@ -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)) { diff --git a/Mage.Sets/src/mage/cards/p/PredatoryImpetus.java b/Mage.Sets/src/mage/cards/p/PredatoryImpetus.java index 76dcd0113f3..101fa288773 100644 --- a/Mage.Sets/src/mage/cards/p/PredatoryImpetus.java +++ b/Mage.Sets/src/mage/cards/p/PredatoryImpetus.java @@ -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) { diff --git a/Mage.Sets/src/mage/cards/p/PsychicImpetus.java b/Mage.Sets/src/mage/cards/p/PsychicImpetus.java index bbbedc9098c..0c477f1680e 100644 --- a/Mage.Sets/src/mage/cards/p/PsychicImpetus.java +++ b/Mage.Sets/src/mage/cards/p/PsychicImpetus.java @@ -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( diff --git a/Mage.Sets/src/mage/cards/s/ShinyImpetus.java b/Mage.Sets/src/mage/cards/s/ShinyImpetus.java index f02de1301c2..2f0eef20584 100644 --- a/Mage.Sets/src/mage/cards/s/ShinyImpetus.java +++ b/Mage.Sets/src/mage/cards/s/ShinyImpetus.java @@ -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( diff --git a/Mage.Sets/src/mage/cards/v/VengefulAncestor.java b/Mage.Sets/src/mage/cards/v/VengefulAncestor.java new file mode 100644 index 00000000000..19b31e4d148 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VengefulAncestor.java @@ -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 { + 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; + } +} diff --git a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java index 37b5b0b584f..90c4f3ffec5 100644 --- a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java +++ b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java @@ -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)); diff --git a/Mage.Sets/src/mage/sets/NeonDynastyCommander.java b/Mage.Sets/src/mage/sets/NeonDynastyCommander.java index 79357c99a9b..55a441a1d25 100644 --- a/Mage.Sets/src/mage/sets/NeonDynastyCommander.java +++ b/Mage.Sets/src/mage/sets/NeonDynastyCommander.java @@ -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)); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/GoadTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/GoadTest.java index e0729305b41..ed3f413b762 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/GoadTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/GoadTest.java @@ -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); + } } diff --git a/Mage/src/main/java/mage/abilities/common/AttacksAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/AttacksAllTriggeredAbility.java index 47686abe0a6..e78ab1da0b6 100644 --- a/Mage/src/main/java/mage/abilities/common/AttacksAllTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/AttacksAllTriggeredAbility.java @@ -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" : "") + ", "; } } diff --git a/Mage/src/main/java/mage/abilities/common/GoadAttachedAbility.java b/Mage/src/main/java/mage/abilities/common/GoadAttachedAbility.java deleted file mode 100644 index 3f5c6b43a96..00000000000 --- a/Mage/src/main/java/mage/abilities/common/GoadAttachedAbility.java +++ /dev/null @@ -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. (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.)"; - } - - 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()); - } -} diff --git a/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackControllerDueToGoadEffect.java b/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackControllerDueToGoadEffect.java deleted file mode 100644 index 964bcf9a597..00000000000 --- a/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackControllerDueToGoadEffect.java +++ /dev/null @@ -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()); - } -} diff --git a/Mage/src/main/java/mage/abilities/effects/common/combat/GoadAttachedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/combat/GoadAttachedEffect.java new file mode 100644 index 00000000000..899aaa166c9 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/combat/GoadAttachedEffect.java @@ -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); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/combat/GoadTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/combat/GoadTargetEffect.java index e2aca007012..a61b27ca12e 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/combat/GoadTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/combat/GoadTargetEffect.java @@ -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; } diff --git a/Mage/src/main/java/mage/game/combat/Combat.java b/Mage/src/main/java/mage/game/combat/Combat.java index 482d8059a16..a4c4e224f24 100644 --- a/Mage/src/main/java/mage/game/combat/Combat.java +++ b/Mage/src/main/java/mage/game/combat/Combat.java @@ -440,65 +440,76 @@ public class Combat implements Serializable, Copyable { for (Permanent creature : player.getAvailableAttackers(game)) { boolean mustAttack = false; Set defendersForcedToAttack = new HashSet<>(); - - // check if a creature has to attack - for (Map.Entry> entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, false, game).entrySet()) { - RequirementEffect effect = entry.getKey(); - if (effect.mustAttack(game) - && checkAttackRestrictions(player, game)) { // needed for Goad Effect + if (creature.getGoadingPlayers().isEmpty()) { + // check if a creature has to attack + for (Map.Entry> entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, false, game).entrySet()) { + RequirementEffect effect = entry.getKey(); + 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)) { - defendersForcedToAttack.add(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) { - // check which defenders the forced to attack creature can attack without paying a cost - Set defendersCostlessAttackable = new HashSet<>(defenders); - for (UUID defenderId : defenders) { - if (game.getContinuousEffects().checkIfThereArePayCostToAttackBlockEffects( - new DeclareAttackerEvent(defenderId, creature.getId(), creature.getControllerId()), game)) { - defendersCostlessAttackable.remove(defenderId); - defendersForcedToAttack.remove(defenderId); - } - } - // force attack only if a defender can be attacked without paying a cost - if (!defendersCostlessAttackable.isEmpty()) { - 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()); - 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); - } - } - } - } - + if (!mustAttack) { + continue; + } + // check which defenders the forced to attack creature can attack without paying a cost + Set defendersCostlessAttackable = new HashSet<>(defenders); + for (UUID defenderId : defenders) { + if (game.getContinuousEffects().checkIfThereArePayCostToAttackBlockEffects( + new DeclareAttackerEvent(defenderId, creature.getId(), creature.getControllerId()), game + )) { + defendersCostlessAttackable.remove(defenderId); + defendersForcedToAttack.remove(defenderId); + continue; + } + for (Map.Entry> 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()) { + continue; + } + creaturesForcedToAttack.put(creature.getId(), defendersForcedToAttack); + // No need to attack a special defender + Set 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); } - } } @@ -529,28 +540,30 @@ public class Combat implements Serializable, Copyable { for (Map.Entry> entry : game.getContinuousEffects().getApplicableRestrictionEffects(attackingCreature, game).entrySet()) { RestrictionEffect effect = entry.getKey(); for (Ability ability : entry.getValue()) { - if (!effect.canAttackCheckAfter(numberAttackers, ability, game, true)) { - 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)) { - 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())); - return true; // break the check - } - continue Check; - } + 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; + } + // 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 + 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())); + return true; // break the check + } + continue Check; } } } @@ -1300,21 +1313,20 @@ public class Combat implements Serializable, Copyable { @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.isTapped()) { - attacker.setTapped(true); - attackersTappedByAttack.add(attacker.getId()); - } - } - return true; - } - } + if (attacker == null + || game.replaceEvent(new DeclareAttackerEvent(defenderId, creatureId, playerId)) + || !addAttackerToCombat(creatureId, defenderId, game)) { + return false; } - 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; } public boolean addAttackerToCombat(UUID attackerId, UUID defenderId, Game game) { diff --git a/Mage/src/main/java/mage/game/permanent/Permanent.java b/Mage/src/main/java/mage/game/permanent/Permanent.java index b91b9f740ae..32573f8c066 100644 --- a/Mage/src/main/java/mage/game/permanent/Permanent.java +++ b/Mage/src/main/java/mage/game/permanent/Permanent.java @@ -87,6 +87,10 @@ public interface Permanent extends Card, Controllable { */ boolean setClassLevel(int classLevel); + void addGoadingPlayer(UUID playerId); + + Set getGoadingPlayers(); + void setCardNumber(String cid); void setExpansionSetCode(String expansionSetCode); diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index 22387e8885c..2864d96bf17 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -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 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 getGoadingPlayers() { + return goadingPlayers; + } + @Override public void setPairedCard(MageObjectReference pairedCard) { this.pairedPermanent = pairedCard; diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 3b7f1571833..51fc3e8602e 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -2625,10 +2625,9 @@ 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)) { - game.undo(playerId); - } + && attacker.isControlledBy(playerId) + && !game.getCombat().declareAttacker(attackerId, defenderId, playerId, game)) { + game.undo(playerId); } } diff --git a/Mage/src/main/java/mage/target/common/TargetDefender.java b/Mage/src/main/java/mage/target/common/TargetDefender.java index d83839e8060..d89d3a35f6e 100644 --- a/Mage/src/main/java/mage/target/common/TargetDefender.java +++ b/Mage/src/main/java/mage/target/common/TargetDefender.java @@ -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) { diff --git a/Mage/src/main/java/mage/util/RandomUtil.java b/Mage/src/main/java/mage/util/RandomUtil.java index 5f2458c4dcc..b0424863455 100644 --- a/Mage/src/main/java/mage/util/RandomUtil.java +++ b/Mage/src/main/java/mage/util/RandomUtil.java @@ -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.