From a6c5209a2a59a7a4e8bfb95b9095d64dea07cc35 Mon Sep 17 00:00:00 2001 From: "Alex W. Jackson" Date: Wed, 7 Sep 2022 22:36:05 -0400 Subject: [PATCH] Combat.getAttackers and Combat.getBlockers now return a Set instead of a List, so that two-headed blockers aren't included twice --- .../src/mage/player/ai/ComputerPlayer6.java | 2 +- .../java/mage/player/ai/ComputerPlayer.java | 4 +- Mage.Sets/src/mage/cards/f/FinestHour.java | 101 +++--------------- .../src/mage/cards/f/FractalHarness.java | 7 +- .../src/mage/cards/i/IllusionistsGambit.java | 12 +-- .../mage/cards/k/KarlachFuryOfAvernus.java | 15 +-- .../src/mage/cards/l/LightmineField.java | 7 +- Mage.Sets/src/mage/cards/m/MageSlayer.java | 42 ++++---- .../src/mage/cards/m/MandateOfPeace.java | 6 +- .../src/mage/cards/r/ReapersTalisman.java | 62 ++--------- Mage.Sets/src/mage/cards/s/SigilOfValor.java | 85 +++------------ Mage.Sets/src/mage/cards/s/SpikedRipsaw.java | 3 +- Mage.Sets/src/mage/cards/t/TwoHandedAxe.java | 7 +- .../AttacksAloneAttachedTriggeredAbility.java | 44 ++++++++ ...ttacksAloneControlledTriggeredAbility.java | 7 +- .../AttacksAloneSourceTriggeredAbility.java | 25 ++--- .../AttacksAttachedTriggeredAbility.java | 47 ++++---- .../common/FirstCombatPhaseCondition.java | 20 ++++ .../abilities/keyword/ExaltedAbility.java | 35 ++---- .../main/java/mage/game/combat/Combat.java | 15 ++- 20 files changed, 192 insertions(+), 354 deletions(-) create mode 100644 Mage/src/main/java/mage/abilities/common/AttacksAloneAttachedTriggeredAbility.java create mode 100644 Mage/src/main/java/mage/abilities/condition/common/FirstCombatPhaseCondition.java diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java index 8a1a35e200..19b668adf1 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java @@ -844,7 +844,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { } private List getAttackers(Game game) { - List attackersUUID = game.getCombat().getAttackers(); + Set attackersUUID = game.getCombat().getAttackers(); if (attackersUUID.isEmpty()) { return null; } 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 ce1d889d3f..8cece00e95 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 @@ -2815,7 +2815,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { log.debug(sb.toString()); } - private void playRemoval(List creatures, Game game) { + private void playRemoval(Set creatures, Game game) { for (UUID creatureId : creatures) { for (Card card : this.playableInstant) { if (card.getSpellAbility().canActivate(playerId, game).canActivate()) { @@ -2833,7 +2833,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { } } - private void playDamage(List creatures, Game game) { + private void playDamage(Set creatures, Game game) { for (UUID creatureId : creatures) { Permanent creature = game.getPermanent(creatureId); for (Card card : this.playableInstant) { diff --git a/Mage.Sets/src/mage/cards/f/FinestHour.java b/Mage.Sets/src/mage/cards/f/FinestHour.java index e53d500137..b594c923a2 100644 --- a/Mage.Sets/src/mage/cards/f/FinestHour.java +++ b/Mage.Sets/src/mage/cards/f/FinestHour.java @@ -2,25 +2,19 @@ package mage.cards.f; import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.Effect; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.common.AttacksAloneControlledTriggeredAbility; +import mage.abilities.condition.common.FirstCombatPhaseCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.abilities.effects.common.AdditionalCombatPhaseEffect; import mage.abilities.keyword.ExaltedAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.TurnPhase; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.game.turn.TurnMod; -import mage.target.targetpointer.FixedTarget; /** * - * @author BetaSteward_at_googlemail.com + * @author awjackson */ public final class FinestHour extends CardImpl { @@ -32,7 +26,14 @@ public final class FinestHour extends CardImpl { // Whenever a creature you control attacks alone, if it's the first combat phase of the turn, untap that // creature. After this phase, there is an additional combat phase. - this.addAbility(new FinestHourAbility()); + Ability ability = new ConditionalInterveningIfTriggeredAbility( + new AttacksAloneControlledTriggeredAbility(new UntapTargetEffect("untap that creature"), true, false), + FirstCombatPhaseCondition.instance, + "Whenever a creature you control attacks alone, if it's the first combat phase of the turn, " + + "untap that creature. After this phase, there is an additional combat phase." + ); + ability.addEffect(new AdditionalCombatPhaseEffect()); + this.addAbility(ability); } private FinestHour(final FinestHour card) { @@ -43,78 +44,4 @@ public final class FinestHour extends CardImpl { public FinestHour copy() { return new FinestHour(this); } - -} - -class FinestHourAbility extends TriggeredAbilityImpl { - - public FinestHourAbility() { - super(Zone.BATTLEFIELD, new FinestHourEffect()); - } - - public FinestHourAbility(final FinestHourAbility ability) { - super(ability); - } - - @Override - public FinestHourAbility copy() { - return new FinestHourAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DECLARED_ATTACKERS; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (game.isActivePlayer(this.controllerId)) { - if (game.getCombat().attacksAlone()) { - for (Effect effect : this.getEffects()) { - effect.setTargetPointer(new FixedTarget(game.getCombat().getAttackers().get(0), game)); - } - return true; - } - } - return false; - } - - @Override - public boolean checkInterveningIfClause(Game game) { - return game.getTurn().getPhase(TurnPhase.COMBAT).getCount() == 0; - } - - @Override - public String getRule() { - return "Whenever a creature you control attacks alone, if it's the first combat phase of the turn, untap that creature. After this phase, there is an additional combat phase."; - } - -} - -class FinestHourEffect extends OneShotEffect { - - public FinestHourEffect() { - super(Outcome.Benefit); - } - - public FinestHourEffect(final FinestHourEffect effect) { - super(effect); - } - - @Override - public FinestHourEffect copy() { - return new FinestHourEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(this.getTargetPointer().getFirst(game, source)); - if (permanent != null) { - permanent.untap(game); - game.getState().getTurnMods().add(new TurnMod(source.getControllerId(), TurnPhase.COMBAT, null, false)); - return true; - } - return false; - } - } diff --git a/Mage.Sets/src/mage/cards/f/FractalHarness.java b/Mage.Sets/src/mage/cards/f/FractalHarness.java index a969ebe165..25efcc29d6 100644 --- a/Mage.Sets/src/mage/cards/f/FractalHarness.java +++ b/Mage.Sets/src/mage/cards/f/FractalHarness.java @@ -8,10 +8,7 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.EquipAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AttachmentType; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.SubType; +import mage.constants.*; import mage.counters.CounterType; import mage.game.Game; import mage.game.permanent.Permanent; @@ -37,7 +34,7 @@ public final class FractalHarness extends CardImpl { // Whenever equipped creature attacks, double the number of +1/+1 counters on it. this.addAbility(new AttacksAttachedTriggeredAbility( - new FractalHarnessDoubleEffect(), AttachmentType.EQUIPMENT, false, true + new FractalHarnessDoubleEffect(), AttachmentType.EQUIPMENT, false, SetTargetPointer.PERMANENT )); // Equip {2} diff --git a/Mage.Sets/src/mage/cards/i/IllusionistsGambit.java b/Mage.Sets/src/mage/cards/i/IllusionistsGambit.java index b7c678c7ae..c41cd46e38 100644 --- a/Mage.Sets/src/mage/cards/i/IllusionistsGambit.java +++ b/Mage.Sets/src/mage/cards/i/IllusionistsGambit.java @@ -15,7 +15,7 @@ import mage.game.permanent.Permanent; import mage.game.turn.Phase; import mage.game.turn.TurnMod; -import java.util.List; +import java.util.Set; import java.util.Objects; import java.util.UUID; @@ -62,7 +62,7 @@ class IllusionistsGambitRemoveFromCombatEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - List attackers = game.getCombat().getAttackers(); + Set attackers = game.getCombat().getAttackers(); for (UUID attackerId : attackers) { Permanent creature = game.getPermanent(attackerId); if (creature != null) { @@ -85,10 +85,10 @@ class IllusionistsGambitRemoveFromCombatEffect extends OneShotEffect { class IllusionistsGambitRequirementEffect extends RequirementEffect { - private List attackers; + private Set attackers; private Phase phase; - public IllusionistsGambitRequirementEffect(List attackers, Phase phase) { + public IllusionistsGambitRequirementEffect(Set attackers, Phase phase) { super(Duration.Custom); this.attackers = attackers; this.phase = phase; @@ -135,10 +135,10 @@ class IllusionistsGambitRequirementEffect extends RequirementEffect { class IllusionistsGambitRestrictionEffect extends RestrictionEffect { - private final List attackers; + private final Set attackers; private final Phase phase; - public IllusionistsGambitRestrictionEffect(List attackers, Phase phase) { + public IllusionistsGambitRestrictionEffect(Set attackers, Phase phase) { super(Duration.Custom, Outcome.Benefit); this.attackers = attackers; this.phase = phase; diff --git a/Mage.Sets/src/mage/cards/k/KarlachFuryOfAvernus.java b/Mage.Sets/src/mage/cards/k/KarlachFuryOfAvernus.java index 2146e2aee4..67be772756 100644 --- a/Mage.Sets/src/mage/cards/k/KarlachFuryOfAvernus.java +++ b/Mage.Sets/src/mage/cards/k/KarlachFuryOfAvernus.java @@ -4,7 +4,7 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; import mage.abilities.common.ChooseABackgroundAbility; -import mage.abilities.condition.Condition; +import mage.abilities.condition.common.FirstCombatPhaseCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.AdditionalCombatPhaseEffect; import mage.abilities.effects.common.UntapAllEffect; @@ -32,11 +32,11 @@ public final class KarlachFuryOfAvernus extends CardImpl { this.power = new MageInt(5); this.toughness = new MageInt(4); - // Whenever you attack, if its the first combat phase of the turn, untap all attacking creatures. They gain first strike until end of turn. After this phase, there is an additional combat phase. + // Whenever you attack, if it's the first combat phase of the turn, untap all attacking creatures. They gain first strike until end of turn. After this phase, there is an additional combat phase. Ability ability = new ConditionalInterveningIfTriggeredAbility( new AttacksWithCreaturesTriggeredAbility( new UntapAllEffect(StaticFilters.FILTER_ATTACKING_CREATURES), 1 - ), KarlachFuryOfAvernusCondition.instance, "Whenever you attack, if its the first " + + ), FirstCombatPhaseCondition.instance, "Whenever you attack, if it's the first " + "combat phase of the turn, untap all attacking creatures. They gain first strike " + "until end of turn. After this phase, there is an additional combat phase." ); @@ -60,12 +60,3 @@ public final class KarlachFuryOfAvernus extends CardImpl { return new KarlachFuryOfAvernus(this); } } - -enum KarlachFuryOfAvernusCondition implements Condition { - instance; - - @Override - public boolean apply(Game game, Ability source) { - return game.getTurn().getPhase(TurnPhase.COMBAT).getCount() == 0; - } -} diff --git a/Mage.Sets/src/mage/cards/l/LightmineField.java b/Mage.Sets/src/mage/cards/l/LightmineField.java index c6cfc878a8..dc6d7a0a30 100644 --- a/Mage.Sets/src/mage/cards/l/LightmineField.java +++ b/Mage.Sets/src/mage/cards/l/LightmineField.java @@ -1,9 +1,7 @@ - package mage.cards.l; import java.util.HashSet; import java.util.Iterator; -import java.util.List; import java.util.Set; import java.util.UUID; import mage.MageObjectReference; @@ -99,10 +97,9 @@ class LightmineFieldEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - List attackers = game.getCombat().getAttackers(); - int damage = attackers.size(); + int damage = game.getCombat().getAttackers().size(); Set attackSet = (Set) getValue("Lightmine Field"); - if (!attackers.isEmpty()) { + if (damage > 0) { for (Iterator it = attackSet.iterator(); it.hasNext();) { MageObjectReference attacker = it.next(); Permanent creature = attacker.getPermanent(game); diff --git a/Mage.Sets/src/mage/cards/m/MageSlayer.java b/Mage.Sets/src/mage/cards/m/MageSlayer.java index a19286ecf4..cd4e036008 100644 --- a/Mage.Sets/src/mage/cards/m/MageSlayer.java +++ b/Mage.Sets/src/mage/cards/m/MageSlayer.java @@ -1,24 +1,19 @@ - package mage.cards.m; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.AttacksAttachedTriggeredAbility; -import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.EquipAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Outcome; +import mage.constants.*; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.target.common.TargetControlledCreaturePermanent; /** * - * @author jeffwadsworth + * @author awjackson */ public final class MageSlayer extends CardImpl { @@ -26,11 +21,13 @@ public final class MageSlayer extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{R}{G}"); this.subtype.add(SubType.EQUIPMENT); - // Whenever equipped creature attacks, it deals damage equal to its power to defending player. - this.addAbility(new AttacksAttachedTriggeredAbility(new MageSlayerEffect(), false)); + // Whenever equipped creature attacks, it deals damage equal to its power to the player or planeswalker it's attacking + this.addAbility(new AttacksAttachedTriggeredAbility( + new MageSlayerEffect(), AttachmentType.EQUIPMENT, false, SetTargetPointer.PERMANENT + )); // Equip {3} - this.addAbility(new EquipAbility(Outcome.Benefit, new GenericManaCost(3), new TargetControlledCreaturePermanent(), false)); + this.addAbility(new EquipAbility(3, false)); } private MageSlayer(final MageSlayer card) { @@ -61,18 +58,19 @@ class MageSlayerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent equipment = game.getPermanent(source.getSourceId()); - if (equipment != null && equipment.getAttachedTo() != null) { - int power = game.getPermanent(equipment.getAttachedTo()).getPower().getValue(); - UUID defenderId = game.getCombat().getDefenderId(equipment.getAttachedTo()); - if (power > 0 && defenderId != null) { - UUID sourceId = (UUID) this.getValue("sourceId"); - if (sourceId != null) { - game.damagePlayerOrPlaneswalker(defenderId, power, source.getSourceId(), source, game, false, true); - } - } - return true; + Permanent attacker = getTargetPointer().getFirstTargetPermanentOrLKI(game, source); + if (attacker == null) { + return false; } - return false; + game.damagePlayerOrPlaneswalker( + game.getCombat().getDefenderId(attacker.getId()), + attacker.getPower().getValue(), + attacker.getId(), + source, + game, + false, + true + ); + return true; } } diff --git a/Mage.Sets/src/mage/cards/m/MandateOfPeace.java b/Mage.Sets/src/mage/cards/m/MandateOfPeace.java index 8b99ab9412..d0a89728b0 100644 --- a/Mage.Sets/src/mage/cards/m/MandateOfPeace.java +++ b/Mage.Sets/src/mage/cards/m/MandateOfPeace.java @@ -1,6 +1,6 @@ package mage.cards.m; -import java.util.List; +import java.util.Set; import java.util.UUID; import java.util.stream.Stream; @@ -108,8 +108,8 @@ class MandateOfPeaceEndCombatEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Combat combat = game.getCombat(); - List attackerIds = combat.getAttackers(); - List blockerIds = combat.getBlockers(); + Set attackerIds = combat.getAttackers(); + Set blockerIds = combat.getBlockers(); Stream.concat(blockerIds.stream(), attackerIds.stream()) .map(id -> game.getPermanent(id)) .filter(e -> e != null) diff --git a/Mage.Sets/src/mage/cards/r/ReapersTalisman.java b/Mage.Sets/src/mage/cards/r/ReapersTalisman.java index 4867bdb6d1..8ef85e5b08 100644 --- a/Mage.Sets/src/mage/cards/r/ReapersTalisman.java +++ b/Mage.Sets/src/mage/cards/r/ReapersTalisman.java @@ -2,9 +2,9 @@ package mage.cards.r; import java.util.UUID; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.Ability; +import mage.abilities.common.AttacksAloneAttachedTriggeredAbility; import mage.abilities.common.AttacksAttachedTriggeredAbility; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.LoseLifeTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; @@ -13,14 +13,10 @@ import mage.abilities.keyword.EquipAbility; import mage.constants.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.target.targetpointer.FixedTarget; /** * - * @author weirddan455 + * @author awjackson */ public final class ReapersTalisman extends CardImpl { @@ -38,7 +34,12 @@ public final class ReapersTalisman extends CardImpl { ))); // Whenever equipped creature attacks alone, defending player loses 2 life and you gain 2 life. - this.addAbility(new ReapersTalismanAttacksLoneTriggeredAbility()); + Ability ability = new AttacksAloneAttachedTriggeredAbility( + new LoseLifeTargetEffect(2).setText("defending player loses 2 life"), + AttachmentType.EQUIPMENT, false, SetTargetPointer.PLAYER + ); + ability.addEffect(new GainLifeEffect(2).concatBy("and")); + this.addAbility(ability); // Equip {2} this.addAbility(new EquipAbility(2)); @@ -53,48 +54,3 @@ public final class ReapersTalisman extends CardImpl { return new ReapersTalisman(this); } } - -class ReapersTalismanAttacksLoneTriggeredAbility extends TriggeredAbilityImpl { - - public ReapersTalismanAttacksLoneTriggeredAbility() { - super(Zone.BATTLEFIELD, new LoseLifeTargetEffect(2)); - this.addEffect(new GainLifeEffect(2)); - } - - private ReapersTalismanAttacksLoneTriggeredAbility(final ReapersTalismanAttacksLoneTriggeredAbility ability) { - super(ability); - } - - @Override - public ReapersTalismanAttacksLoneTriggeredAbility copy() { - return new ReapersTalismanAttacksLoneTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DECLARED_ATTACKERS; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (game.isActivePlayer(this.controllerId) && game.getCombat().attacksAlone()) { - Permanent equipment = game.getPermanent(this.sourceId); - UUID attackerId = game.getCombat().getAttackers().get(0); - if (equipment != null && equipment.isAttachedTo(attackerId)) { - UUID defender = game.getCombat().getDefendingPlayerId(attackerId, game); - if (defender != null) { - for (Effect effect : this.getEffects()) { - effect.setTargetPointer(new FixedTarget(defender)); - } - } - return true; - } - } - return false; - } - - @Override - public String getRule() { - return "Whenever equipped creature attacks alone, defending player loses 2 life and you gain 2 life."; - } -} diff --git a/Mage.Sets/src/mage/cards/s/SigilOfValor.java b/Mage.Sets/src/mage/cards/s/SigilOfValor.java index aea9436e7d..f20866bdab 100644 --- a/Mage.Sets/src/mage/cards/s/SigilOfValor.java +++ b/Mage.Sets/src/mage/cards/s/SigilOfValor.java @@ -1,8 +1,7 @@ package mage.cards.s; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.common.AttacksAloneAttachedTriggeredAbility; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; import mage.abilities.effects.common.continuous.BoostTargetEffect; @@ -10,19 +9,15 @@ import mage.abilities.keyword.EquipAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.CardIdPredicate; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.target.targetpointer.FixedTarget; import java.util.UUID; /** - * @author LevelX2 + * @author awjackson */ public final class SigilOfValor extends CardImpl { @@ -31,10 +26,13 @@ public final class SigilOfValor extends CardImpl { this.subtype.add(SubType.EQUIPMENT); // Whenever equipped creature attacks alone, it gets +1/+1 until end of turn for each other creature you control. - this.addAbility(new SigilOfValorTriggeredAbility(new SigilOfValorCount())); + this.addAbility(new AttacksAloneAttachedTriggeredAbility( + new BoostTargetEffect(SigilOfValorCount.instance, SigilOfValorCount.instance, Duration.EndOfTurn), + AttachmentType.EQUIPMENT, false, SetTargetPointer.PERMANENT + )); // Equip {1} - this.addAbility(new EquipAbility(Outcome.AddAbility, new GenericManaCost(1))); + this.addAbility(new EquipAbility(1)); } private SigilOfValor(final SigilOfValor card) { @@ -47,81 +45,32 @@ public final class SigilOfValor extends CardImpl { } } -class SigilOfValorTriggeredAbility extends TriggeredAbilityImpl { - - public SigilOfValorTriggeredAbility(DynamicValue boostValue) { - super(Zone.BATTLEFIELD, new BoostTargetEffect(boostValue, boostValue, Duration.EndOfTurn)); - } - - public SigilOfValorTriggeredAbility(final SigilOfValorTriggeredAbility ability) { - super(ability); - } - - @Override - public SigilOfValorTriggeredAbility copy() { - return new SigilOfValorTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DECLARED_ATTACKERS; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (game.isActivePlayer(getControllerId())) { - if (game.getCombat().attacksAlone()) { - Permanent equipment = game.getPermanent(getSourceId()); - UUID attackerId = game.getCombat().getAttackers().get(0); - if (equipment != null - && equipment.isAttachedTo(attackerId)) { - this.getEffects().get(0).setTargetPointer(new FixedTarget(attackerId, game)); - return true; - } - } - } - return false; - } - - @Override - public String getRule() { - return "Whenever equipped creature attacks alone, it gets +1/+1 until end of turn for each other creature you control."; - } - -} - -class SigilOfValorCount implements DynamicValue { - - public SigilOfValorCount() { - } - - public SigilOfValorCount(final SigilOfValorCount dynamicValue) { - super(); - } +enum SigilOfValorCount implements DynamicValue { + instance; @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - Permanent equipment = game.getPermanent(sourceAbility.getSourceId()); - if (equipment != null && equipment.getAttachedTo() != null) { - FilterPermanent filterPermanent = new FilterControlledCreaturePermanent(); - filterPermanent.add(Predicates.not(new CardIdPredicate(equipment.getAttachedTo()))); - return game.getBattlefield().count(filterPermanent, sourceAbility.getControllerId(), sourceAbility, game); + UUID attackerId = effect.getTargetPointer().getFirst(game, sourceAbility); + if (attackerId != null) { + FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); + filter.add(Predicates.not(new CardIdPredicate(attackerId))); + return game.getBattlefield().count(filter, sourceAbility.getControllerId(), sourceAbility, game); } return 0; } @Override public DynamicValue copy() { - return new SigilOfValorCount(this); + return instance; } @Override public String toString() { - return "X"; + return "1"; } @Override public String getMessage() { - return ""; + return "other creature you control"; } } diff --git a/Mage.Sets/src/mage/cards/s/SpikedRipsaw.java b/Mage.Sets/src/mage/cards/s/SpikedRipsaw.java index 6b16b2be20..94d587a810 100644 --- a/Mage.Sets/src/mage/cards/s/SpikedRipsaw.java +++ b/Mage.Sets/src/mage/cards/s/SpikedRipsaw.java @@ -12,6 +12,7 @@ import mage.abilities.keyword.EquipAbility; import mage.abilities.keyword.TrampleAbility; import mage.constants.AttachmentType; import mage.constants.Duration; +import mage.constants.SetTargetPointer; import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -37,7 +38,7 @@ public final class SpikedRipsaw extends CardImpl { // Whenever equipped creature attacks, you may sacrifice a Forest. If you do, that creature gains trample until end of turn. this.addAbility(new AttacksAttachedTriggeredAbility( new DoIfCostPaid(new GainAbilityTargetEffect(TrampleAbility.getInstance(), Duration.EndOfTurn, "that creature gains trample until end of turn"), new SacrificeTargetCost(filter)), - AttachmentType.EQUIPMENT, false, true + AttachmentType.EQUIPMENT, false, SetTargetPointer.PERMANENT )); // Equip {3} diff --git a/Mage.Sets/src/mage/cards/t/TwoHandedAxe.java b/Mage.Sets/src/mage/cards/t/TwoHandedAxe.java index 552a99531a..8831602e87 100644 --- a/Mage.Sets/src/mage/cards/t/TwoHandedAxe.java +++ b/Mage.Sets/src/mage/cards/t/TwoHandedAxe.java @@ -10,10 +10,7 @@ import mage.abilities.keyword.DoubleStrikeAbility; import mage.abilities.keyword.EquipAbility; import mage.cards.AdventureCard; import mage.cards.CardSetInfo; -import mage.constants.AttachmentType; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.SubType; +import mage.constants.*; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.common.TargetControlledCreaturePermanent; @@ -33,7 +30,7 @@ public final class TwoHandedAxe extends AdventureCard { // Whenever equipped creature attacks, double its power until end of turn. this.addAbility(new AttacksAttachedTriggeredAbility( - new TwoHandedAxeEffect(), AttachmentType.EQUIPMENT, false, true + new TwoHandedAxeEffect(), AttachmentType.EQUIPMENT, false, SetTargetPointer.PERMANENT )); // Equip {1}{R} diff --git a/Mage/src/main/java/mage/abilities/common/AttacksAloneAttachedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/AttacksAloneAttachedTriggeredAbility.java new file mode 100644 index 0000000000..e5b498b726 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/AttacksAloneAttachedTriggeredAbility.java @@ -0,0 +1,44 @@ +package mage.abilities.common; + +import mage.abilities.effects.Effect; +import mage.constants.AttachmentType; +import mage.constants.SetTargetPointer; +import mage.game.Game; +import mage.game.events.GameEvent; + +/** + * @author awjackson + */ +public class AttacksAloneAttachedTriggeredAbility extends AttacksAttachedTriggeredAbility { + + public AttacksAloneAttachedTriggeredAbility(Effect effect) { + this(effect, false); + } + + public AttacksAloneAttachedTriggeredAbility(Effect effect, boolean optional) { + this(effect, AttachmentType.EQUIPMENT, optional); + } + + public AttacksAloneAttachedTriggeredAbility(Effect effect, AttachmentType attachmentType, boolean optional) { + this(effect, attachmentType, optional, SetTargetPointer.NONE); + } + + public AttacksAloneAttachedTriggeredAbility(Effect effect, AttachmentType attachmentType, boolean optional, SetTargetPointer setTargetPointer) { + super(effect, attachmentType, optional, setTargetPointer); + setTriggerPhrase("Whenever " + attachmentType.verb().toLowerCase() + " creature attacks alone, "); + } + + protected AttacksAloneAttachedTriggeredAbility(final AttacksAloneAttachedTriggeredAbility ability) { + super(ability); + } + + @Override + public AttacksAloneAttachedTriggeredAbility copy() { + return new AttacksAloneAttachedTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return game.getCombat().attacksAlone() && super.checkTrigger(event, game); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/AttacksAloneControlledTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/AttacksAloneControlledTriggeredAbility.java index 25eb36beab..4bc9106aa8 100644 --- a/Mage/src/main/java/mage/abilities/common/AttacksAloneControlledTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/AttacksAloneControlledTriggeredAbility.java @@ -10,7 +10,6 @@ import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; -import mage.util.CardUtil; /** * @author TheElk801 @@ -39,7 +38,7 @@ public class AttacksAloneControlledTriggeredAbility extends TriggeredAbilityImpl setTriggerPhrase("Whenever " + CardUtil.addArticle(filter.getMessage()) + " attacks alone, "); } - private AttacksAloneControlledTriggeredAbility(final AttacksAloneControlledTriggeredAbility ability) { + protected AttacksAloneControlledTriggeredAbility(final AttacksAloneControlledTriggeredAbility ability) { super(ability); this.filter = ability.filter; this.setTargetPointer = ability.setTargetPointer; @@ -52,7 +51,7 @@ public class AttacksAloneControlledTriggeredAbility extends TriggeredAbilityImpl @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DECLARED_ATTACKERS; + return event.getType() == GameEvent.EventType.ATTACKER_DECLARED; } @Override @@ -60,7 +59,7 @@ public class AttacksAloneControlledTriggeredAbility extends TriggeredAbilityImpl if (!game.getCombat().attacksAlone()) { return false; } - Permanent permanent = game.getPermanent(game.getCombat().getAttackers().get(0)); + Permanent permanent = game.getPermanent(event.getSourceId()); if (permanent == null || !filter.match(permanent, getControllerId(), this, game)) { return false; } diff --git a/Mage/src/main/java/mage/abilities/common/AttacksAloneSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/AttacksAloneSourceTriggeredAbility.java index a8c350f2c3..66dbdb2166 100644 --- a/Mage/src/main/java/mage/abilities/common/AttacksAloneSourceTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/AttacksAloneSourceTriggeredAbility.java @@ -1,8 +1,5 @@ - package mage.abilities.common; -import java.util.Objects; -import java.util.UUID; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.constants.Zone; @@ -21,7 +18,7 @@ public class AttacksAloneSourceTriggeredAbility extends TriggeredAbilityImpl { setTriggerPhrase("Whenever {this} attacks alone, "); } - public AttacksAloneSourceTriggeredAbility(final AttacksAloneSourceTriggeredAbility ability) { + protected AttacksAloneSourceTriggeredAbility(final AttacksAloneSourceTriggeredAbility ability) { super(ability); } @@ -32,25 +29,15 @@ public class AttacksAloneSourceTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DECLARED_ATTACKERS; + return event.getType() == GameEvent.EventType.ATTACKER_DECLARED; } @Override public boolean checkTrigger(GameEvent event, Game game) { - if(game.isActivePlayer(this.controllerId) ) { - UUID creatureId = this.getSourceId(); - if(creatureId != null) { - if(game.getCombat().attacksAlone() && Objects.equals(creatureId, game.getCombat().getAttackers().get(0))) { - UUID defender = game.getCombat().getDefenderId(creatureId); - if(defender != null) { - for(Effect effect: getEffects()) { - effect.setTargetPointer(new FixedTarget(defender)); - } - } - return true; - } - } + if (!getSourceId().equals(event.getSourceId()) || !game.getCombat().attacksAlone()) { + return false; } - return false; + getEffects().setTargetPointer(new FixedTarget(game.getCombat().getDefendingPlayerId(getSourceId(), game))); + return true; } } diff --git a/Mage/src/main/java/mage/abilities/common/AttacksAttachedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/AttacksAttachedTriggeredAbility.java index a694128dbc..ed662d8dca 100644 --- a/Mage/src/main/java/mage/abilities/common/AttacksAttachedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/AttacksAttachedTriggeredAbility.java @@ -1,16 +1,16 @@ - package mage.abilities.common; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.constants.AttachmentType; +import mage.constants.SetTargetPointer; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; -import java.util.Locale; +import java.util.UUID; /** * "When enchanted/equipped creature attacks " triggered ability @@ -20,7 +20,7 @@ import java.util.Locale; public class AttacksAttachedTriggeredAbility extends TriggeredAbilityImpl { private final AttachmentType attachmentType; - private final boolean setTargetPointer; + private final SetTargetPointer setTargetPointer; public AttacksAttachedTriggeredAbility(Effect effect) { this(effect, false); @@ -31,20 +31,20 @@ public class AttacksAttachedTriggeredAbility extends TriggeredAbilityImpl { } public AttacksAttachedTriggeredAbility(Effect effect, AttachmentType attachmentType, boolean optional) { - this(effect, attachmentType, optional, false); + this(effect, attachmentType, optional, SetTargetPointer.NONE); } - public AttacksAttachedTriggeredAbility(Effect effect, AttachmentType attachmentType, boolean optional, boolean setTargetPointer) { + public AttacksAttachedTriggeredAbility(Effect effect, AttachmentType attachmentType, boolean optional, SetTargetPointer setTargetPointer) { super(Zone.BATTLEFIELD, effect, optional); this.attachmentType = attachmentType; this.setTargetPointer = setTargetPointer; - setTriggerPhrase("Whenever " + attachmentType.verb().toLowerCase(Locale.ENGLISH) + " creature attacks, "); + setTriggerPhrase("Whenever " + attachmentType.verb().toLowerCase() + " creature attacks, "); } - public AttacksAttachedTriggeredAbility(final AttacksAttachedTriggeredAbility abiltity) { - super(abiltity); - this.attachmentType = abiltity.attachmentType; - this.setTargetPointer = abiltity.setTargetPointer; + protected AttacksAttachedTriggeredAbility(final AttacksAttachedTriggeredAbility ability) { + super(ability); + this.attachmentType = ability.attachmentType; + this.setTargetPointer = ability.setTargetPointer; } @Override @@ -59,20 +59,19 @@ public class AttacksAttachedTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - Permanent equipment = game.getPermanent(this.sourceId); - if (equipment != null && equipment.getAttachedTo() != null - && event.getSourceId().equals(equipment.getAttachedTo())) { - getEffects().setValue("sourceId", event.getSourceId()); - // TODO: Passing a permanent object like this can cause bugs. May need refactoring to use UUID instead. - // See https://github.com/magefree/mage/issues/8377 - // 11-08-2021: Added a new constructor to set target pointer. Should probably be using this instead. - Permanent attachedPermanent = game.getPermanent(event.getSourceId()); - getEffects().setValue("attachedPermanent", attachedPermanent); - if (setTargetPointer && attachedPermanent != null) { - getEffects().setTargetPointer(new FixedTarget(attachedPermanent, game)); - } - return true; + Permanent attachment = getSourcePermanentOrLKI(game); + UUID attackerId = event.getSourceId(); + if (attachment == null || !attackerId.equals(attachment.getAttachedTo())) { + return false; } - return false; + switch (setTargetPointer) { + case PERMANENT: + getEffects().setTargetPointer(new FixedTarget(attackerId, game)); + break; + case PLAYER: + getEffects().setTargetPointer(new FixedTarget(game.getCombat().getDefendingPlayerId(attackerId, game))); + break; + } + return true; } } diff --git a/Mage/src/main/java/mage/abilities/condition/common/FirstCombatPhaseCondition.java b/Mage/src/main/java/mage/abilities/condition/common/FirstCombatPhaseCondition.java new file mode 100644 index 0000000000..b5ed0f2cb2 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/FirstCombatPhaseCondition.java @@ -0,0 +1,20 @@ +package mage.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.constants.TurnPhase; +import mage.game.Game; + +public enum FirstCombatPhaseCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return game.getTurn().getPhase(TurnPhase.COMBAT).getCount() == 0; + } + + @Override + public String toString() { + return "it's the first combat phase of the turn"; + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/ExaltedAbility.java b/Mage/src/main/java/mage/abilities/keyword/ExaltedAbility.java index e2a6cf9aca..2777205369 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ExaltedAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ExaltedAbility.java @@ -1,25 +1,19 @@ - package mage.abilities.keyword; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.AttacksAloneControlledTriggeredAbility; import mage.abilities.effects.common.continuous.BoostTargetEffect; -import mage.constants.Duration; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.target.targetpointer.FixedTarget; /** * - * @author BetaSteward_at_googlemail.com + * @author awjackson */ -public class ExaltedAbility extends TriggeredAbilityImpl { +public class ExaltedAbility extends AttacksAloneControlledTriggeredAbility { public ExaltedAbility() { - super(Zone.BATTLEFIELD, new BoostTargetEffect(1, 1, Duration.EndOfTurn)); + super(new BoostTargetEffect(1, 1), true, false); } - public ExaltedAbility(final ExaltedAbility ability) { + private ExaltedAbility(final ExaltedAbility ability) { super(ability); } @@ -28,25 +22,8 @@ public class ExaltedAbility extends TriggeredAbilityImpl { return new ExaltedAbility(this); } - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DECLARED_ATTACKERS; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (game.isActivePlayer(this.controllerId)) { - if (game.getCombat().attacksAlone()) { - this.getEffects().get(0).setTargetPointer(new FixedTarget(game.getCombat().getAttackers().get(0))); - return true; - } - } - return false; - } - @Override public String getRule() { - return "exalted (Whenever a creature you control attacks alone, that creature gets +1/+1 until end of turn.)"; + return "Exalted (Whenever a creature you control attacks alone, that creature gets +1/+1 until end of turn.)"; } - } diff --git a/Mage/src/main/java/mage/game/combat/Combat.java b/Mage/src/main/java/mage/game/combat/Combat.java index 1de5186cbd..af840b4112 100644 --- a/Mage/src/main/java/mage/game/combat/Combat.java +++ b/Mage/src/main/java/mage/game/combat/Combat.java @@ -115,16 +115,16 @@ public class Combat implements Serializable, Copyable { return defenders; } - public List getAttackers() { - List attackers = new ArrayList<>(); + public Set getAttackers() { + Set attackers = new HashSet<>(); for (CombatGroup group : groups) { attackers.addAll(group.attackers); } return attackers; } - public List getBlockers() { - List blockers = new ArrayList<>(); + public Set getBlockers() { + Set blockers = new HashSet<>(); for (CombatGroup group : groups) { blockers.addAll(group.blockers); } @@ -1160,14 +1160,13 @@ public class Combat implements Serializable, Copyable { Set blockedSet = mustBeBlockedByAtLeastX.get(blockedAttackerId); Set toBlockSet = mustBeBlockedByAtLeastX.get(toBeBlockedCreatureId); if (toBlockSet == null) { - // This should never happen.
 + // This should never happen. return null; } else if (toBlockSet.containsAll(blockedSet)) { - // the creature already blocks alone a creature that has to be blocked by at least one
 - // and has more possible blockers, so this is ok
 + // the creature already blocks alone a creature that has to be blocked by at least one + // and has more possible blockers, so this is ok return null; } - } // TODO: Check if the attacker is already blocked by another creature // and despite there is need that this attacker blocks this attacker also