[LCI] Implement Fabrication Foundry; Unstable Glyphbridge (#11521)

* First pass at Fabrication Foundry

* misc cleanup

* First pass at Unstable Glyphbridge/Sandswirl Wanderglyph

* Set up backside correctly

* Fix Unstable Bridge cast condition

* Improve Fabrication Foundry to use Crew-like hint

* Fix equality check

Tested, though only manually.

Alongside the direct card implementations, I also:

-   Fix Oaken Siren's mana to not be able to pay for soft counterspells from artifact sources
-   Change PlayersAttackedThisTurnWatcher to follow the rules better and to have a separate section for planeswalkers' controllers who were attacked (needed for Sandswirl Wanderglyph)
-   Removed an unused constructor in ExileTargetCost (though I didn't end up using that cost in the end)
-   minor cleanup in CrewAbility
This commit is contained in:
ssk97 2023-12-10 18:30:20 -08:00 committed by GitHub
parent 3bc28d63c3
commit ec0166bf7f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 449 additions and 14 deletions

View file

@ -0,0 +1,179 @@
package mage.cards.f;
import mage.ConditionalMana;
import mage.MageObject;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.common.ActivateAsSorceryActivatedAbility;
import mage.abilities.condition.Condition;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect;
import mage.abilities.hint.HintUtils;
import mage.abilities.mana.ConditionalColoredManaAbility;
import mage.abilities.mana.builder.ConditionalManaBuilder;
import mage.cards.*;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.filter.common.FilterControlledArtifactPermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import mage.target.TargetPermanent;
import mage.target.common.TargetCardInYourGraveyard;
import java.awt.*;
import java.util.Objects;
import java.util.UUID;
/**
*
* @author notgreat
*/
public final class FabricationFoundry extends CardImpl {
public FabricationFoundry(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{W}");
// {T}: Add {W}. Spend this mana only to cast an artifact spell or activate an ability of an artifact source.
this.addAbility(new ConditionalColoredManaAbility(Mana.WhiteMana(1), new ArtifactManaBuilder()));
// {2}{W}, {T}, Exile one or more other artifacts you control with total mana value X: Return target artifact card with mana value X or less from your graveyard to the battlefield. Activate only as a sorcery.
Ability ability = new ActivateAsSorceryActivatedAbility(new ReturnFromGraveyardToBattlefieldTargetEffect(), new ManaCostsImpl<>("{2}{W}"));
ability.addCost(new TapSourceCost());
ability.addCost(new ExileTargetsTotalManaValueCost());
Target target = new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_ARTIFACT_FROM_YOUR_GRAVEYARD);
target.setTargetName("artifact card with mana value X or less from your graveyard");
ability.addTarget(target);
this.addAbility(ability);
}
private FabricationFoundry(final FabricationFoundry card) {
super(card);
}
@Override
public FabricationFoundry copy() {
return new FabricationFoundry(this);
}
}
//Mana based on Oaken Siren
class ArtifactManaBuilder extends ConditionalManaBuilder {
@Override
public ConditionalMana build(Object... options) {
return new ArtifactConditionalMana(this.mana);
}
@Override
public String getRule() {
return "Spend this mana only to cast an artifact spell or activate an ability of an artifact source";
}
}
class ArtifactConditionalMana extends ConditionalMana {
ArtifactConditionalMana(Mana mana) {
super(mana);
addCondition(ArtifactSpellOrActivatedAbilityCondition.instance);
}
}
enum ArtifactSpellOrActivatedAbilityCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
MageObject object = game.getObject(source);
return object != null && object.isArtifact(game) && !source.isActivated();
}
}
//Cost based on Kozilek, The Great Distortion and CrewAbility
class ExileTargetsTotalManaValueCost extends CostImpl {
private static final FilterPermanent filter = new FilterControlledArtifactPermanent("one or more other artifacts you control with total mana value X");
static {
filter.add(AnotherPredicate.instance);
}
public ExileTargetsTotalManaValueCost() {
this.text = "Exile one or more other artifacts you control with total mana value X";
}
public ExileTargetsTotalManaValueCost(ExileTargetsTotalManaValueCost cost) {
super(cost);
}
@Override
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
Card abilityTarget = game.getCard(ability.getFirstTarget());
if (abilityTarget == null) {
return paid;
}
Player player = game.getPlayer(ability.getControllerId());
if (player == null) {
return paid;
}
int minX = abilityTarget.getManaValue();
int sum = 0;
Target target = new TargetPermanent(1, Integer.MAX_VALUE, filter, true){
@Override
public String getMessage() {
// shows selected mana value
int selectedPower = this.targets.keySet().stream()
.map(game::getPermanent)
.filter(Objects::nonNull)
.mapToInt(Permanent::getManaValue)
.sum();
String extraInfo = "(selected mana value " + selectedPower + " of " + minX + ")";
if (selectedPower >= minX) {
extraInfo = HintUtils.prepareText(extraInfo, Color.GREEN);
}
return super.getMessage() + " " + extraInfo;
}
};
if (!target.choose(Outcome.Exile, controllerId, source.getSourceId(), source, game)){
return paid;
}
Cards cards = new CardsImpl();
cards.addAll(target.getTargets());
for (UUID targetId : target.getTargets()) {
Permanent permanent = game.getPermanent(targetId);
if (permanent != null) {
sum += permanent.getManaValue();
}
}
paid = (sum >= minX);
if (paid) {
player.moveCardsToExile(cards.getCards(game), source, game, false,null,null);
}
return paid;
}
@Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
int totalExileMV = 0;
boolean anyExileFound = false;
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, controllerId, source, game)){
totalExileMV += permanent.getManaValue();
anyExileFound = true;
}
int minTargetMV = Integer.MAX_VALUE;
for (Card card : game.getPlayer(controllerId).getGraveyard().getCards(StaticFilters.FILTER_CARD_ARTIFACT_FROM_YOUR_GRAVEYARD, game)){
minTargetMV = Integer.min(minTargetMV, card.getManaValue());
}
return anyExileFound && totalExileMV >= minTargetMV;
}
@Override
public ExileTargetsTotalManaValueCost copy() {
return new ExileTargetsTotalManaValueCost(this);
}
}

View file

@ -78,6 +78,6 @@ enum OakenSirenCondition implements Condition {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
MageObject object = game.getObject(source); MageObject object = game.getObject(source);
return object != null && object.isArtifact(game); return object != null && object.isArtifact(game) && !source.isActivated();
} }
} }

View file

@ -0,0 +1,123 @@
package mage.cards.s;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.common.SpellCastOpponentTriggeredAbility;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.abilities.effects.RestrictionEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterSpell;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.watchers.common.PlayersAttackedThisTurnWatcher;
import java.util.UUID;
/**
*
* @author notgreat
*/
public final class SandswirlWanderglyph extends CardImpl {
private static final FilterSpell filter = new FilterSpell("a spell during their turn");
static {
filter.add(TargetController.ACTIVE.getControllerPredicate());
}
public SandswirlWanderglyph(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "");
this.subtype.add(SubType.GOLEM);
this.power = new MageInt(5);
this.toughness = new MageInt(3);
this.nightCard = true;
this.color.setWhite(true);
// Flying
this.addAbility(FlyingAbility.getInstance());
// Whenever an opponent casts a spell during their turn, they can't attack you or planeswalkers you control this turn.
this.addAbility(new SpellCastOpponentTriggeredAbility(Zone.BATTLEFIELD,
new CantAttackSourcePlayerOrPlaneswalkerThisTurnEffect(), filter, false, SetTargetPointer.PLAYER));
// Each opponent who attacked you or a planeswalker you control this turn can't cast spells.
this.addAbility(new SimpleStaticAbility(new SandswirlWanderglyphCantCastEffect()), new PlayersAttackedThisTurnWatcher());
}
private SandswirlWanderglyph(final SandswirlWanderglyph card) {
super(card);
}
@Override
public SandswirlWanderglyph copy() {
return new SandswirlWanderglyph(this);
}
}
class SandswirlWanderglyphCantCastEffect extends ContinuousRuleModifyingEffectImpl {
public SandswirlWanderglyphCantCastEffect() {
super(Duration.WhileOnBattlefield, Outcome.Benefit);
staticText = "Each opponent who attacked you or a planeswalker you control this turn can't cast spells";
}
private SandswirlWanderglyphCantCastEffect(final SandswirlWanderglyphCantCastEffect effect) {
super(effect);
}
@Override
public SandswirlWanderglyphCantCastEffect copy() {
return new SandswirlWanderglyphCantCastEffect(this);
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.CAST_SPELL;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (game.isActivePlayer(event.getPlayerId()) && game.getOpponents(source.getControllerId()).contains(event.getPlayerId())) {
PlayersAttackedThisTurnWatcher watcher = game.getState().getWatcher(PlayersAttackedThisTurnWatcher.class);
return watcher != null && watcher.hasPlayerAttackedPlayerOrControlledPlaneswalker(event.getPlayerId(), source.getControllerId());
}
return false;
}
}
class CantAttackSourcePlayerOrPlaneswalkerThisTurnEffect extends RestrictionEffect {
CantAttackSourcePlayerOrPlaneswalkerThisTurnEffect() {
super(Duration.EndOfTurn);
staticText = "they can't attack you or planeswalkers you control this turn";
}
private CantAttackSourcePlayerOrPlaneswalkerThisTurnEffect(final CantAttackSourcePlayerOrPlaneswalkerThisTurnEffect effect) {
super(effect);
}
@Override
public CantAttackSourcePlayerOrPlaneswalkerThisTurnEffect copy() {
return new CantAttackSourcePlayerOrPlaneswalkerThisTurnEffect(this);
}
@Override
public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game, boolean canUseChooseDialogs) {
if (game.getPlayer(defenderId) != null){
return !(source.getControllerId().equals(defenderId));
}
Permanent defender = game.getPermanent(defenderId);
if (defender != null && defender.isPlaneswalker()){
return !(source.getControllerId().equals(defender.getControllerId()));
}
return true;
}
@Override
public boolean applies(Permanent permanent, Ability source, Game game) {
return permanent.getControllerId().equals(getTargetPointer().getFirst(game, source));
}
}

View file

@ -0,0 +1,105 @@
package mage.cards.u;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.condition.common.CastFromEverywhereSourceCondition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.keyword.CraftAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.constants.Outcome;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.PowerPredicate;
import mage.filter.predicate.permanent.ControllerIdPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
*
* @author notgreat
*/
public final class UnstableGlyphbridge extends CardImpl {
public UnstableGlyphbridge(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}{W}{W}");
this.secondSideCardClazz = mage.cards.s.SandswirlWanderglyph.class;
// When Unstable Glyphbridge enters the battlefield, if you cast it, for each player, choose a creature with power 2 or less that player controls. Then destroy all creatures except creatures chosen this way.
this.addAbility(new ConditionalInterveningIfTriggeredAbility(new EntersBattlefieldTriggeredAbility(
new UnstableGlyphbridgeEffect()), CastFromEverywhereSourceCondition.instance,
"When {this} enters the battlefield, if you cast it, " +
"for each player, choose a creature with power 2 or less that player controls. " +
"Then destroy all creatures except creatures chosen this way."
));
// Craft with artifact {3}{W}{W}
this.addAbility(new CraftAbility("{3}{W}{W}"));
}
private UnstableGlyphbridge(final UnstableGlyphbridge card) {
super(card);
}
@Override
public UnstableGlyphbridge copy() {
return new UnstableGlyphbridge(this);
}
}
class UnstableGlyphbridgeEffect extends OneShotEffect {
UnstableGlyphbridgeEffect() {
super(Outcome.Benefit);
staticText = "for each player, choose a creature with power 2 or less that player controls. " +
"Then destroy all creatures except creatures chosen this way.";
}
private UnstableGlyphbridgeEffect(final UnstableGlyphbridgeEffect effect) {
super(effect);
}
@Override
public UnstableGlyphbridgeEffect copy() {
return new UnstableGlyphbridgeEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
Cards cards = new CardsImpl();
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
Player player = game.getPlayer(playerId);
if (player == null) {
continue;
}
FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with power 2 or less");
filter.add(new PowerPredicate(ComparisonType.OR_LESS,2));
filter.add(new ControllerIdPredicate(playerId));
TargetCreaturePermanent target = new TargetCreaturePermanent(filter);
target.withNotTarget(true);
target.withChooseHint(player.getName() + " controls");
controller.choose(Outcome.PutCreatureInPlay, target, source, game);
cards.add(target.getFirstTarget());
}
for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURES, source.getControllerId(), source, game)) {
if (!cards.contains(permanent.getId())) {
permanent.destroy(source, game);
}
}
return true;
}
}

View file

@ -134,6 +134,7 @@ public final class TheLostCavernsOfIxalan extends ExpansionSet {
cards.add(new SetCardInfo("Envoy of Okinec Ahau", 11, Rarity.COMMON, mage.cards.e.EnvoyOfOkinecAhau.class)); cards.add(new SetCardInfo("Envoy of Okinec Ahau", 11, Rarity.COMMON, mage.cards.e.EnvoyOfOkinecAhau.class));
cards.add(new SetCardInfo("Etali's Favor", 149, Rarity.COMMON, mage.cards.e.EtalisFavor.class)); cards.add(new SetCardInfo("Etali's Favor", 149, Rarity.COMMON, mage.cards.e.EtalisFavor.class));
cards.add(new SetCardInfo("Explorer's Cache", 184, Rarity.UNCOMMON, mage.cards.e.ExplorersCache.class)); cards.add(new SetCardInfo("Explorer's Cache", 184, Rarity.UNCOMMON, mage.cards.e.ExplorersCache.class));
cards.add(new SetCardInfo("Fabrication Foundry", 12, Rarity.RARE, mage.cards.f.FabricationFoundry.class));
cards.add(new SetCardInfo("Family Reunion", 13, Rarity.COMMON, mage.cards.f.FamilyReunion.class)); cards.add(new SetCardInfo("Family Reunion", 13, Rarity.COMMON, mage.cards.f.FamilyReunion.class));
cards.add(new SetCardInfo("Fanatical Offering", 105, Rarity.COMMON, mage.cards.f.FanaticalOffering.class)); cards.add(new SetCardInfo("Fanatical Offering", 105, Rarity.COMMON, mage.cards.f.FanaticalOffering.class));
cards.add(new SetCardInfo("Forest", 291, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_UST_VARIOUS)); cards.add(new SetCardInfo("Forest", 291, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_UST_VARIOUS));
@ -273,6 +274,7 @@ public final class TheLostCavernsOfIxalan extends ExpansionSet {
cards.add(new SetCardInfo("Sage of Days", 73, Rarity.COMMON, mage.cards.s.SageOfDays.class)); cards.add(new SetCardInfo("Sage of Days", 73, Rarity.COMMON, mage.cards.s.SageOfDays.class));
cards.add(new SetCardInfo("Saheeli's Lattice", 164, Rarity.UNCOMMON, mage.cards.s.SaheelisLattice.class)); cards.add(new SetCardInfo("Saheeli's Lattice", 164, Rarity.UNCOMMON, mage.cards.s.SaheelisLattice.class));
cards.add(new SetCardInfo("Saheeli, the Sun's Brilliance", 239, Rarity.MYTHIC, mage.cards.s.SaheeliTheSunsBrilliance.class)); cards.add(new SetCardInfo("Saheeli, the Sun's Brilliance", 239, Rarity.MYTHIC, mage.cards.s.SaheeliTheSunsBrilliance.class));
cards.add(new SetCardInfo("Sandswirl Wanderglyph", 41, Rarity.RARE, mage.cards.s.SandswirlWanderglyph.class));
cards.add(new SetCardInfo("Sanguine Evangelist", 34, Rarity.RARE, mage.cards.s.SanguineEvangelist.class)); cards.add(new SetCardInfo("Sanguine Evangelist", 34, Rarity.RARE, mage.cards.s.SanguineEvangelist.class));
cards.add(new SetCardInfo("Scampering Surveyor", 260, Rarity.UNCOMMON, mage.cards.s.ScamperingSurveyor.class)); cards.add(new SetCardInfo("Scampering Surveyor", 260, Rarity.UNCOMMON, mage.cards.s.ScamperingSurveyor.class));
cards.add(new SetCardInfo("Screaming Phantom", 118, Rarity.COMMON, mage.cards.s.ScreamingPhantom.class)); cards.add(new SetCardInfo("Screaming Phantom", 118, Rarity.COMMON, mage.cards.s.ScreamingPhantom.class));
@ -347,6 +349,7 @@ public final class TheLostCavernsOfIxalan extends ExpansionSet {
cards.add(new SetCardInfo("Twists and Turns", 217, Rarity.UNCOMMON, mage.cards.t.TwistsAndTurns.class)); cards.add(new SetCardInfo("Twists and Turns", 217, Rarity.UNCOMMON, mage.cards.t.TwistsAndTurns.class));
cards.add(new SetCardInfo("Uchbenbak, the Great Mistake", 242, Rarity.UNCOMMON, mage.cards.u.UchbenbakTheGreatMistake.class)); cards.add(new SetCardInfo("Uchbenbak, the Great Mistake", 242, Rarity.UNCOMMON, mage.cards.u.UchbenbakTheGreatMistake.class));
cards.add(new SetCardInfo("Unlucky Drop", 82, Rarity.COMMON, mage.cards.u.UnluckyDrop.class)); cards.add(new SetCardInfo("Unlucky Drop", 82, Rarity.COMMON, mage.cards.u.UnluckyDrop.class));
cards.add(new SetCardInfo("Unstable Glyphbridge", 41, Rarity.RARE, mage.cards.u.UnstableGlyphbridge.class));
cards.add(new SetCardInfo("Vanguard of the Rose", 42, Rarity.UNCOMMON, mage.cards.v.VanguardOfTheRose.class)); cards.add(new SetCardInfo("Vanguard of the Rose", 42, Rarity.UNCOMMON, mage.cards.v.VanguardOfTheRose.class));
cards.add(new SetCardInfo("Visage of Dread", 129, Rarity.UNCOMMON, mage.cards.v.VisageOfDread.class)); cards.add(new SetCardInfo("Visage of Dread", 129, Rarity.UNCOMMON, mage.cards.v.VisageOfDread.class));
cards.add(new SetCardInfo("Vito's Inquisitor", 130, Rarity.COMMON, mage.cards.v.VitosInquisitor.class)); cards.add(new SetCardInfo("Vito's Inquisitor", 130, Rarity.COMMON, mage.cards.v.VitosInquisitor.class));

View file

@ -31,10 +31,6 @@ public class ExileTargetCost extends CostImpl {
this.text = "exile " + target.getDescription(); this.text = "exile " + target.getDescription();
} }
public ExileTargetCost(TargetControlledPermanent target, boolean noText) {
this.addTarget(target);
}
public ExileTargetCost(ExileTargetCost cost) { public ExileTargetCost(ExileTargetCost cost) {
super(cost); super(cost);
for (Permanent permanent : cost.permanents) { for (Permanent permanent : cost.permanents) {

View file

@ -152,8 +152,8 @@ class CrewCost extends CostImpl {
@Override @Override
public String getMessage() { public String getMessage() {
// shows selected power // shows selected power
int selectedPower = this.targets.entrySet().stream() int selectedPower = this.targets.keySet().stream()
.map(entry -> (game.getPermanent(entry.getKey()))) .map(game::getPermanent)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.mapToInt(p -> (getCrewPower(p, game))) .mapToInt(p -> (getCrewPower(p, game)))
.sum(); .sum();

View file

@ -3,6 +3,7 @@ package mage.watchers.common;
import mage.constants.WatcherScope; import mage.constants.WatcherScope;
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.players.PlayerList; import mage.players.PlayerList;
import mage.watchers.Watcher; import mage.watchers.Watcher;
@ -18,6 +19,7 @@ public class PlayersAttackedThisTurnWatcher extends Watcher {
// how many players or opponents each player attacked this turn // how many players or opponents each player attacked this turn
private final Map<UUID, PlayerList> playersAttackedThisTurn = new HashMap<>(); private final Map<UUID, PlayerList> playersAttackedThisTurn = new HashMap<>();
private final Map<UUID, PlayerList> opponentsAttackedThisTurn = new HashMap<>(); private final Map<UUID, PlayerList> opponentsAttackedThisTurn = new HashMap<>();
private final Map<UUID, PlayerList> planeswalkerControllerAttackedThisTurn = new HashMap<>();
public PlayersAttackedThisTurnWatcher() { public PlayersAttackedThisTurnWatcher() {
super(WatcherScope.GAME); super(WatcherScope.GAME);
@ -25,10 +27,10 @@ public class PlayersAttackedThisTurnWatcher extends Watcher {
@Override @Override
public void watch(GameEvent event, Game game) { public void watch(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.BEGINNING_PHASE_PRE) { /*
playersAttackedThisTurn.clear(); Faramir, Prince of Ithilien:
opponentsAttackedThisTurn.clear(); Attacking a planeswalker you control or battle you're protecting doesn't count as attacking you. (2023-06-16)
} */
if (event.getType() == GameEvent.EventType.ATTACKER_DECLARED) { if (event.getType() == GameEvent.EventType.ATTACKER_DECLARED) {
@ -37,8 +39,8 @@ public class PlayersAttackedThisTurnWatcher extends Watcher {
if (playersAttacked == null) { if (playersAttacked == null) {
playersAttacked = new PlayerList(); playersAttacked = new PlayerList();
} }
UUID playerDefender = game.getCombat().getDefendingPlayerId(event.getSourceId(), game); UUID playerDefender = event.getTargetId();
if (playerDefender != null if (playerDefender != null && game.getPlayer(playerDefender) != null
&& !playersAttacked.contains(playerDefender)) { && !playersAttacked.contains(playerDefender)) {
playersAttacked.add(playerDefender); playersAttacked.add(playerDefender);
} }
@ -49,13 +51,26 @@ public class PlayersAttackedThisTurnWatcher extends Watcher {
if (opponentsAttacked == null) { if (opponentsAttacked == null) {
opponentsAttacked = new PlayerList(); opponentsAttacked = new PlayerList();
} }
UUID opponentDefender = game.getCombat().getDefendingPlayerId(event.getSourceId(), game); UUID opponentDefender = event.getTargetId();
if (opponentDefender != null if (opponentDefender != null
&& game.getOpponents(event.getPlayerId()).contains(opponentDefender) && game.getOpponents(event.getPlayerId()).contains(opponentDefender)
&& !opponentsAttacked.contains(opponentDefender)) { && !opponentsAttacked.contains(opponentDefender)) {
opponentsAttacked.add(opponentDefender); opponentsAttacked.add(opponentDefender);
} }
opponentsAttackedThisTurn.putIfAbsent(event.getPlayerId(), opponentsAttacked); opponentsAttackedThisTurn.putIfAbsent(event.getPlayerId(), opponentsAttacked);
//Planeswalker controllers
PlayerList controllersAttacked = planeswalkerControllerAttackedThisTurn.get(event.getPlayerId());
if (controllersAttacked == null) {
controllersAttacked = new PlayerList();
}
Permanent permanent = game.getPermanent(event.getTargetId());
UUID controllingDefender = game.getCombat().getDefendingPlayerId(event.getSourceId(), game);
if (controllingDefender != null && permanent != null && permanent.isPlaneswalker(game)
&& !controllersAttacked.contains(controllingDefender)) {
controllersAttacked.add(controllingDefender);
}
planeswalkerControllerAttackedThisTurn.putIfAbsent(event.getPlayerId(), controllersAttacked);
} }
} }
@ -63,6 +78,12 @@ public class PlayersAttackedThisTurnWatcher extends Watcher {
PlayerList defendersList = playersAttackedThisTurn.getOrDefault(attacker, null); PlayerList defendersList = playersAttackedThisTurn.getOrDefault(attacker, null);
return defendersList != null && defendersList.contains(defender); return defendersList != null && defendersList.contains(defender);
} }
public boolean hasPlayerAttackedPlayerOrControlledPlaneswalker(UUID attacker, UUID defender){
PlayerList defendersList = playersAttackedThisTurn.getOrDefault(attacker, null);
PlayerList planeswalkerControllersList = planeswalkerControllerAttackedThisTurn.getOrDefault(attacker, null);
return (defendersList != null && defendersList.contains(defender)) ||
(planeswalkerControllersList != null && planeswalkerControllersList.contains(defender));
}
public int getAttackedPlayersCount(UUID playerID) { public int getAttackedPlayersCount(UUID playerID) {
PlayerList defendersList = playersAttackedThisTurn.getOrDefault(playerID, null); PlayerList defendersList = playersAttackedThisTurn.getOrDefault(playerID, null);
@ -79,4 +100,12 @@ public class PlayersAttackedThisTurnWatcher extends Watcher {
} }
return 0; return 0;
} }
@Override
public void reset() {
super.reset();
playersAttackedThisTurn.clear();
opponentsAttackedThisTurn.clear();
planeswalkerControllerAttackedThisTurn.clear();
}
} }