mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 02:30:08 -08:00
Merge 5a63b1df22 into 2a3b4aff7a
This commit is contained in:
commit
c744d71ce9
17 changed files with 423 additions and 171 deletions
|
|
@ -2,8 +2,10 @@ package mage.cards.b;
|
|||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.common.WaterbendCost;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.common.InfoEffect;
|
||||
import mage.abilities.effects.keyword.ScryEffect;
|
||||
import mage.abilities.keyword.FlyingAbility;
|
||||
import mage.abilities.keyword.WardAbility;
|
||||
|
|
@ -11,6 +13,7 @@ import mage.cards.CardImpl;
|
|||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
|
|
@ -28,6 +31,9 @@ public final class BenevolentRiverSpirit extends CardImpl {
|
|||
|
||||
// As an additional cost to cast this spell, waterbend {5}.
|
||||
this.getSpellAbility().addCost(new WaterbendCost(5));
|
||||
this.addAbility(new SimpleStaticAbility(
|
||||
Zone.ALL, new InfoEffect("as an additional cost to cast this spell, waterbend {5}")
|
||||
).setRuleAtTheTop(true));
|
||||
|
||||
// Flying
|
||||
this.addAbility(FlyingAbility.getInstance());
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
package mage.cards.c;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.costs.common.WaterbendCost;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.common.WaterbendXCost;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.InfoEffect;
|
||||
import mage.abilities.effects.common.TapTargetEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.common.FilterOpponentsCreaturePermanent;
|
||||
|
|
@ -30,7 +33,10 @@ public final class CrashingWave extends CardImpl {
|
|||
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{U}{U}");
|
||||
|
||||
// As an additional cost to cast this spell, waterbend {X}.
|
||||
this.getSpellAbility().addCost(new WaterbendCost("{X}"));
|
||||
this.getSpellAbility().addCost(new WaterbendXCost());
|
||||
this.addAbility(new SimpleStaticAbility(
|
||||
Zone.ALL, new InfoEffect("as an additional cost to cast this spell, waterbend {X}")
|
||||
).setRuleAtTheTop(true));
|
||||
|
||||
// Tap up to X target creatures, then distribute three stun counters among tapped creatures your opponents control.
|
||||
this.getSpellAbility().addEffect(new TapTargetEffect("tap up to X target creatures"));
|
||||
|
|
@ -79,7 +85,7 @@ class CrashingWaveEffect extends OneShotEffect {
|
|||
}
|
||||
TargetPermanentAmount target = new TargetPermanentAmount(3, 1, filter);
|
||||
target.withNotTarget(true);
|
||||
player.chooseTarget(outcome, target, source, game);
|
||||
target.chooseTarget(outcome, player.getId(), source, game);
|
||||
for (UUID targetId : target.getTargets()) {
|
||||
Optional.ofNullable(targetId)
|
||||
.map(game::getPermanent)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package mage.cards.f;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
|
||||
import mage.abilities.costs.common.WaterbendCost;
|
||||
import mage.abilities.costs.common.WaterbendXCost;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.CreateTokenCopyTargetEffect;
|
||||
import mage.abilities.effects.common.InfoEffect;
|
||||
import mage.abilities.effects.common.SacrificeTargetEffect;
|
||||
import mage.cards.*;
|
||||
import mage.constants.CardType;
|
||||
|
|
@ -33,7 +35,10 @@ public final class FoggySwampVisions extends CardImpl {
|
|||
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}{B}");
|
||||
|
||||
// As an additional cost to cast this spell, waterbend {X}.
|
||||
this.getSpellAbility().addCost(new WaterbendCost("{X}"));
|
||||
this.getSpellAbility().addCost(new WaterbendXCost());
|
||||
this.addAbility(new SimpleStaticAbility(
|
||||
Zone.ALL, new InfoEffect("as an additional cost to cast this spell, waterbend {X}")
|
||||
).setRuleAtTheTop(true));
|
||||
|
||||
// Exile X target creature cards from graveyards. For each creature card exiled this way, create a token that's a copy of it. At the beginning of your next end step, sacrifice those tokens.
|
||||
this.getSpellAbility().addEffect(new FoggySwampVisionsEffect());
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ package mage.cards.h;
|
|||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.Costs;
|
||||
import mage.abilities.costs.CostsImpl;
|
||||
import mage.abilities.costs.common.WaterbendCost;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.AsThoughEffectImpl;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.MillCardsTargetEffect;
|
||||
|
|
@ -20,6 +20,7 @@ import mage.game.Game;
|
|||
import mage.players.Player;
|
||||
import mage.target.TargetCard;
|
||||
import mage.target.common.TargetCardInGraveyard;
|
||||
import mage.target.common.TargetOpponent;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
|
|
@ -42,6 +43,7 @@ public final class HamaTheBloodbender extends CardImpl {
|
|||
// When Hama enters, target opponent mills three cards. Exile up to one noncreature, nonland card from that player's graveyard. For as long as you control Hama, you may cast the exiled card during your turn by waterbending {X} rather than paying its mana cost, where X is its mana value.
|
||||
Ability ability = new EntersBattlefieldTriggeredAbility(new MillCardsTargetEffect(3));
|
||||
ability.addEffect(new HamaTheBloodbenderExileEffect());
|
||||
ability.addTarget(new TargetOpponent());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
@ -143,10 +145,9 @@ class HamaTheBloodbenderCastEffect extends AsThoughEffectImpl {
|
|||
if (player == null) {
|
||||
return false;
|
||||
}
|
||||
Costs<Cost> newCosts = new CostsImpl<>();
|
||||
newCosts.add(new WaterbendCost(card.getManaValue()));
|
||||
newCosts.addAll(card.getSpellAbility().getCosts());
|
||||
player.setCastSourceIdWithAlternateMana(card.getId(), null, newCosts);
|
||||
ManaCosts<ManaCost> manaCosts = new ManaCostsImpl<>("{0}");
|
||||
manaCosts.add(new WaterbendCost(card.getManaValue()));
|
||||
player.setCastSourceIdWithAlternateMana(card.getId(), manaCosts, card.getSpellAbility().getCosts());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ import mage.abilities.Ability;
|
|||
import mage.abilities.common.ActivateIfConditionActivatedAbility;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.condition.common.MyTurnCondition;
|
||||
import mage.abilities.costs.common.WaterbendCost;
|
||||
import mage.abilities.costs.mana.VariableManaCost;
|
||||
import mage.abilities.costs.common.WaterbendXCost;
|
||||
import mage.abilities.dynamicvalue.common.GetXValue;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.effects.common.InfoEffect;
|
||||
|
|
@ -20,7 +19,6 @@ import mage.constants.SubType;
|
|||
import mage.constants.SuperType;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.permanent.token.AllyToken;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
|
|
@ -49,9 +47,8 @@ public final class KataraWaterTribesHope extends CardImpl {
|
|||
Ability ability = new ActivateIfConditionActivatedAbility(new SetBasePowerToughnessAllEffect(
|
||||
GetXValue.instance, GetXValue.instance, Duration.EndOfTurn,
|
||||
StaticFilters.FILTER_CONTROLLED_CREATURES
|
||||
), new WaterbendCost("{X}"), MyTurnCondition.instance);
|
||||
), new WaterbendXCost(1), MyTurnCondition.instance);
|
||||
ability.addEffect(new InfoEffect("X can't be 0"));
|
||||
CardUtil.castStream(ability.getCosts(), VariableManaCost.class).forEach(cost -> cost.setMinX(1));
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public final class TheLegendOfKuruk extends TransformingDoubleFacedCard {
|
|||
super(ownerId, setInfo,
|
||||
new SuperType[]{}, new CardType[]{CardType.ENCHANTMENT}, new SubType[]{SubType.SAGA}, "{2}{U}{U}",
|
||||
"Avatar Kuruk",
|
||||
new SuperType[]{SuperType.LEGENDARY}, new CardType[]{CardType.CREATURE}, new SubType[]{SubType.AVATAR}, "U"
|
||||
new SuperType[]{SuperType.LEGENDARY}, new CardType[]{CardType.CREATURE}, new SubType[]{SubType.AVATAR}, ""
|
||||
);
|
||||
|
||||
// The Legend of Kuruk
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
package mage.cards.w;
|
||||
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.common.WaterbendCost;
|
||||
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||
import mage.abilities.effects.common.InfoEffect;
|
||||
import mage.abilities.effects.common.ReturnToHandTargetEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
|
@ -23,6 +26,9 @@ public final class WaterWhip extends CardImpl {
|
|||
|
||||
// As an additional cost to cast this spell, waterbend {5}.
|
||||
this.getSpellAbility().addCost(new WaterbendCost(5));
|
||||
this.addAbility(new SimpleStaticAbility(
|
||||
Zone.ALL, new InfoEffect("as an additional cost to cast this spell, waterbend {5}")
|
||||
).setRuleAtTheTop(true));
|
||||
|
||||
// Return up to two target creatures to their owners' hands. Draw two cards.
|
||||
this.getSpellAbility().addEffect(new ReturnToHandTargetEffect());
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
package mage.cards.w;
|
||||
|
||||
import mage.abilities.costs.common.WaterbendCost;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.common.WaterbendXCost;
|
||||
import mage.abilities.effects.common.ExileReturnBattlefieldNextEndStepTargetEffect;
|
||||
import mage.abilities.effects.common.InfoEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.target.common.TargetControlledCreaturePermanent;
|
||||
import mage.target.targetadjustment.XTargetsCountAdjuster;
|
||||
|
||||
|
|
@ -22,7 +25,10 @@ public final class WaterbendersRestoration extends CardImpl {
|
|||
this.subtype.add(SubType.LESSON);
|
||||
|
||||
// As an additional cost to cast this spell, waterbend {X}.
|
||||
this.getSpellAbility().addCost(new WaterbendCost("{X}"));
|
||||
this.getSpellAbility().addCost(new WaterbendXCost());
|
||||
this.addAbility(new SimpleStaticAbility(
|
||||
Zone.ALL, new InfoEffect("as an additional cost to cast this spell, waterbend {X}")
|
||||
).setRuleAtTheTop(true));
|
||||
|
||||
// Exile X target creatures you control. Return those cards to the battlefield under their owner's control at the beginning of the next end step.
|
||||
this.getSpellAbility().addEffect(new ExileReturnBattlefieldNextEndStepTargetEffect()
|
||||
|
|
|
|||
|
|
@ -4,15 +4,11 @@ import mage.cards.ExpansionSet;
|
|||
import mage.constants.Rarity;
|
||||
import mage.constants.SetType;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class AvatarTheLastAirbender extends ExpansionSet {
|
||||
|
||||
private static final List<String> unfinished = Arrays.asList("Aang's Iceberg", "Aang, Swift Savior", "Avatar Aang", "Benevolent River Spirit", "Crashing Wave", "Flexible Waterbender", "Foggy Swamp Vinebender", "Foggy Swamp Visions", "Geyser Leaper", "Giant Koi", "Hama, the Bloodbender", "Invasion Submersible", "Katara, Bending Prodigy", "Katara, Water Tribe's Hope", "North Pole Patrol", "Ruinous Waterbending", "Secret of Bloodbending", "Spirit Water Revival", "The Legend of Kuruk", "The Unagi of Kyoshi Island", "Waterbender Ascension", "Waterbending Lesson", "Water Tribe Rallier", "Watery Grasp", "Yue, the Moon Spirit");
|
||||
private static final AvatarTheLastAirbender instance = new AvatarTheLastAirbender();
|
||||
|
||||
public static AvatarTheLastAirbender getInstance() {
|
||||
|
|
@ -420,7 +416,5 @@ public final class AvatarTheLastAirbender extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Zuko, Conflicted", 253, Rarity.RARE, mage.cards.z.ZukoConflicted.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Zuko, Conflicted", 302, Rarity.RARE, mage.cards.z.ZukoConflicted.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Zuko, Exiled Prince", 163, Rarity.UNCOMMON, mage.cards.z.ZukoExiledPrince.class));
|
||||
|
||||
cards.removeIf(setCardInfo -> unfinished.contains(setCardInfo.getName()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,16 +4,11 @@ import mage.cards.ExpansionSet;
|
|||
import mage.constants.Rarity;
|
||||
import mage.constants.SetType;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class AvatarTheLastAirbenderEternal extends ExpansionSet {
|
||||
|
||||
private static final List<String> unfinished = Arrays.asList("Katara, Seeking Revenge", "Ruthless Waterbender", "Waterbender's Restoration", "Water Whip");
|
||||
|
||||
private static final AvatarTheLastAirbenderEternal instance = new AvatarTheLastAirbenderEternal();
|
||||
|
||||
public static AvatarTheLastAirbenderEternal getInstance() {
|
||||
|
|
@ -338,7 +333,5 @@ public final class AvatarTheLastAirbenderEternal extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Zuko, Firebending Master", 127, Rarity.MYTHIC, mage.cards.z.ZukoFirebendingMaster.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Zuko, Firebending Master", 200, Rarity.MYTHIC, mage.cards.z.ZukoFirebendingMaster.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Zuko, Seeking Honor", 150, Rarity.UNCOMMON, mage.cards.z.ZukoSeekingHonor.class));
|
||||
|
||||
cards.removeIf(setCardInfo -> unfinished.contains(setCardInfo.getName()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
package org.mage.test.cards.mana;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.player.TestPlayer;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class WaterbendTest extends CardTestPlayerBase {
|
||||
|
||||
private static final String waterbender = "Flexible Waterbender";
|
||||
|
||||
@Test
|
||||
public void testJustMana() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 3);
|
||||
addCard(Zone.BATTLEFIELD, playerA, waterbender);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "waterbend");
|
||||
setChoice(playerA, TestPlayer.CHOICE_SKIP);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertPowerToughness(playerA, waterbender, 5, 2);
|
||||
assertTapped(waterbender, false);
|
||||
assertTapped("Island", true);
|
||||
}
|
||||
|
||||
private static final String relic = "Darksteel Relic";
|
||||
|
||||
@Test
|
||||
public void testNoMana() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, relic, 2);
|
||||
addCard(Zone.BATTLEFIELD, playerA, waterbender);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "waterbend");
|
||||
setChoice(playerA, waterbender);
|
||||
setChoice(playerA, relic);
|
||||
setChoice(playerA, relic);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertPowerToughness(playerA, waterbender, 5, 2);
|
||||
assertTapped(waterbender, true);
|
||||
assertTapped(relic, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testManaAndCreature() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
|
||||
addCard(Zone.BATTLEFIELD, playerA, waterbender);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "waterbend");
|
||||
setChoice(playerA, waterbender);
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertPowerToughness(playerA, waterbender, 5, 2);
|
||||
assertTapped(waterbender, true);
|
||||
assertTapped("Island", true);
|
||||
}
|
||||
|
||||
private static final String katara = "Katara, Water Tribe's Hope";
|
||||
|
||||
@Test
|
||||
public void testX() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island");
|
||||
addCard(Zone.BATTLEFIELD, playerA, relic);
|
||||
addCard(Zone.BATTLEFIELD, playerA, katara);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "waterbend");
|
||||
setChoice(playerA, "X=3");
|
||||
setChoice(playerA, relic);
|
||||
setChoice(playerA, katara);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertPowerToughness(playerA, katara, 3, 3);
|
||||
assertTapped(relic, true);
|
||||
assertTapped(katara, true);
|
||||
assertTapped("Island", true);
|
||||
}
|
||||
|
||||
private static final String spirit = "Benevolent River Spirit";
|
||||
|
||||
@Test
|
||||
public void testSpellCost() {
|
||||
removeAllCardsFromLibrary(playerA); // removes need to make scry choices
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
|
||||
addCard(Zone.BATTLEFIELD, playerA, relic, 3);
|
||||
addCard(Zone.HAND, playerA, spirit);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, spirit);
|
||||
setChoice(playerA, relic);
|
||||
setChoice(playerA, relic);
|
||||
setChoice(playerA, relic);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertTapped("Island", true);
|
||||
assertTapped(relic, true);
|
||||
assertTapped(spirit, false);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,10 +7,9 @@ import mage.abilities.common.EntersBattlefieldAbility;
|
|||
import mage.abilities.condition.Condition;
|
||||
import mage.abilities.costs.*;
|
||||
import mage.abilities.costs.common.PayLifeCost;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.costs.mana.VariableManaCost;
|
||||
import mage.abilities.costs.common.WaterbendCost;
|
||||
import mage.abilities.costs.common.WaterbendXCost;
|
||||
import mage.abilities.costs.mana.*;
|
||||
import mage.abilities.effects.ContinuousEffect;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.Effects;
|
||||
|
|
@ -25,6 +24,7 @@ import mage.choices.ChoiceHintType;
|
|||
import mage.choices.ChoiceImpl;
|
||||
import mage.constants.*;
|
||||
import mage.filter.FilterMana;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.game.command.Dungeon;
|
||||
import mage.game.command.Emblem;
|
||||
|
|
@ -39,8 +39,10 @@ import mage.game.stack.StackAbility;
|
|||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.TargetCard;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.Targets;
|
||||
import mage.target.common.TargetCardInLibrary;
|
||||
import mage.target.common.TargetControlledPermanent;
|
||||
import mage.target.targetadjustment.GenericTargetAdjuster;
|
||||
import mage.target.targetadjustment.TargetAdjuster;
|
||||
import mage.util.CardUtil;
|
||||
|
|
@ -50,6 +52,7 @@ import mage.watchers.Watcher;
|
|||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
|
|
@ -349,6 +352,7 @@ public abstract class AbilityImpl implements Ability {
|
|||
// Phyrexian mana symbols, the player announces whether they intend to pay 2
|
||||
// life or the corresponding colored mana cost for each of those symbols.
|
||||
AbilityImpl.handlePhyrexianCosts(game, this, this, this.getManaCostsToPay());
|
||||
AbilityImpl.handleWaterbendingCosts(game, this, this, this.getManaCostsToPay());
|
||||
|
||||
// 20241022 - 601.2b
|
||||
// Not yet included in 601.2b but this is where it will be
|
||||
|
|
@ -687,6 +691,38 @@ public abstract class AbilityImpl implements Ability {
|
|||
}
|
||||
}
|
||||
|
||||
public static void handleWaterbendingCosts(Game game, Ability source, Ability abilityToPay, ManaCosts manaCostsToPay) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int total = CardUtil
|
||||
.castStream(manaCostsToPay, WaterbendCost.class)
|
||||
.mapToInt(WaterbendCost::manaValue)
|
||||
.sum();
|
||||
if (total < 1) {
|
||||
return;
|
||||
}
|
||||
TargetPermanent target = new TargetControlledPermanent(
|
||||
0, total, StaticFilters.FILTER_CONTROLLED_UNTAPPED_ARTIFACT_OR_CREATURE, true
|
||||
);
|
||||
target.withChooseHint("to tap for waterbending");
|
||||
controller.choose(Outcome.Tap, target, source, game);
|
||||
Set<Permanent> permanents = target
|
||||
.getTargets()
|
||||
.stream()
|
||||
.map(game::getPermanent)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
for (Permanent permanent : permanents) {
|
||||
permanent.tap(source, game);
|
||||
}
|
||||
manaCostsToPay.removeIf(WaterbendCost.class::isInstance);
|
||||
abilityToPay.addCost(new GenericManaCost(total - permanents.size()));
|
||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.WATERBENDED, source.getSourceId(), source, controller.getId(), total));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare and pay Phyrexian style effects like replace mana by life
|
||||
* Must be called after original Phyrexian mana processing and after cost modifications, e.g. on payment
|
||||
|
|
@ -779,8 +815,12 @@ public abstract class AbilityImpl implements Ability {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (variableManaCost != null) {
|
||||
if (!variableManaCost.isPaid()) { // should only happen for human players
|
||||
if (variableManaCost == null) {
|
||||
return variableManaCost;
|
||||
}
|
||||
if (variableManaCost.isPaid()) {
|
||||
return variableManaCost;
|
||||
} // should only happen for human players
|
||||
int xValue;
|
||||
if (!noMana || variableManaCost.getCostType().canUseAnnounceOnFreeCast()) {
|
||||
if (variableManaCost.wasAnnounced()) {
|
||||
|
|
@ -794,10 +834,11 @@ public abstract class AbilityImpl implements Ability {
|
|||
|
||||
int amountMana = xValue * variableManaCost.getXInstancesCount();
|
||||
StringBuilder manaString = threadLocalBuilder.get();
|
||||
if (!(variableManaCost instanceof WaterbendXCost)) {
|
||||
if (variableManaCost.getFilter() == null || variableManaCost.getFilter().isGeneric()) {
|
||||
manaString.append('{').append(amountMana).append('}');
|
||||
} else {
|
||||
String manaSymbol = null;
|
||||
String manaSymbol;
|
||||
if (variableManaCost.getFilter().isBlack()) {
|
||||
if (variableManaCost.getFilter().isRed()) {
|
||||
manaSymbol = "B/R";
|
||||
|
|
@ -812,8 +853,7 @@ public abstract class AbilityImpl implements Ability {
|
|||
manaSymbol = "G";
|
||||
} else if (variableManaCost.getFilter().isWhite()) {
|
||||
manaSymbol = "W";
|
||||
}
|
||||
if (manaSymbol == null) {
|
||||
} else {
|
||||
throw new UnsupportedOperationException("ManaFilter is not supported: " + this);
|
||||
}
|
||||
for (int i = 0; i < amountMana; i++) {
|
||||
|
|
@ -821,12 +861,13 @@ public abstract class AbilityImpl implements Ability {
|
|||
}
|
||||
}
|
||||
addManaCostsToPay(new ManaCostsImpl<>(manaString.toString()));
|
||||
} else {
|
||||
addManaCostsToPay(new WaterbendCost(amountMana));
|
||||
}
|
||||
getManaCostsToPay().setX(xValue, amountMana);
|
||||
setCostsTag("X", xValue);
|
||||
}
|
||||
variableManaCost.setPaid();
|
||||
}
|
||||
}
|
||||
|
||||
return variableManaCost;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,31 @@
|
|||
package mage.abilities.costs.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.CostImpl;
|
||||
import mage.game.Game;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.Mana;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
|
||||
/**
|
||||
* TODO: Implement properly
|
||||
* 701.67. Waterbend
|
||||
* <p>
|
||||
* 701.67a “Waterbend [cost]” means “Pay [cost]. For each generic mana in that cost,
|
||||
* you may tap an untapped artifact or creature you control rather than pay that mana.”
|
||||
* <p>
|
||||
* 701.67b If a waterbend cost is part of the total cost to cast a spell or activate an ability
|
||||
* (usually because the waterbend cost itself is an additional cost), the alternate method to pay for mana
|
||||
* described in rule 701.67a may be used only to pay for the amount of generic mana in the waterbend cost,
|
||||
* even if the total cost to cast that spell or activate that ability includes other generic mana components.
|
||||
* <p>
|
||||
* If you need Waterbend {X} then use {@link WaterbendXCost}
|
||||
* If using as an additional cost for a spell, add an ability with an InfoEffect for proper text generation (see WaterWhip)
|
||||
*
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class WaterbendCost extends CostImpl {
|
||||
public class WaterbendCost extends GenericManaCost {
|
||||
|
||||
public WaterbendCost(int amount) {
|
||||
this("{" + amount + '}');
|
||||
super(amount);
|
||||
for (int i = 0; i < amount; i++) {
|
||||
options.add(Mana.ColorlessMana(i));
|
||||
}
|
||||
|
||||
public WaterbendCost(String mana) {
|
||||
super();
|
||||
this.text = "waterbend " + mana;
|
||||
}
|
||||
|
||||
private WaterbendCost(final WaterbendCost cost) {
|
||||
|
|
@ -33,12 +38,7 @@ public class WaterbendCost extends CostImpl {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
|
||||
return false;
|
||||
public String getText() {
|
||||
return "waterbend " + super.getText();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package mage.abilities.costs.common;
|
||||
|
||||
import mage.abilities.costs.VariableCostType;
|
||||
import mage.abilities.costs.mana.VariableManaCost;
|
||||
|
||||
/**
|
||||
* Used for Waterbend {X} costs, otherwise use {@link WaterbendCost}
|
||||
* If using as an additional cost for a spell, add an ability with an InfoEffect for proper text generation (see WaterbendersRestoration)
|
||||
*
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class WaterbendXCost extends VariableManaCost {
|
||||
|
||||
public WaterbendXCost() {
|
||||
this(0);
|
||||
}
|
||||
|
||||
public WaterbendXCost(int minX) {
|
||||
super(VariableCostType.NORMAL);
|
||||
this.setMinX(minX);
|
||||
}
|
||||
|
||||
private WaterbendXCost(final WaterbendXCost cost) {
|
||||
super(cost);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WaterbendXCost copy() {
|
||||
return new WaterbendXCost(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return "waterbend {X}";
|
||||
}
|
||||
}
|
||||
|
|
@ -5,20 +5,27 @@ import mage.abilities.Ability;
|
|||
import mage.abilities.AbilityImpl;
|
||||
import mage.abilities.costs.*;
|
||||
import mage.abilities.costs.common.PayLifeCost;
|
||||
import mage.abilities.costs.common.WaterbendCost;
|
||||
import mage.abilities.mana.ManaOptions;
|
||||
import mage.constants.ColoredManaSymbol;
|
||||
import mage.constants.ManaType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.filter.Filter;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.ManaPool;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.Targets;
|
||||
import mage.target.common.TargetControlledPermanent;
|
||||
import mage.util.CardUtil;
|
||||
import mage.util.ManaUtil;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @param <T>
|
||||
|
|
@ -162,6 +169,7 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
|
|||
if (payingPlayer != null) {
|
||||
int bookmark = game.bookmarkState();
|
||||
handlePhyrexianManaCosts(ability, payingPlayer, source, game);
|
||||
handleWaterbendingCosts(ability, payingPlayer, source, game);
|
||||
if (pay(ability, game, source, payingPlayerId, false, null)) {
|
||||
game.removeBookmark(bookmark);
|
||||
return true;
|
||||
|
|
@ -195,6 +203,33 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
|
|||
tempCosts.pay(source, game, source, payingPlayer.getId(), false, null);
|
||||
}
|
||||
|
||||
private void handleWaterbendingCosts(Ability abilityToPay, Player payingPlayer, Ability source, Game game) {
|
||||
int total = CardUtil
|
||||
.castStream(this, WaterbendCost.class)
|
||||
.mapToInt(WaterbendCost::manaValue)
|
||||
.sum();
|
||||
if (total < 1) {
|
||||
return;
|
||||
}
|
||||
TargetPermanent target = new TargetControlledPermanent(
|
||||
0, total, StaticFilters.FILTER_CONTROLLED_UNTAPPED_ARTIFACT_OR_CREATURE, true
|
||||
);
|
||||
target.withChooseHint("to tap for waterbending");
|
||||
payingPlayer.choose(Outcome.Tap, target, source, game);
|
||||
Set<Permanent> permanents = target
|
||||
.getTargets()
|
||||
.stream()
|
||||
.map(game::getPermanent)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
for (Permanent permanent : permanents) {
|
||||
permanent.tap(source, game);
|
||||
}
|
||||
this.removeIf(WaterbendCost.class::isInstance);
|
||||
this.add(new GenericManaCost(total - permanents.size()));
|
||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.WATERBENDED, source.getSourceId(), source, payingPlayer.getId(), total));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManaCosts<T> getUnpaid() {
|
||||
ManaCosts<T> unpaid = new ManaCostsImpl<>();
|
||||
|
|
|
|||
|
|
@ -478,6 +478,17 @@ public final class StaticFilters {
|
|||
FILTER_CONTROLLED_PERMANENT_ARTIFACT_OR_CREATURE.setLockedFilter(true);
|
||||
}
|
||||
|
||||
public static final FilterControlledPermanent FILTER_CONTROLLED_UNTAPPED_ARTIFACT_OR_CREATURE = new FilterControlledPermanent("untapped artifact or creature you control");
|
||||
|
||||
static {
|
||||
FILTER_CONTROLLED_UNTAPPED_ARTIFACT_OR_CREATURE.add(TappedPredicate.UNTAPPED);
|
||||
FILTER_CONTROLLED_UNTAPPED_ARTIFACT_OR_CREATURE.add(Predicates.or(
|
||||
CardType.ARTIFACT.getPredicate(),
|
||||
CardType.CREATURE.getPredicate()
|
||||
));
|
||||
FILTER_CONTROLLED_UNTAPPED_ARTIFACT_OR_CREATURE.setLockedFilter(true);
|
||||
}
|
||||
|
||||
public static final FilterControlledPermanent FILTER_CONTROLLED_ARTIFACT_OR_OTHER_CREATURE = new FilterControlledPermanent("another creature or an artifact");
|
||||
|
||||
static {
|
||||
|
|
|
|||
|
|
@ -3711,7 +3711,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
* @return
|
||||
*/
|
||||
protected boolean canPlay(ActivatedAbility ability, ManaOptions availableMana, MageObject sourceObject, Game game) {
|
||||
if (!ability.isManaActivatedAbility()) {
|
||||
if (ability.isManaActivatedAbility()) {
|
||||
return false;
|
||||
}
|
||||
ActivatedAbility copy = ability.copy(); // Copy is needed because cost reduction effects modify e.g. the mana to activate/cast the ability
|
||||
if (!copy.canActivate(playerId, game).canActivate()) {
|
||||
return false;
|
||||
|
|
@ -3767,7 +3769,6 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
if (AbilityType.SPELL.equals(ability.getAbilityType())) {
|
||||
return canPlayCardByAlternateCost(game.getCard(ability.getSourceId()), availableMana, copy, game);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -4254,8 +4255,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
Game game = originalGame.createSimulationForPlayableCalc();
|
||||
ManaOptions availableMana = getManaAvailable(game); // get available mana options (mana pool and conditional mana added (but conditional still lose condition))
|
||||
boolean fromAll = fromZone.equals(Zone.ALL);
|
||||
if (hidden && (fromAll || fromZone == Zone.HAND)) {
|
||||
if (hidden && fromZone.match(Zone.HAND)) {
|
||||
for (Card card : hand.getCards(game)) {
|
||||
for (Ability ability : card.getAbilities(game)) { // gets this activated ability from hand? (Morph?)
|
||||
if (ability.getZone().match(Zone.HAND)) {
|
||||
|
|
@ -4300,7 +4300,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
if (fromAll || fromZone == Zone.GRAVEYARD) {
|
||||
if (fromZone.match(Zone.GRAVEYARD)) {
|
||||
for (UUID playerId : game.getState().getPlayersInRange(getId(), game)) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player == null) {
|
||||
|
|
@ -4312,7 +4312,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
if (fromAll || fromZone == Zone.EXILED) {
|
||||
if (fromZone.match(Zone.EXILED)) {
|
||||
for (ExileZone exile : game.getExile().getExileZones()) {
|
||||
for (Card card : exile.getCards(game)) {
|
||||
getPlayableFromObjectAll(game, Zone.EXILED, card, availableMana, playable);
|
||||
|
|
@ -4321,7 +4321,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
// check to play revealed cards
|
||||
if (fromAll) {
|
||||
if (fromZone.match(Zone.ALL)) {
|
||||
for (Cards revealedCards : game.getState().getRevealed().values()) {
|
||||
for (Card card : revealedCards.getCards(game)) {
|
||||
// revealed cards can be from any zones
|
||||
|
|
@ -4331,7 +4331,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
// outside cards
|
||||
if (fromAll || fromZone == Zone.OUTSIDE) {
|
||||
if (fromZone.match(Zone.OUTSIDE)) {
|
||||
// companion cards
|
||||
for (Cards companionCards : game.getState().getCompanion().values()) {
|
||||
for (Card card : companionCards.getCards(game)) {
|
||||
|
|
@ -4349,7 +4349,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
// check if it's possible to play the top card of a library
|
||||
if (fromAll || fromZone == Zone.LIBRARY) {
|
||||
if (fromZone.match(Zone.LIBRARY)) {
|
||||
for (UUID playerInRangeId : game.getState().getPlayersInRange(getId(), game)) {
|
||||
Player player = game.getPlayer(playerInRangeId);
|
||||
if (player != null && player.getLibrary().hasCards()) {
|
||||
|
|
@ -4365,7 +4365,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
// TODO: remove direct hand check (reveal fix in Sen Triplets)?
|
||||
// human games: cards from opponent's hand must be revealed before play
|
||||
// AI games: computer can see and play cards from opponent's hand without reveal
|
||||
if (fromAll || fromZone == Zone.HAND) {
|
||||
if (fromZone.match(Zone.HAND)) {
|
||||
for (UUID playerInRangeId : game.getState().getPlayersInRange(getId(), game)) {
|
||||
Player player = game.getPlayer(playerInRangeId);
|
||||
if (player != null && !player.getHand().isEmpty()) {
|
||||
|
|
@ -4383,7 +4383,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
List<ActivatedAbility> activatedAll = new ArrayList<>();
|
||||
|
||||
// activated abilities from battlefield objects
|
||||
if (fromAll || fromZone == Zone.BATTLEFIELD) {
|
||||
if (fromZone.match(Zone.BATTLEFIELD)) {
|
||||
for (Permanent permanent : game.getBattlefield().getAllActivePermanents()) {
|
||||
boolean canUseActivated = permanent.canUseActivatedAbilities(game);
|
||||
List<ActivatedAbility> currentPlayable = new ArrayList<>();
|
||||
|
|
@ -4398,7 +4398,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
// activated abilities from stack objects
|
||||
if (fromAll || fromZone == Zone.STACK) {
|
||||
if (fromZone.match(Zone.STACK)) {
|
||||
for (StackObject stackObject : game.getState().getStack()) {
|
||||
List<ActivatedAbility> currentPlayable = new ArrayList<>();
|
||||
getPlayableFromObjectAll(game, Zone.STACK, stackObject, availableMana, currentPlayable);
|
||||
|
|
@ -4410,7 +4410,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
// activated abilities from objects in the command zone (emblems or commanders)
|
||||
if (fromAll || fromZone == Zone.COMMAND) {
|
||||
if (fromZone.match(Zone.COMMAND)) {
|
||||
for (CommandObject commandObject : game.getState().getCommand()) {
|
||||
List<ActivatedAbility> currentPlayable = new ArrayList<>();
|
||||
getPlayableFromObjectAll(game, Zone.COMMAND, commandObject, availableMana, currentPlayable);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue