From d8a199047d29cbd6a4518805d55161e9c5d32786 Mon Sep 17 00:00:00 2001 From: Lukas Spillner Date: Sat, 3 Feb 2024 22:46:03 +0100 Subject: [PATCH] implement [LTC] Call for Aid (#11727) * [LTC] Implementing "Call for Aid" (#10259) * [LTC] Call for Aid: Correcting permanent selection filter and "can't attack target this turn" effect * [LTC] Call for Aid: Making "You can't attack target opponent" a global effect. * address PR comments * fix duration --------- Co-authored-by: xenohedron --- Mage.Sets/src/mage/cards/c/CallForAid.java | 165 ++++++++++++++++++ .../sets/TalesOfMiddleEarthCommander.java | 1 + 2 files changed, 166 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/c/CallForAid.java diff --git a/Mage.Sets/src/mage/cards/c/CallForAid.java b/Mage.Sets/src/mage/cards/c/CallForAid.java new file mode 100644 index 00000000000..5f5249c4c10 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CallForAid.java @@ -0,0 +1,165 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.RestrictionEffect; +import mage.abilities.effects.common.UntapAllEffect; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.effects.common.continuous.GainControlAllEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.filter.predicate.permanent.PermanentReferenceInCollectionPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetOpponent; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +/** + * @author spillnerdev, xenohedron + */ +public final class CallForAid extends CardImpl { + + public CallForAid(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{R}"); + + // Gain control of all creatures target opponent controls until end of turn. Untap those creatures. They gain haste until end of turn. You can't attack that player this turn. You can't sacrifice those creatures this turn. + getSpellAbility().addTarget(new TargetOpponent()); + getSpellAbility().addEffect(new CallForAidEffect()); + } + + private CallForAid(final CallForAid card) { + super(card); + } + + @Override + public CallForAid copy() { + return new CallForAid(this); + } +} + +class CallForAidEffect extends OneShotEffect { + + CallForAidEffect() { + super(Outcome.Benefit); + this.staticText = "Gain control of all creatures target opponent controls until end of turn. " + + "Untap those creatures. They gain haste until end of turn. " + + "You can't attack that player this turn. You can't sacrifice those creatures this turn."; + } + + private CallForAidEffect(CallForAidEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (opponent == null) { + return false; + } + + FilterPermanent filter = new FilterCreaturePermanent(); + filter.add(new ControllerIdPredicate(opponent.getId())); + List opponentCreatures = game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game); + filter = new FilterPermanent(); + filter.add(new PermanentReferenceInCollectionPredicate(opponentCreatures, game)); + + //"Gain control of all creatures target opponent controls until end of turn" + new GainControlAllEffect(Duration.EndOfTurn, filter).apply(game, source); + game.getState().processAction(game); + + //"Untap those creatures". + new UntapAllEffect(filter).apply(game, source); + + //"They gain haste until end of turn" + game.addEffect(new GainAbilityAllEffect(HasteAbility.getInstance(), Duration.EndOfTurn, filter), source); + + //"You can't attack that player this turn" + game.addEffect(new CallForAidCantAttackThatPlayerEffect().setTargetPointer(this.getTargetPointer().copy()), source); + + //"You can't sacrifice those creatures this turn" + game.addEffect(new CallForAidYouCantSacrificeEffect(source.getControllerId(), filter), source); + + return true; + } + + @Override + public CallForAidEffect copy() { + return new CallForAidEffect(this); + } +} + +class CallForAidCantAttackThatPlayerEffect extends RestrictionEffect { + + CallForAidCantAttackThatPlayerEffect() { + super(Duration.EndOfTurn, Outcome.Detriment); + } + + private CallForAidCantAttackThatPlayerEffect(final CallForAidCantAttackThatPlayerEffect effect) { + super(effect); + } + + @Override + public CallForAidCantAttackThatPlayerEffect copy() { + return new CallForAidCantAttackThatPlayerEffect(this); + } + + @Override + public boolean applies(Permanent permanent, Ability source, Game game) { + // Effect applies to all permanents (most likely creatures) controlled by the caster of "Call for Aid" + return Objects.equals(source.getControllerId(), permanent.getControllerId()); + } + + @Override + public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game, boolean canUseChooseDialogs) { + if (defenderId == null || attacker == null) { + return true; + } + // Should be only a single target in case of "Call for Aid" + return !this.getTargetPointer().getTargets(game,source).contains(defenderId); + } + +} + +class CallForAidYouCantSacrificeEffect extends ContinuousEffectImpl { + + private final UUID playerId; // controller who is prevented from sacrificing + private final FilterPermanent filter; // filter containing permanents that can't be sacrificed + + CallForAidYouCantSacrificeEffect(UUID playerId, FilterPermanent filter) { + super(Duration.EndOfTurn, Layer.RulesEffects, SubLayer.NA, Outcome.Benefit); + this.playerId = playerId; + this.filter = filter; + } + + private CallForAidYouCantSacrificeEffect(final CallForAidYouCantSacrificeEffect effect) { + super(effect); + this.playerId = effect.playerId; + this.filter = effect.filter; + } + + @Override + public CallForAidYouCantSacrificeEffect copy() { + return new CallForAidYouCantSacrificeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) { + if (permanent.getControllerId().equals(playerId)) { + permanent.setCanBeSacrificed(false); + } + } + return true; + } + +} diff --git a/Mage.Sets/src/mage/sets/TalesOfMiddleEarthCommander.java b/Mage.Sets/src/mage/sets/TalesOfMiddleEarthCommander.java index f1bacd4604f..dada6568c0a 100644 --- a/Mage.Sets/src/mage/sets/TalesOfMiddleEarthCommander.java +++ b/Mage.Sets/src/mage/sets/TalesOfMiddleEarthCommander.java @@ -50,6 +50,7 @@ public final class TalesOfMiddleEarthCommander extends ExpansionSet { cards.add(new SetCardInfo("Brushland", 297, Rarity.RARE, mage.cards.b.Brushland.class)); cards.add(new SetCardInfo("Cabal Coffers", 360, Rarity.MYTHIC, mage.cards.c.CabalCoffers.class)); cards.add(new SetCardInfo("Call Forth the Tempest", 509, Rarity.RARE, mage.cards.c.CallForthTheTempest.class)); + cards.add(new SetCardInfo("Call for Aid", 30, Rarity.RARE, mage.cards.c.CallForAid.class)); cards.add(new SetCardInfo("Call for Unity", 163, Rarity.RARE, mage.cards.c.CallForUnity.class)); cards.add(new SetCardInfo("Canopy Vista", 298, Rarity.RARE, mage.cards.c.CanopyVista.class)); cards.add(new SetCardInfo("Castle Ardenvale", 361, Rarity.MYTHIC, mage.cards.c.CastleArdenvale.class));