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) { private List<Permanent> getAttackers(Game game) {
List<UUID> attackersUUID = game.getCombat().getAttackers(); Set<UUID> attackersUUID = game.getCombat().getAttackers();
if (attackersUUID.isEmpty()) { if (attackersUUID.isEmpty()) {
return null; return null;
} }

View file

@ -2815,7 +2815,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
log.debug(sb.toString()); 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 (UUID creatureId : creatures) {
for (Card card : this.playableInstant) { for (Card card : this.playableInstant) {
if (card.getSpellAbility().canActivate(playerId, game).canActivate()) { 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) { for (UUID creatureId : creatures) {
Permanent creature = game.getPermanent(creatureId); Permanent creature = game.getPermanent(creatureId);
for (Card card : this.playableInstant) { for (Card card : this.playableInstant) {

View file

@ -2,25 +2,19 @@ package mage.cards.f;
import java.util.UUID; import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.AttacksAloneControlledTriggeredAbility;
import mage.abilities.effects.Effect; import mage.abilities.condition.common.FirstCombatPhaseCondition;
import mage.abilities.effects.OneShotEffect; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.common.UntapTargetEffect;
import mage.abilities.effects.common.AdditionalCombatPhaseEffect;
import mage.abilities.keyword.ExaltedAbility; import mage.abilities.keyword.ExaltedAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; 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 { 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 // 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. // 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) { private FinestHour(final FinestHour card) {
@ -43,78 +44,4 @@ public final class FinestHour extends CardImpl {
public FinestHour copy() { public FinestHour copy() {
return new FinestHour(this); 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.abilities.keyword.EquipAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.AttachmentType; import mage.constants.*;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; 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. // Whenever equipped creature attacks, double the number of +1/+1 counters on it.
this.addAbility(new AttacksAttachedTriggeredAbility( this.addAbility(new AttacksAttachedTriggeredAbility(
new FractalHarnessDoubleEffect(), AttachmentType.EQUIPMENT, false, true new FractalHarnessDoubleEffect(), AttachmentType.EQUIPMENT, false, SetTargetPointer.PERMANENT
)); ));
// Equip {2} // Equip {2}

View file

@ -15,7 +15,7 @@ import mage.game.permanent.Permanent;
import mage.game.turn.Phase; import mage.game.turn.Phase;
import mage.game.turn.TurnMod; import mage.game.turn.TurnMod;
import java.util.List; import java.util.Set;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
@ -62,7 +62,7 @@ class IllusionistsGambitRemoveFromCombatEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
List<UUID> attackers = game.getCombat().getAttackers(); Set<UUID> attackers = game.getCombat().getAttackers();
for (UUID attackerId : attackers) { for (UUID attackerId : attackers) {
Permanent creature = game.getPermanent(attackerId); Permanent creature = game.getPermanent(attackerId);
if (creature != null) { if (creature != null) {
@ -85,10 +85,10 @@ class IllusionistsGambitRemoveFromCombatEffect extends OneShotEffect {
class IllusionistsGambitRequirementEffect extends RequirementEffect { class IllusionistsGambitRequirementEffect extends RequirementEffect {
private List attackers; private Set<UUID> attackers;
private Phase phase; private Phase phase;
public IllusionistsGambitRequirementEffect(List attackers, Phase phase) { public IllusionistsGambitRequirementEffect(Set<UUID> attackers, Phase phase) {
super(Duration.Custom); super(Duration.Custom);
this.attackers = attackers; this.attackers = attackers;
this.phase = phase; this.phase = phase;
@ -135,10 +135,10 @@ class IllusionistsGambitRequirementEffect extends RequirementEffect {
class IllusionistsGambitRestrictionEffect extends RestrictionEffect { class IllusionistsGambitRestrictionEffect extends RestrictionEffect {
private final List attackers; private final Set<UUID> attackers;
private final Phase phase; private final Phase phase;
public IllusionistsGambitRestrictionEffect(List attackers, Phase phase) { public IllusionistsGambitRestrictionEffect(Set<UUID> attackers, Phase phase) {
super(Duration.Custom, Outcome.Benefit); super(Duration.Custom, Outcome.Benefit);
this.attackers = attackers; this.attackers = attackers;
this.phase = phase; this.phase = phase;

View file

@ -4,7 +4,7 @@ import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; import mage.abilities.common.AttacksWithCreaturesTriggeredAbility;
import mage.abilities.common.ChooseABackgroundAbility; import mage.abilities.common.ChooseABackgroundAbility;
import mage.abilities.condition.Condition; import mage.abilities.condition.common.FirstCombatPhaseCondition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.common.AdditionalCombatPhaseEffect; import mage.abilities.effects.common.AdditionalCombatPhaseEffect;
import mage.abilities.effects.common.UntapAllEffect; import mage.abilities.effects.common.UntapAllEffect;
@ -32,11 +32,11 @@ public final class KarlachFuryOfAvernus extends CardImpl {
this.power = new MageInt(5); this.power = new MageInt(5);
this.toughness = new MageInt(4); 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( Ability ability = new ConditionalInterveningIfTriggeredAbility(
new AttacksWithCreaturesTriggeredAbility( new AttacksWithCreaturesTriggeredAbility(
new UntapAllEffect(StaticFilters.FILTER_ATTACKING_CREATURES), 1 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 " + "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." "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); 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; package mage.cards.l;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import mage.MageObjectReference; import mage.MageObjectReference;
@ -99,10 +97,9 @@ class LightmineFieldEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
List<UUID> attackers = game.getCombat().getAttackers(); int damage = game.getCombat().getAttackers().size();
int damage = attackers.size();
Set<MageObjectReference> attackSet = (Set<MageObjectReference>) getValue("Lightmine Field"); Set<MageObjectReference> attackSet = (Set<MageObjectReference>) getValue("Lightmine Field");
if (!attackers.isEmpty()) { if (damage > 0) {
for (Iterator<MageObjectReference> it = attackSet.iterator(); it.hasNext();) { for (Iterator<MageObjectReference> it = attackSet.iterator(); it.hasNext();) {
MageObjectReference attacker = it.next(); MageObjectReference attacker = it.next();
Permanent creature = attacker.getPermanent(game); Permanent creature = attacker.getPermanent(game);

View file

@ -1,24 +1,19 @@
package mage.cards.m; package mage.cards.m;
import java.util.UUID; import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.AttacksAttachedTriggeredAbility; import mage.abilities.common.AttacksAttachedTriggeredAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.keyword.EquipAbility; import mage.abilities.keyword.EquipAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.*;
import mage.constants.SubType;
import mage.constants.Outcome;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.target.common.TargetControlledCreaturePermanent;
/** /**
* *
* @author jeffwadsworth * @author awjackson
*/ */
public final class MageSlayer extends CardImpl { 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}"); super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{R}{G}");
this.subtype.add(SubType.EQUIPMENT); this.subtype.add(SubType.EQUIPMENT);
// Whenever equipped creature attacks, it deals damage equal to its power to defending player. // 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(), false)); this.addAbility(new AttacksAttachedTriggeredAbility(
new MageSlayerEffect(), AttachmentType.EQUIPMENT, false, SetTargetPointer.PERMANENT
));
// Equip {3} // 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) { private MageSlayer(final MageSlayer card) {
@ -61,18 +58,19 @@ class MageSlayerEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
Permanent equipment = game.getPermanent(source.getSourceId()); Permanent attacker = getTargetPointer().getFirstTargetPermanentOrLKI(game, source);
if (equipment != null && equipment.getAttachedTo() != null) { if (attacker == null) {
int power = game.getPermanent(equipment.getAttachedTo()).getPower().getValue(); return false;
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;
} }
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; package mage.cards.m;
import java.util.List; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -108,8 +108,8 @@ class MandateOfPeaceEndCombatEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
Combat combat = game.getCombat(); Combat combat = game.getCombat();
List<UUID> attackerIds = combat.getAttackers(); Set<UUID> attackerIds = combat.getAttackers();
List<UUID> blockerIds = combat.getBlockers(); Set<UUID> blockerIds = combat.getBlockers();
Stream.concat(blockerIds.stream(), attackerIds.stream()) Stream.concat(blockerIds.stream(), attackerIds.stream())
.map(id -> game.getPermanent(id)) .map(id -> game.getPermanent(id))
.filter(e -> e != null) .filter(e -> e != null)

View file

@ -2,9 +2,9 @@ package mage.cards.r;
import java.util.UUID; 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.common.AttacksAttachedTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.LoseLifeTargetEffect; import mage.abilities.effects.common.LoseLifeTargetEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
@ -13,14 +13,10 @@ import mage.abilities.keyword.EquipAbility;
import mage.constants.*; import mage.constants.*;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; 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 { 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. // 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} // Equip {2}
this.addAbility(new EquipAbility(2)); this.addAbility(new EquipAbility(2));
@ -53,48 +54,3 @@ public final class ReapersTalisman extends CardImpl {
return new ReapersTalisman(this); 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; package mage.cards.s;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.AttacksAloneAttachedTriggeredAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect;
@ -10,19 +9,15 @@ import mage.abilities.keyword.EquipAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.Predicates; import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.CardIdPredicate; import mage.filter.predicate.mageobject.CardIdPredicate;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID; import java.util.UUID;
/** /**
* @author LevelX2 * @author awjackson
*/ */
public final class SigilOfValor extends CardImpl { public final class SigilOfValor extends CardImpl {
@ -31,10 +26,13 @@ public final class SigilOfValor extends CardImpl {
this.subtype.add(SubType.EQUIPMENT); this.subtype.add(SubType.EQUIPMENT);
// Whenever equipped creature attacks alone, it gets +1/+1 until end of turn for each other creature you control. // 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} // Equip {1}
this.addAbility(new EquipAbility(Outcome.AddAbility, new GenericManaCost(1))); this.addAbility(new EquipAbility(1));
} }
private SigilOfValor(final SigilOfValor card) { private SigilOfValor(final SigilOfValor card) {
@ -47,81 +45,32 @@ public final class SigilOfValor extends CardImpl {
} }
} }
class SigilOfValorTriggeredAbility extends TriggeredAbilityImpl { enum SigilOfValorCount implements DynamicValue {
instance;
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();
}
@Override @Override
public int calculate(Game game, Ability sourceAbility, Effect effect) { public int calculate(Game game, Ability sourceAbility, Effect effect) {
Permanent equipment = game.getPermanent(sourceAbility.getSourceId()); UUID attackerId = effect.getTargetPointer().getFirst(game, sourceAbility);
if (equipment != null && equipment.getAttachedTo() != null) { if (attackerId != null) {
FilterPermanent filterPermanent = new FilterControlledCreaturePermanent(); FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent();
filterPermanent.add(Predicates.not(new CardIdPredicate(equipment.getAttachedTo()))); filter.add(Predicates.not(new CardIdPredicate(attackerId)));
return game.getBattlefield().count(filterPermanent, sourceAbility.getControllerId(), sourceAbility, game); return game.getBattlefield().count(filter, sourceAbility.getControllerId(), sourceAbility, game);
} }
return 0; return 0;
} }
@Override @Override
public DynamicValue copy() { public DynamicValue copy() {
return new SigilOfValorCount(this); return instance;
} }
@Override @Override
public String toString() { public String toString() {
return "X"; return "1";
} }
@Override @Override
public String getMessage() { 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.abilities.keyword.TrampleAbility;
import mage.constants.AttachmentType; import mage.constants.AttachmentType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.SetTargetPointer;
import mage.constants.SubType; import mage.constants.SubType;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; 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. // Whenever equipped creature attacks, you may sacrifice a Forest. If you do, that creature gains trample until end of turn.
this.addAbility(new AttacksAttachedTriggeredAbility( this.addAbility(new AttacksAttachedTriggeredAbility(
new DoIfCostPaid(new GainAbilityTargetEffect(TrampleAbility.getInstance(), Duration.EndOfTurn, "that creature gains trample until end of turn"), new SacrificeTargetCost(filter)), 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} // Equip {3}

View file

@ -10,10 +10,7 @@ import mage.abilities.keyword.DoubleStrikeAbility;
import mage.abilities.keyword.EquipAbility; import mage.abilities.keyword.EquipAbility;
import mage.cards.AdventureCard; import mage.cards.AdventureCard;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.AttachmentType; import mage.constants.*;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.target.common.TargetControlledCreaturePermanent; 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. // Whenever equipped creature attacks, double its power until end of turn.
this.addAbility(new AttacksAttachedTriggeredAbility( this.addAbility(new AttacksAttachedTriggeredAbility(
new TwoHandedAxeEffect(), AttachmentType.EQUIPMENT, false, true new TwoHandedAxeEffect(), AttachmentType.EQUIPMENT, false, SetTargetPointer.PERMANENT
)); ));
// Equip {1}{R} // 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.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil; import mage.util.CardUtil;
import mage.util.CardUtil;
/** /**
* @author TheElk801 * @author TheElk801
@ -39,7 +38,7 @@ public class AttacksAloneControlledTriggeredAbility extends TriggeredAbilityImpl
setTriggerPhrase("Whenever " + CardUtil.addArticle(filter.getMessage()) + " attacks alone, "); setTriggerPhrase("Whenever " + CardUtil.addArticle(filter.getMessage()) + " attacks alone, ");
} }
private AttacksAloneControlledTriggeredAbility(final AttacksAloneControlledTriggeredAbility ability) { protected AttacksAloneControlledTriggeredAbility(final AttacksAloneControlledTriggeredAbility ability) {
super(ability); super(ability);
this.filter = ability.filter; this.filter = ability.filter;
this.setTargetPointer = ability.setTargetPointer; this.setTargetPointer = ability.setTargetPointer;
@ -52,7 +51,7 @@ public class AttacksAloneControlledTriggeredAbility extends TriggeredAbilityImpl
@Override @Override
public boolean checkEventType(GameEvent event, Game game) { public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DECLARED_ATTACKERS; return event.getType() == GameEvent.EventType.ATTACKER_DECLARED;
} }
@Override @Override
@ -60,7 +59,7 @@ public class AttacksAloneControlledTriggeredAbility extends TriggeredAbilityImpl
if (!game.getCombat().attacksAlone()) { if (!game.getCombat().attacksAlone()) {
return false; 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)) { if (permanent == null || !filter.match(permanent, getControllerId(), this, game)) {
return false; return false;
} }

View file

@ -1,8 +1,5 @@
package mage.abilities.common; package mage.abilities.common;
import java.util.Objects;
import java.util.UUID;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.constants.Zone; import mage.constants.Zone;
@ -21,7 +18,7 @@ public class AttacksAloneSourceTriggeredAbility extends TriggeredAbilityImpl {
setTriggerPhrase("Whenever {this} attacks alone, "); setTriggerPhrase("Whenever {this} attacks alone, ");
} }
public AttacksAloneSourceTriggeredAbility(final AttacksAloneSourceTriggeredAbility ability) { protected AttacksAloneSourceTriggeredAbility(final AttacksAloneSourceTriggeredAbility ability) {
super(ability); super(ability);
} }
@ -32,25 +29,15 @@ public class AttacksAloneSourceTriggeredAbility extends TriggeredAbilityImpl {
@Override @Override
public boolean checkEventType(GameEvent event, Game game) { public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DECLARED_ATTACKERS; return event.getType() == GameEvent.EventType.ATTACKER_DECLARED;
} }
@Override @Override
public boolean checkTrigger(GameEvent event, Game game) { public boolean checkTrigger(GameEvent event, Game game) {
if(game.isActivePlayer(this.controllerId) ) { if (!getSourceId().equals(event.getSourceId()) || !game.getCombat().attacksAlone()) {
UUID creatureId = this.getSourceId(); return false;
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;
}
}
} }
return false; getEffects().setTargetPointer(new FixedTarget(game.getCombat().getDefendingPlayerId(getSourceId(), game)));
return true;
} }
} }

View file

@ -1,16 +1,16 @@
package mage.abilities.common; package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.constants.AttachmentType; import mage.constants.AttachmentType;
import mage.constants.SetTargetPointer;
import mage.constants.Zone; import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import java.util.Locale; import java.util.UUID;
/** /**
* "When enchanted/equipped creature attacks " triggered ability * "When enchanted/equipped creature attacks " triggered ability
@ -20,7 +20,7 @@ import java.util.Locale;
public class AttacksAttachedTriggeredAbility extends TriggeredAbilityImpl { public class AttacksAttachedTriggeredAbility extends TriggeredAbilityImpl {
private final AttachmentType attachmentType; private final AttachmentType attachmentType;
private final boolean setTargetPointer; private final SetTargetPointer setTargetPointer;
public AttacksAttachedTriggeredAbility(Effect effect) { public AttacksAttachedTriggeredAbility(Effect effect) {
this(effect, false); this(effect, false);
@ -31,20 +31,20 @@ public class AttacksAttachedTriggeredAbility extends TriggeredAbilityImpl {
} }
public AttacksAttachedTriggeredAbility(Effect effect, AttachmentType attachmentType, boolean optional) { 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); super(Zone.BATTLEFIELD, effect, optional);
this.attachmentType = attachmentType; this.attachmentType = attachmentType;
this.setTargetPointer = setTargetPointer; this.setTargetPointer = setTargetPointer;
setTriggerPhrase("Whenever " + attachmentType.verb().toLowerCase(Locale.ENGLISH) + " creature attacks, "); setTriggerPhrase("Whenever " + attachmentType.verb().toLowerCase() + " creature attacks, ");
} }
public AttacksAttachedTriggeredAbility(final AttacksAttachedTriggeredAbility abiltity) { protected AttacksAttachedTriggeredAbility(final AttacksAttachedTriggeredAbility ability) {
super(abiltity); super(ability);
this.attachmentType = abiltity.attachmentType; this.attachmentType = ability.attachmentType;
this.setTargetPointer = abiltity.setTargetPointer; this.setTargetPointer = ability.setTargetPointer;
} }
@Override @Override
@ -59,20 +59,19 @@ public class AttacksAttachedTriggeredAbility extends TriggeredAbilityImpl {
@Override @Override
public boolean checkTrigger(GameEvent event, Game game) { public boolean checkTrigger(GameEvent event, Game game) {
Permanent equipment = game.getPermanent(this.sourceId); Permanent attachment = getSourcePermanentOrLKI(game);
if (equipment != null && equipment.getAttachedTo() != null UUID attackerId = event.getSourceId();
&& event.getSourceId().equals(equipment.getAttachedTo())) { if (attachment == null || !attackerId.equals(attachment.getAttachedTo())) {
getEffects().setValue("sourceId", event.getSourceId()); return false;
// 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;
} }
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; package mage.abilities.keyword;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.AttacksAloneControlledTriggeredAbility;
import mage.abilities.effects.common.continuous.BoostTargetEffect; 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() { 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); super(ability);
} }
@ -28,25 +22,8 @@ public class ExaltedAbility extends TriggeredAbilityImpl {
return new ExaltedAbility(this); 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 @Override
public String getRule() { 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; return defenders;
} }
public List<UUID> getAttackers() { public Set<UUID> getAttackers() {
List<UUID> attackers = new ArrayList<>(); Set<UUID> attackers = new HashSet<>();
for (CombatGroup group : groups) { for (CombatGroup group : groups) {
attackers.addAll(group.attackers); attackers.addAll(group.attackers);
} }
return attackers; return attackers;
} }
public List<UUID> getBlockers() { public Set<UUID> getBlockers() {
List<UUID> blockers = new ArrayList<>(); Set<UUID> blockers = new HashSet<>();
for (CombatGroup group : groups) { for (CombatGroup group : groups) {
blockers.addAll(group.blockers); blockers.addAll(group.blockers);
} }
@ -1160,14 +1160,13 @@ public class Combat implements Serializable, Copyable<Combat> {
Set<UUID> blockedSet = mustBeBlockedByAtLeastX.get(blockedAttackerId); Set<UUID> blockedSet = mustBeBlockedByAtLeastX.get(blockedAttackerId);
Set<UUID> toBlockSet = mustBeBlockedByAtLeastX.get(toBeBlockedCreatureId); Set<UUID> toBlockSet = mustBeBlockedByAtLeastX.get(toBeBlockedCreatureId);
if (toBlockSet == null) { if (toBlockSet == null) {
// This should never happen. // This should never happen.
return null; return null;
} else if (toBlockSet.containsAll(blockedSet)) { } else if (toBlockSet.containsAll(blockedSet)) {
// the creature already blocks alone a creature that has to be blocked by at least one // 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 // and has more possible blockers, so this is ok
return null; return null;
} }
} }
// TODO: Check if the attacker is already blocked by another creature // TODO: Check if the attacker is already blocked by another creature
// and despite there is need that this attacker blocks this attacker also // and despite there is need that this attacker blocks this attacker also