Combat.getAttackers and Combat.getBlockers now return a Set instead of a List, so that two-headed blockers aren't included twice

This commit is contained in:
Alex W. Jackson 2022-09-07 22:36:05 -04:00
parent efaccf8564
commit a6c5209a2a
20 changed files with 192 additions and 354 deletions

View file

@ -844,7 +844,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
}
private List<Permanent> getAttackers(Game game) {
List<UUID> attackersUUID = game.getCombat().getAttackers();
Set<UUID> attackersUUID = game.getCombat().getAttackers();
if (attackersUUID.isEmpty()) {
return null;
}

View file

@ -2815,7 +2815,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
log.debug(sb.toString());
}
private void playRemoval(List<UUID> creatures, Game game) {
private void playRemoval(Set<UUID> 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<UUID> creatures, Game game) {
private void playDamage(Set<UUID> creatures, Game game) {
for (UUID creatureId : creatures) {
Permanent creature = game.getPermanent(creatureId);
for (Card card : this.playableInstant) {

View file

@ -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;
}
}

View file

@ -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}

View file

@ -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<UUID> attackers = game.getCombat().getAttackers();
Set<UUID> 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<UUID> attackers;
private Phase phase;
public IllusionistsGambitRequirementEffect(List attackers, Phase phase) {
public IllusionistsGambitRequirementEffect(Set<UUID> 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<UUID> attackers;
private final Phase phase;
public IllusionistsGambitRestrictionEffect(List attackers, Phase phase) {
public IllusionistsGambitRestrictionEffect(Set<UUID> attackers, Phase phase) {
super(Duration.Custom, Outcome.Benefit);
this.attackers = attackers;
this.phase = phase;

View file

@ -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;
}
}

View file

@ -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<UUID> attackers = game.getCombat().getAttackers();
int damage = attackers.size();
int damage = game.getCombat().getAttackers().size();
Set<MageObjectReference> attackSet = (Set<MageObjectReference>) getValue("Lightmine Field");
if (!attackers.isEmpty()) {
if (damage > 0) {
for (Iterator<MageObjectReference> it = attackSet.iterator(); it.hasNext();) {
MageObjectReference attacker = it.next();
Permanent creature = attacker.getPermanent(game);

View file

@ -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;
}
}

View file

@ -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<UUID> attackerIds = combat.getAttackers();
List<UUID> blockerIds = combat.getBlockers();
Set<UUID> attackerIds = combat.getAttackers();
Set<UUID> blockerIds = combat.getBlockers();
Stream.concat(blockerIds.stream(), attackerIds.stream())
.map(id -> game.getPermanent(id))
.filter(e -> e != null)

View file

@ -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.";
}
}

View file

@ -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";
}
}

View file

@ -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}

View file

@ -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}

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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";
}
}

View file

@ -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 <i>(Whenever a creature you control attacks alone, that creature gets +1/+1 until end of turn.)</i>";
return "Exalted <i>(Whenever a creature you control attacks alone, that creature gets +1/+1 until end of turn.)</i>";
}
}

View file

@ -115,16 +115,16 @@ public class Combat implements Serializable, Copyable<Combat> {
return defenders;
}
public List<UUID> getAttackers() {
List<UUID> attackers = new ArrayList<>();
public Set<UUID> getAttackers() {
Set<UUID> attackers = new HashSet<>();
for (CombatGroup group : groups) {
attackers.addAll(group.attackers);
}
return attackers;
}
public List<UUID> getBlockers() {
List<UUID> blockers = new ArrayList<>();
public Set<UUID> getBlockers() {
Set<UUID> blockers = new HashSet<>();
for (CombatGroup group : groups) {
blockers.addAll(group.blockers);
}
@ -1160,14 +1160,13 @@ public class Combat implements Serializable, Copyable<Combat> {
Set<UUID> blockedSet = mustBeBlockedByAtLeastX.get(blockedAttackerId);
Set<UUID> 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