diff --git a/Mage.Sets/src/mage/cards/b/BloodthirstyConqueror.java b/Mage.Sets/src/mage/cards/b/BloodthirstyConqueror.java index f0b3929fe97c..7b79ec2d3268 100644 --- a/Mage.Sets/src/mage/cards/b/BloodthirstyConqueror.java +++ b/Mage.Sets/src/mage/cards/b/BloodthirstyConqueror.java @@ -1,8 +1,8 @@ package mage.cards.b; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.dynamicvalue.common.SavedGainedLifeValue; +import mage.abilities.common.LoseLifeTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedLifeLossValue; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.keyword.DeathtouchAbility; import mage.abilities.keyword.FlyingAbility; @@ -10,9 +10,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; +import mage.constants.TargetController; import java.util.UUID; @@ -36,7 +34,8 @@ public BloodthirstyConqueror(UUID ownerId, CardSetInfo setInfo) { this.addAbility(DeathtouchAbility.getInstance()); // Whenever an opponent loses life, you gain that much life. - this.addAbility(new BloodthirstyConquerorTriggeredAbility()); + this.addAbility(new LoseLifeTriggeredAbility(new GainLifeEffect(SavedLifeLossValue.MUCH), + TargetController.OPPONENT, false, false)); } private BloodthirstyConqueror(final BloodthirstyConqueror card) { @@ -48,34 +47,3 @@ public BloodthirstyConqueror copy() { return new BloodthirstyConqueror(this); } } - -class BloodthirstyConquerorTriggeredAbility extends TriggeredAbilityImpl { - - BloodthirstyConquerorTriggeredAbility() { - super(Zone.BATTLEFIELD, new GainLifeEffect(SavedGainedLifeValue.MUCH)); - this.setTriggerPhrase("Whenever an opponent loses life, "); - } - - private BloodthirstyConquerorTriggeredAbility(final BloodthirstyConquerorTriggeredAbility ability) { - super(ability); - } - - @Override - public BloodthirstyConquerorTriggeredAbility copy() { - return new BloodthirstyConquerorTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.LOST_LIFE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (!game.getOpponents(getControllerId()).contains(event.getTargetId())) { - return false; - } - this.getEffects().setValue(SavedGainedLifeValue.VALUE_KEY, event.getAmount()); - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/c/CuratorBeastie.java b/Mage.Sets/src/mage/cards/c/CuratorBeastie.java index c387cf705768..32259b1911e0 100644 --- a/Mage.Sets/src/mage/cards/c/CuratorBeastie.java +++ b/Mage.Sets/src/mage/cards/c/CuratorBeastie.java @@ -41,7 +41,7 @@ public CuratorBeastie(UUID ownerId, CardSetInfo setInfo) { // Colorless creatures you control enter with two additional +1/+1 counters on them. this.addAbility(new SimpleStaticAbility(new EntersWithCountersControlledEffect( filter, CounterType.P1P1.createInstance(2), false - ))); + ).setText("colorless creatures you control enter with two additional +1/+1 counters on them"))); // Whenever Curator Beastie enters or attacks, manifest dread. this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new ManifestDreadEffect())); diff --git a/Mage.Sets/src/mage/cards/e/ExquisiteBlood.java b/Mage.Sets/src/mage/cards/e/ExquisiteBlood.java index c84713ae9c03..62a3d573b9da 100644 --- a/Mage.Sets/src/mage/cards/e/ExquisiteBlood.java +++ b/Mage.Sets/src/mage/cards/e/ExquisiteBlood.java @@ -1,16 +1,14 @@ - package mage.cards.e; -import java.util.UUID; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.LoseLifeTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedLifeLossValue; import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; +import mage.constants.TargetController; + +import java.util.UUID; /** * @author noxx @@ -18,12 +16,11 @@ public final class ExquisiteBlood extends CardImpl { public ExquisiteBlood(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{4}{B}"); - + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{B}"); // Whenever an opponent loses life, you gain that much life. - ExquisiteBloodTriggeredAbility ability = new ExquisiteBloodTriggeredAbility(); - this.addAbility(ability); + this.addAbility(new LoseLifeTriggeredAbility(new GainLifeEffect(SavedLifeLossValue.MUCH), + TargetController.OPPONENT, false, false)); } private ExquisiteBlood(final ExquisiteBlood card) { @@ -35,39 +32,3 @@ public ExquisiteBlood copy() { return new ExquisiteBlood(this); } } - -class ExquisiteBloodTriggeredAbility extends TriggeredAbilityImpl { - - public ExquisiteBloodTriggeredAbility() { - super(Zone.BATTLEFIELD, null); - } - - private ExquisiteBloodTriggeredAbility(final ExquisiteBloodTriggeredAbility ability) { - super(ability); - } - - @Override - public ExquisiteBloodTriggeredAbility copy() { - return new ExquisiteBloodTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.LOST_LIFE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (game.getOpponents(this.controllerId).contains(event.getPlayerId())) { - this.getEffects().clear(); - this.addEffect(new GainLifeEffect(event.getAmount())); - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever an opponent loses life, you gain that much life."; - } -} diff --git a/Mage.Sets/src/mage/cards/g/GlitchInterpreter.java b/Mage.Sets/src/mage/cards/g/GlitchInterpreter.java index 825c32831844..07ca760bfc9b 100644 --- a/Mage.Sets/src/mage/cards/g/GlitchInterpreter.java +++ b/Mage.Sets/src/mage/cards/g/GlitchInterpreter.java @@ -46,7 +46,8 @@ public GlitchInterpreter(UUID ownerId, CardSetInfo setInfo) { this.toughness = new MageInt(3); // When Glitch Interpreter enters, if you control no face-down permanents, return Glitch Interpreter to its owner's hand and manifest dread. - Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnToHandSourceEffect()).withInterveningIf(condition); + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnToHandSourceEffect()) + .withRuleTextReplacement(false).withInterveningIf(condition); ability.addEffect(new ManifestDreadEffect().concatBy("and")); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/g/GontisMachinations.java b/Mage.Sets/src/mage/cards/g/GontisMachinations.java index 58ea48c619fc..3503bdb83f17 100644 --- a/Mage.Sets/src/mage/cards/g/GontisMachinations.java +++ b/Mage.Sets/src/mage/cards/g/GontisMachinations.java @@ -1,7 +1,7 @@ package mage.cards.g; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.LoseLifeFirstTimeEachTurnTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.PayEnergyCost; import mage.abilities.costs.common.SacrificeSourceCost; @@ -10,14 +10,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.WatcherScope; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.watchers.Watcher; -import java.util.HashMap; -import java.util.Map; import java.util.UUID; /** @@ -29,8 +22,8 @@ public GontisMachinations(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{B}"); // Whenever you lose life for the first time each turn, you get {E}. - this.addAbility(new GontisMachinationsTriggeredAbility(), - new GontisMachinationsFirstLostLifeThisTurnWatcher()); + this.addAbility(new LoseLifeFirstTimeEachTurnTriggeredAbility( + new GetEnergyCountersControllerEffect(1))); // Pay {E}{E}, Sacrifice Gonti's Machinations: Each opponent loses 3 life. You gain life equal to the life lost this way. Ability ability = new SimpleActivatedAbility( @@ -50,68 +43,3 @@ public GontisMachinations copy() { return new GontisMachinations(this); } } - -class GontisMachinationsTriggeredAbility extends TriggeredAbilityImpl { - - public GontisMachinationsTriggeredAbility() { - super(Zone.BATTLEFIELD, new GetEnergyCountersControllerEffect(1), false); - setTriggerPhrase("Whenever you lose life for the first time each turn, "); - } - - private GontisMachinationsTriggeredAbility(final GontisMachinationsTriggeredAbility ability) { - super(ability); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.LOST_LIFE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getPlayerId().equals(getControllerId())) { - GontisMachinationsFirstLostLifeThisTurnWatcher watcher - = game.getState().getWatcher(GontisMachinationsFirstLostLifeThisTurnWatcher.class); - if (watcher != null - && watcher.timesLostLifeThisTurn(event.getPlayerId()) < 2) { - return true; - } - } - return false; - } - - @Override - public GontisMachinationsTriggeredAbility copy() { - return new GontisMachinationsTriggeredAbility(this); - } -} - -class GontisMachinationsFirstLostLifeThisTurnWatcher extends Watcher { - - private final Map playersLostLife = new HashMap<>(); - - public GontisMachinationsFirstLostLifeThisTurnWatcher() { - super(WatcherScope.GAME); - } - - @Override - public void watch(GameEvent event, Game game) { - switch (event.getType()) { - case LOST_LIFE: - int timesLifeLost = playersLostLife.getOrDefault(event.getPlayerId(), 0); - timesLifeLost++; - playersLostLife.put(event.getPlayerId(), timesLifeLost); - } - } - - - @Override - public void reset() { - super.reset(); - playersLostLife.clear(); - } - - public int timesLostLifeThisTurn(UUID playerId) { - return playersLostLife.getOrDefault(playerId, 0); - } -} diff --git a/Mage.Sets/src/mage/cards/l/LichsMastery.java b/Mage.Sets/src/mage/cards/l/LichsMastery.java index c62db9e6f64d..b07d5c77b2e3 100644 --- a/Mage.Sets/src/mage/cards/l/LichsMastery.java +++ b/Mage.Sets/src/mage/cards/l/LichsMastery.java @@ -1,12 +1,12 @@ - package mage.cards.l; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.GainLifeControllerTriggeredAbility; import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.common.LoseLifeTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.common.SavedGainedLifeValue; +import mage.abilities.dynamicvalue.common.SavedLifeLossValue; import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; @@ -56,7 +56,7 @@ public LichsMastery(UUID ownerId, CardSetInfo setInfo) { )); // Whenever you lose life, for each 1 life you lost, exile a permanent you control or a card from your hand or graveyard. - this.addAbility(new LichsMasteryLoseLifeTriggeredAbility()); + this.addAbility(new LoseLifeTriggeredAbility(new LichsMasteryLoseLifeEffect())); // When Lich's Mastery leaves the battlefield, you lose the game. this.addAbility(new LeavesBattlefieldTriggeredAbility(new LoseGameSourceControllerEffect(), false)); @@ -99,57 +99,15 @@ public boolean applies(GameEvent event, Ability source, Game game) { } } -class LichsMasteryLoseLifeTriggeredAbility extends TriggeredAbilityImpl { - - public LichsMasteryLoseLifeTriggeredAbility() { - super(Zone.BATTLEFIELD, new LichsMasteryLoseLifeEffect(), false); - } - - private LichsMasteryLoseLifeTriggeredAbility(final LichsMasteryLoseLifeTriggeredAbility ability) { - super(ability); - } - - @Override - public LichsMasteryLoseLifeTriggeredAbility copy() { - return new LichsMasteryLoseLifeTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.LOST_LIFE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getPlayerId().equals(this.getControllerId())) { - for (Effect effect : this.getEffects()) { - if (effect instanceof LichsMasteryLoseLifeEffect) { - ((LichsMasteryLoseLifeEffect) effect).setAmount(event.getAmount()); - } - } - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever you lose life, for each 1 life you lost, exile a permanent you control or a card from your hand or graveyard."; - } -} - class LichsMasteryLoseLifeEffect extends OneShotEffect { - private int amount = 0; - - public LichsMasteryLoseLifeEffect() { + LichsMasteryLoseLifeEffect() { super(Outcome.Exile); - this.staticText = "for each 1 life you lost, exile a permanent you control or a card from your hand or graveyard."; + this.staticText = "for each 1 life you lost, exile a permanent you control or a card from your hand or graveyard"; } private LichsMasteryLoseLifeEffect(final LichsMasteryLoseLifeEffect effect) { super(effect); - this.amount = effect.amount; } @Override @@ -165,7 +123,7 @@ public boolean apply(Game game, Ability source) { } FilterPermanent filter = new FilterPermanent(); filter.add(new ControllerIdPredicate(controller.getId())); - for (int i = 0; i < amount; i++) { + for (int i = 0; i < SavedLifeLossValue.MANY.calculate(game, source, this); i++) { int handCount = controller.getHand().size(); int graveCount = controller.getGraveyard().size(); int permCount = game.getBattlefield().getActivePermanents(filter, controller.getId(), game).size(); @@ -182,7 +140,7 @@ public boolean apply(Game game, Ability source) { if (card != null) { controller.moveCards(card, Zone.EXILED, source, game); } - } else if (graveCount > 0) { + } else { Target target = new TargetCardInYourGraveyard(1, 1, new FilterCard(), true); target.choose(Outcome.Exile, source.getControllerId(), source.getSourceId(), source, game); Card card = controller.getGraveyard().get(target.getFirstTarget(), game); @@ -194,7 +152,4 @@ public boolean apply(Game game, Ability source) { return true; } - public void setAmount(int amount) { - this.amount = amount; - } } diff --git a/Mage.Sets/src/mage/cards/l/LichsTomb.java b/Mage.Sets/src/mage/cards/l/LichsTomb.java index f19915094974..ce4d9f56978a 100644 --- a/Mage.Sets/src/mage/cards/l/LichsTomb.java +++ b/Mage.Sets/src/mage/cards/l/LichsTomb.java @@ -1,37 +1,33 @@ - package mage.cards.l; -import java.util.UUID; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.LoseLifeTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.dynamicvalue.common.SavedLifeLossValue; import mage.abilities.effects.common.SacrificeControllerEffect; -import mage.abilities.effects.common.SacrificeEffect; import mage.abilities.effects.common.continuous.DontLoseByZeroOrLessLifeEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.constants.Zone; -import mage.filter.FilterPermanent; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author emerald000 */ public final class LichsTomb extends CardImpl { public LichsTomb(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{4}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}"); // You don't lose the game for having 0 or less life. this.addAbility(new SimpleStaticAbility(new DontLoseByZeroOrLessLifeEffect(Duration.WhileOnBattlefield))); // Whenever you lose life, sacrifice a permanent for each 1 life you lost. - this.addAbility(new LichsTombTriggeredAbility()); + this.addAbility(new LoseLifeTriggeredAbility(new SacrificeControllerEffect( + StaticFilters.FILTER_PERMANENT, SavedLifeLossValue.MANY, "" + ).setText("sacrifice a permanent for each 1 life you lost"))); } private LichsTomb(final LichsTomb card) { @@ -43,38 +39,3 @@ public LichsTomb copy() { return new LichsTomb(this); } } - -class LichsTombTriggeredAbility extends TriggeredAbilityImpl { - - LichsTombTriggeredAbility() { - super(Zone.BATTLEFIELD, new SacrificeControllerEffect(new FilterPermanent(), 0, ""), false); - } - - private LichsTombTriggeredAbility(final LichsTombTriggeredAbility ability) { - super(ability); - } - - @Override - public LichsTombTriggeredAbility copy() { - return new LichsTombTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.LOST_LIFE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getPlayerId().equals(this.getControllerId())) { - ((SacrificeEffect) this.getEffects().get(0)).setAmount(StaticValue.get(event.getAmount())); - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever you lose life, sacrifice a permanent for each 1 life you lost."; - } -} diff --git a/Mage.Sets/src/mage/cards/m/MarinaVendrellsGrimoire.java b/Mage.Sets/src/mage/cards/m/MarinaVendrellsGrimoire.java index 02d66919f0d0..82a54e1e1404 100644 --- a/Mage.Sets/src/mage/cards/m/MarinaVendrellsGrimoire.java +++ b/Mage.Sets/src/mage/cards/m/MarinaVendrellsGrimoire.java @@ -1,16 +1,16 @@ package mage.cards.m; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.GainLifeControllerTriggeredAbility; +import mage.abilities.common.LoseLifeTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.CastFromEverywhereSourceCondition; import mage.abilities.condition.common.HellbentCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.decorator.ConditionalOneShotEffect; -import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.dynamicvalue.common.SavedGainedLifeValue; +import mage.abilities.dynamicvalue.common.SavedLifeLossValue; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.LoseGameSourceControllerEffect; import mage.abilities.effects.common.continuous.DontLoseByZeroOrLessLifeEffect; @@ -21,9 +21,6 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SuperType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; import java.util.UUID; @@ -58,7 +55,12 @@ public MarinaVendrellsGrimoire(UUID ownerId, CardSetInfo setInfo) { )); // Whenever you lose life, discard that many cards. Then if you have no cards in hand, you lose the game. - this.addAbility(new MarinaVendrellsGrimoireTriggeredAbility()); + Ability ability2 = new LoseLifeTriggeredAbility(new DiscardControllerEffect(SavedLifeLossValue.MANY)); + ability2.addEffect(new ConditionalOneShotEffect( + new LoseGameSourceControllerEffect(), HellbentCondition.instance, + "Then if you have no cards in hand, you lose the game" + )); + this.addAbility(ability2); } private MarinaVendrellsGrimoire(final MarinaVendrellsGrimoire card) { @@ -70,38 +72,3 @@ public MarinaVendrellsGrimoire copy() { return new MarinaVendrellsGrimoire(this); } } - -class MarinaVendrellsGrimoireTriggeredAbility extends TriggeredAbilityImpl { - - MarinaVendrellsGrimoireTriggeredAbility() { - super(Zone.BATTLEFIELD, new DiscardControllerEffect(SavedDamageValue.MANY)); - this.addEffect(new ConditionalOneShotEffect( - new LoseGameSourceControllerEffect(), HellbentCondition.instance, - "Then if you have no cards in hand, you lose the game" - )); - this.setTriggerPhrase("Whenever you lose life, "); - } - - private MarinaVendrellsGrimoireTriggeredAbility(final MarinaVendrellsGrimoireTriggeredAbility ability) { - super(ability); - } - - @Override - public MarinaVendrellsGrimoireTriggeredAbility copy() { - return new MarinaVendrellsGrimoireTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.LOST_LIFE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (!isControlledBy(event.getPlayerId())) { - return false; - } - this.getEffects().setValue("damage", event.getAmount()); - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/m/Mindcrank.java b/Mage.Sets/src/mage/cards/m/Mindcrank.java index b2c5aebe1094..24dbfefe16d5 100644 --- a/Mage.Sets/src/mage/cards/m/Mindcrank.java +++ b/Mage.Sets/src/mage/cards/m/Mindcrank.java @@ -1,21 +1,13 @@ - package mage.cards.m; -import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.Effect; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.common.LoseLifeTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedLifeLossValue; +import mage.abilities.effects.common.MillCardsTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.players.Player; -import mage.target.targetpointer.FixedTarget; +import mage.constants.TargetController; -import java.util.Set; import java.util.UUID; /** @@ -26,9 +18,11 @@ public final class Mindcrank extends CardImpl { public Mindcrank(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); - // Whenever an opponent loses life, that player puts that many cards from the top of their library into their graveyard. - // (Damage dealt by sources without infect causes loss of life.) - this.addAbility(new MindcrankTriggeredAbility()); + // Whenever an opponent loses life, that player mills that many cards. + this.addAbility(new LoseLifeTriggeredAbility( + new MillCardsTargetEffect(SavedLifeLossValue.MANY), + TargetController.OPPONENT, false, true + )); } private Mindcrank(final Mindcrank card) { @@ -40,71 +34,3 @@ public Mindcrank copy() { return new Mindcrank(this); } } - -class MindcrankTriggeredAbility extends TriggeredAbilityImpl { - - public MindcrankTriggeredAbility() { - super(Zone.BATTLEFIELD, new MindcrankEffect(), false); - } - - private MindcrankTriggeredAbility(final MindcrankTriggeredAbility ability) { - super(ability); - } - - @Override - public MindcrankTriggeredAbility copy() { - return new MindcrankTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.LOST_LIFE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - Set opponents = game.getOpponents(this.getControllerId()); - if (opponents.contains(event.getPlayerId())) { - for (Effect effect : this.getEffects()) { - effect.setValue("lostLife", event.getAmount()); - effect.setTargetPointer(new FixedTarget(event.getPlayerId())); - } - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever an opponent loses life, that player mills that many cards."; - } -} - -class MindcrankEffect extends OneShotEffect { - - MindcrankEffect() { - super(Outcome.Detriment); - } - - private MindcrankEffect(final MindcrankEffect effect) { - super(effect); - } - - @Override - public MindcrankEffect copy() { - return new MindcrankEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); - if (targetPlayer != null) { - Integer amount = (Integer) getValue("lostLife"); - if (amount == null) { - amount = 0; - } - targetPlayer.millCards(amount, source, game); - } - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/o/OathOfLimDul.java b/Mage.Sets/src/mage/cards/o/OathOfLimDul.java index 46aab0dff8ad..6367bcca7365 100644 --- a/Mage.Sets/src/mage/cards/o/OathOfLimDul.java +++ b/Mage.Sets/src/mage/cards/o/OathOfLimDul.java @@ -1,31 +1,28 @@ package mage.cards.o; -import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.LoseLifeTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.Cost; -import mage.abilities.costs.common.DiscardTargetCost; +import mage.abilities.costs.common.DiscardCardCost; import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.SavedLifeLossValue; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoUnlessControllerPaysEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.SacrificeControllerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.constants.Zone; -import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.common.TargetCardInHand; -import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; /** - * * @author jeffwadsworth */ public final class OathOfLimDul extends CardImpl { @@ -34,7 +31,7 @@ public OathOfLimDul(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{B}"); // Whenever you lose life, for each 1 life you lost, sacrifice a permanent other than Oath of Lim-Dul unless you discard a card. - this.addAbility(new OathOfLimDulTriggeredAbility()); + this.addAbility(new LoseLifeTriggeredAbility(new OathOfLimDulEffect())); // {B}{B}: Draw a card. this.addAbility(new SimpleActivatedAbility(new DrawCardSourceControllerEffect(1), new ManaCostsImpl<>("{B}{B}"))); @@ -51,51 +48,17 @@ public OathOfLimDul copy() { } } -class OathOfLimDulTriggeredAbility extends TriggeredAbilityImpl { - - public OathOfLimDulTriggeredAbility() { - super(Zone.BATTLEFIELD, new OathOfLimDulEffect()); - } - - private OathOfLimDulTriggeredAbility(final OathOfLimDulTriggeredAbility ability) { - super(ability); - } - - @Override - public OathOfLimDulTriggeredAbility copy() { - return new OathOfLimDulTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.LOST_LIFE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getPlayerId().equals(controllerId)) { - game.getState().setValue(sourceId.toString() + "oathOfLimDul", event.getAmount()); - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever you lose life, for each 1 life you lost, sacrifice a permanent other than {this} unless you discard a card."; - } -} - class OathOfLimDulEffect extends OneShotEffect { - private static final FilterControlledPermanent filter = new FilterControlledPermanent("controlled permanent other than Oath of Lim-Dul to sacrifice"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent("controlled permanent other than {this} to sacrifice"); static { filter.add(AnotherPredicate.instance); } - public OathOfLimDulEffect() { - super(Outcome.Neutral); + OathOfLimDulEffect() { + super(Outcome.Detriment); + staticText = "for each 1 life you lost, sacrifice a permanent other than {this} unless you discard a card"; } private OathOfLimDulEffect(final OathOfLimDulEffect effect) { @@ -104,34 +67,19 @@ private OathOfLimDulEffect(final OathOfLimDulEffect effect) { @Override public boolean apply(Game game, Ability source) { - boolean sacrificeDone = false; - int numberSacrificed = 0; - int numberToDiscard = 0; - int numberOfControlledPermanents = 0; + int amountDamage = SavedLifeLossValue.MANY.calculate(game, source, this); Player controller = game.getPlayer(source.getControllerId()); - int amountDamage = (int) game.getState().getValue(source.getSourceId().toString() + "oathOfLimDul"); - if (amountDamage > 0 - && controller != null) { - TargetControlledPermanent target = new TargetControlledPermanent(0, numberOfControlledPermanents, filter, true); - target.withNotTarget(true); - if (controller.choose(Outcome.Detriment, target, source, game)) { - for (UUID targetPermanentId : target.getTargets()) { - Permanent permanent = game.getPermanent(targetPermanentId); - if (permanent != null - && permanent.sacrifice(source, game)) { - numberSacrificed += 1; - sacrificeDone = true; - } - } - } - numberToDiscard = amountDamage - numberSacrificed; - Cost cost = new DiscardTargetCost(new TargetCardInHand(numberToDiscard, new FilterCard("card(s) in your hand to discard"))); - if (numberToDiscard > 0 - && cost.canPay(source, source, controller.getId(), game)) { - return cost.pay(source, game, source, controller.getId(), true); // discard cost paid simultaneously - } + if (amountDamage <= 0 || controller == null) { + return false; + } + boolean didSomething = false; + for (int i = 0; i < amountDamage; ++i) { + didSomething |= new DoUnlessControllerPaysEffect( + new SacrificeControllerEffect(StaticFilters.FILTER_CONTROLLED_ANOTHER_PERMANENT, 1, ""), + new DiscardCardCost() + ).apply(game, source); } - return sacrificeDone; + return didSomething; } @Override diff --git a/Mage.Sets/src/mage/cards/t/Transcendence.java b/Mage.Sets/src/mage/cards/t/Transcendence.java index 5bd4fba41463..c45f3e7d82c5 100644 --- a/Mage.Sets/src/mage/cards/t/Transcendence.java +++ b/Mage.Sets/src/mage/cards/t/Transcendence.java @@ -1,34 +1,34 @@ - package mage.cards.t; -import java.util.UUID; -import mage.abilities.Ability; import mage.abilities.StateTriggeredAbility; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.LoseLifeTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.Effect; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.MultipliedValue; +import mage.abilities.dynamicvalue.common.SavedLifeLossValue; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.LoseGameSourceControllerEffect; import mage.abilities.effects.common.continuous.DontLoseByZeroOrLessLifeEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.players.Player; +import java.util.UUID; + /** - * * @author emerald000 */ public final class Transcendence extends CardImpl { + private static final DynamicValue value = new MultipliedValue(SavedLifeLossValue.MUCH, 2); + public Transcendence(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{3}{W}{W}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}{W}{W}"); // You don't lose the game for having 0 or less life. this.addAbility(new SimpleStaticAbility(new DontLoseByZeroOrLessLifeEffect(Duration.WhileOnBattlefield))); @@ -37,7 +37,9 @@ public Transcendence(UUID ownerId, CardSetInfo setInfo) { this.addAbility(new TranscendenceStateTriggeredAbility()); // Whenever you lose life, you gain 2 life for each 1 life you lost. - this.addAbility(new TranscendenceLoseLifeTriggeredAbility()); + this.addAbility(new LoseLifeTriggeredAbility( + new GainLifeEffect(value).setText("you gain 2 life for each 1 life you lost") + )); } private Transcendence(final Transcendence card) { @@ -79,76 +81,3 @@ public String getRule() { return "When you have 20 or more life, you lose the game."; } } - -class TranscendenceLoseLifeTriggeredAbility extends TriggeredAbilityImpl { - - TranscendenceLoseLifeTriggeredAbility() { - super(Zone.BATTLEFIELD, new TranscendenceLoseLifeEffect(), false); - } - - private TranscendenceLoseLifeTriggeredAbility(final TranscendenceLoseLifeTriggeredAbility ability) { - super(ability); - } - - @Override - public TranscendenceLoseLifeTriggeredAbility copy() { - return new TranscendenceLoseLifeTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.LOST_LIFE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getPlayerId().equals(this.getControllerId())) { - for (Effect effect : this.getEffects()) { - if (effect instanceof TranscendenceLoseLifeEffect) { - ((TranscendenceLoseLifeEffect) effect).setAmount(event.getAmount()); - } - } - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever you lose life, you gain 2 life for each 1 life you lost."; - } -} - -class TranscendenceLoseLifeEffect extends OneShotEffect { - - private int amount = 0; - - TranscendenceLoseLifeEffect() { - super(Outcome.GainLife); - this.staticText = "you gain 2 life for each 1 life you lost"; - } - - private TranscendenceLoseLifeEffect(final TranscendenceLoseLifeEffect effect) { - super(effect); - this.amount = effect.amount; - } - - @Override - public TranscendenceLoseLifeEffect copy() { - return new TranscendenceLoseLifeEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - controller.gainLife(2 * amount, game, source); - return true; - } - return false; - } - - public void setAmount(int amount) { - this.amount = amount; - } -} diff --git a/Mage.Sets/src/mage/cards/v/ValgavothHarrowerOfSouls.java b/Mage.Sets/src/mage/cards/v/ValgavothHarrowerOfSouls.java new file mode 100644 index 000000000000..14c24744ed7d --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/ValgavothHarrowerOfSouls.java @@ -0,0 +1,79 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.common.LoseLifeFirstTimeEachTurnTriggeredAbility; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.TargetController; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; + +import java.util.UUID; + +/** + * @author xenohedron + */ +public final class ValgavothHarrowerOfSouls extends CardImpl { + + public ValgavothHarrowerOfSouls(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ELDER); + this.subtype.add(SubType.DEMON); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Ward--Pay 2 life. + this.addAbility(new WardAbility(new PayLifeCost(2))); + + // Whenever an opponent loses life for the first time during each of their turns, put a +1/+1 counter on Valgavoth, Harrower of Souls and draw a card. + this.addAbility(new ValgavothHarrowerOfSoulsTriggeredAbility()); + + } + + private ValgavothHarrowerOfSouls(final ValgavothHarrowerOfSouls card) { + super(card); + } + + @Override + public ValgavothHarrowerOfSouls copy() { + return new ValgavothHarrowerOfSouls(this); + } +} + +class ValgavothHarrowerOfSoulsTriggeredAbility extends LoseLifeFirstTimeEachTurnTriggeredAbility { + + ValgavothHarrowerOfSoulsTriggeredAbility() { + super(new AddCountersSourceEffect(CounterType.P1P1.createInstance()), TargetController.OPPONENT, false, false); + this.addEffect(new DrawCardSourceControllerEffect(1).concatBy("and")); + setTriggerPhrase("Whenever an opponent loses life for the first time during each of their turns, "); + } + + private ValgavothHarrowerOfSoulsTriggeredAbility(final ValgavothHarrowerOfSoulsTriggeredAbility ability) { + super(ability); + } + + @Override + public ValgavothHarrowerOfSoulsTriggeredAbility copy() { + return new ValgavothHarrowerOfSoulsTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return game.isActivePlayer(event.getTargetId()) && super.checkTrigger(event, game); + } + +} diff --git a/Mage.Sets/src/mage/cards/v/VampireScrivener.java b/Mage.Sets/src/mage/cards/v/VampireScrivener.java index 8da71789ddbc..582fd6b96a29 100644 --- a/Mage.Sets/src/mage/cards/v/VampireScrivener.java +++ b/Mage.Sets/src/mage/cards/v/VampireScrivener.java @@ -1,8 +1,8 @@ package mage.cards.v; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.GainLifeControllerTriggeredAbility; +import mage.abilities.common.LoseLifeTriggeredAbility; import mage.abilities.condition.common.MyTurnCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.counter.AddCountersSourceEffect; @@ -11,7 +11,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; import mage.game.events.GameEvent; @@ -54,10 +53,11 @@ public VampireScrivener copy() { } } -class VampireScrivenerTriggeredAbility extends TriggeredAbilityImpl { +class VampireScrivenerTriggeredAbility extends LoseLifeTriggeredAbility { VampireScrivenerTriggeredAbility() { - super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance())); + super(new AddCountersSourceEffect(CounterType.P1P1.createInstance())); + setTriggerPhrase("Whenever you lose life during your turn, "); } private VampireScrivenerTriggeredAbility(final VampireScrivenerTriggeredAbility ability) { @@ -69,18 +69,9 @@ public VampireScrivenerTriggeredAbility copy() { return new VampireScrivenerTriggeredAbility(this); } - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.LOST_LIFE; - } - @Override public boolean checkTrigger(GameEvent event, Game game) { - return game.isActivePlayer(event.getPlayerId()) && game.isActivePlayer(getControllerId()); + return game.isActivePlayer(getControllerId()) && super.checkTrigger(event, game); } - @Override - public String getRule() { - return "Whenever you lose life during your turn, put a +1/+1 counter on {this}."; - } } diff --git a/Mage.Sets/src/mage/cards/v/VengefulWarchief.java b/Mage.Sets/src/mage/cards/v/VengefulWarchief.java index 8555a795f0fe..1e138a5d8615 100644 --- a/Mage.Sets/src/mage/cards/v/VengefulWarchief.java +++ b/Mage.Sets/src/mage/cards/v/VengefulWarchief.java @@ -1,21 +1,14 @@ package mage.cards.v; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.LoseLifeFirstTimeEachTurnTriggeredAbility; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.WatcherScope; -import mage.constants.Zone; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.watchers.Watcher; -import java.util.HashMap; -import java.util.Map; import java.util.UUID; /** @@ -32,7 +25,8 @@ public VengefulWarchief(UUID ownerId, CardSetInfo setInfo) { this.toughness = new MageInt(4); // Whenever you lose life for the first time each turn, put a +1/+1 counter on Vengeful Warchief. - this.addAbility(new VengefulWarchiefTriggeredAbility(), new VengefulWarchiefWatcher()); + this.addAbility(new LoseLifeFirstTimeEachTurnTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()))); } private VengefulWarchief(final VengefulWarchief card) { @@ -44,62 +38,3 @@ public VengefulWarchief copy() { return new VengefulWarchief(this); } } - -class VengefulWarchiefTriggeredAbility extends TriggeredAbilityImpl { - - VengefulWarchiefTriggeredAbility() { - super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance()), false); - setTriggerPhrase("Whenever you lose life for the first time each turn, "); - } - - private VengefulWarchiefTriggeredAbility(final VengefulWarchiefTriggeredAbility ability) { - super(ability); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.LOST_LIFE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (!event.getPlayerId().equals(getControllerId())) { - return false; - } - VengefulWarchiefWatcher watcher = game.getState().getWatcher(VengefulWarchiefWatcher.class); - return watcher != null && watcher.timesLostLifeThisTurn(event.getPlayerId()) < 2; - } - - @Override - public VengefulWarchiefTriggeredAbility copy() { - return new VengefulWarchiefTriggeredAbility(this); - } -} - -class VengefulWarchiefWatcher extends Watcher { - - private final Map playersLostLife = new HashMap<>(); - - VengefulWarchiefWatcher() { - super(WatcherScope.GAME); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.LOST_LIFE) { - int timesLifeLost = playersLostLife.getOrDefault(event.getPlayerId(), 0); - timesLifeLost++; - playersLostLife.put(event.getPlayerId(), timesLifeLost); - } - } - - @Override - public void reset() { - super.reset(); - playersLostLife.clear(); - } - - int timesLostLifeThisTurn(UUID playerId) { - return playersLostLife.getOrDefault(playerId, 0); - } -} diff --git a/Mage.Sets/src/mage/cards/v/VilisBrokerOfBlood.java b/Mage.Sets/src/mage/cards/v/VilisBrokerOfBlood.java index abf53785dbd0..9ee117518f56 100644 --- a/Mage.Sets/src/mage/cards/v/VilisBrokerOfBlood.java +++ b/Mage.Sets/src/mage/cards/v/VilisBrokerOfBlood.java @@ -2,10 +2,11 @@ import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.LoseLifeTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.PayLifeCost; import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.SavedLifeLossValue; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.abilities.keyword.FlyingAbility; @@ -14,9 +15,6 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -46,7 +44,7 @@ public VilisBrokerOfBlood(UUID ownerId, CardSetInfo setInfo) { this.addAbility(ability); // Whenever you lose life, draw that many cards. - this.addAbility(new VilisBrokerOfBloodTriggeredAbility()); + this.addAbility(new LoseLifeTriggeredAbility(new DrawCardSourceControllerEffect(SavedLifeLossValue.MANY))); } private VilisBrokerOfBlood(final VilisBrokerOfBlood card) { @@ -58,39 +56,3 @@ public VilisBrokerOfBlood copy() { return new VilisBrokerOfBlood(this); } } - -class VilisBrokerOfBloodTriggeredAbility extends TriggeredAbilityImpl { - - VilisBrokerOfBloodTriggeredAbility() { - super(Zone.BATTLEFIELD, null, false); - } - - private VilisBrokerOfBloodTriggeredAbility(final VilisBrokerOfBloodTriggeredAbility ability) { - super(ability); - } - - @Override - public VilisBrokerOfBloodTriggeredAbility copy() { - return new VilisBrokerOfBloodTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.LOST_LIFE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getPlayerId().equals(this.getControllerId())) { - this.getEffects().clear(); - this.addEffect(new DrawCardSourceControllerEffect(event.getAmount())); - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever you lose life, draw that many cards."; - } -} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java index 0e6f30434602..e8fdce8dc5ea 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java @@ -283,6 +283,7 @@ private DuskmournHouseOfHorrorCommander() { cards.add(new SetCardInfo("Twilight Mire", 320, Rarity.RARE, mage.cards.t.TwilightMire.class)); cards.add(new SetCardInfo("Underground River", 321, Rarity.RARE, mage.cards.u.UndergroundRiver.class)); cards.add(new SetCardInfo("Utter End", 91, Rarity.RARE, mage.cards.u.UtterEnd.class)); + cards.add(new SetCardInfo("Valgavoth, Harrower of Souls", 6, Rarity.MYTHIC, mage.cards.v.ValgavothHarrowerOfSouls.class)); cards.add(new SetCardInfo("Vault of Whispers", 322, Rarity.COMMON, mage.cards.v.VaultOfWhispers.class)); cards.add(new SetCardInfo("Verge Rangers", 108, Rarity.RARE, mage.cards.v.VergeRangers.class)); cards.add(new SetCardInfo("Vial Smasher the Fierce", 239, Rarity.MYTHIC, mage.cards.v.VialSmasherTheFierce.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/watchers/GontisMachinationsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/aer/GontisMachinationsTest.java similarity index 89% rename from Mage.Tests/src/test/java/org/mage/test/cards/watchers/GontisMachinationsTest.java rename to Mage.Tests/src/test/java/org/mage/test/cards/single/aer/GontisMachinationsTest.java index 26cc85ba36d1..e74abeb44e48 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/watchers/GontisMachinationsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/aer/GontisMachinationsTest.java @@ -1,4 +1,4 @@ -package org.mage.test.cards.watchers; +package org.mage.test.cards.single.aer; import mage.constants.PhaseStep; import mage.constants.Zone; @@ -7,7 +7,6 @@ import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author escplan9 */ public class GontisMachinationsTest extends CardTestPlayerBase { @@ -19,57 +18,59 @@ public class GontisMachinationsTest extends CardTestPlayerBase { Pay {E}{E}, Sacrifice Gonti's Machinations: Each opponent loses 3 life. You gain life equal to the life lost this way. */ private final String gMachinations = "Gonti's Machinations"; - + /* * Reported bug: [[Gonti's Machinations]] currently triggers and gain 1 energy whenever you lose life instead of only the first life loss of each turn. - * See issue #3499 (test is currently failing due to bug in code) - */ + * See issue #3499 for context + */ @Test public void machinations_ThreeCreaturesCombatDamage_OneTrigger() { - + setStrictChooseMode(true); + String memnite = "Memnite"; // {0} 1/1 String gBears = "Grizzly Bears"; // {1}{G} 2/2 String hGiant = "Hill Giant"; // {2}{R} 3/3 - + addCard(Zone.BATTLEFIELD, playerB, gMachinations); addCard(Zone.BATTLEFIELD, playerA, memnite); addCard(Zone.BATTLEFIELD, playerA, gBears); addCard(Zone.BATTLEFIELD, playerA, hGiant); - + attack(3, playerA, memnite); attack(3, playerA, gBears); attack(3, playerA, hGiant); - + setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); execute(); - + assertTapped(memnite, true); assertTapped(gBears, true); assertTapped(hGiant, true); assertLife(playerB, 14); // 1 + 2 + 3 damage assertCounterCount(playerB, CounterType.ENERGY, 1); } - + /* * Reported bug: [[Gonti's Machinations]] currently triggers and gain 1 energy whenever you lose life instead of only the first life loss of each turn. - * See issue #3499 (test is currently failing due to bug in code) - */ + * See issue #3499 for context + */ @Test public void machinations_NonCombatDamageThreeTimes_OneTrigger() { - + setStrictChooseMode(true); + String bolt = "Lightning Bolt"; // {R} deal 3 - + addCard(Zone.BATTLEFIELD, playerB, gMachinations); addCard(Zone.HAND, playerA, bolt, 3); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bolt, playerB); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bolt, playerB); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bolt, playerB); - + setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - + assertTappedCount("Mountain", true, 3); assertGraveyardCount(playerA, bolt, 3); assertLife(playerB, 11); // 3 x 3 damage diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/avr/ExquisiteBloodTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/avr/ExquisiteBloodTest.java index db6b4e2b1b2f..036ee0f44418 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/avr/ExquisiteBloodTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/avr/ExquisiteBloodTest.java @@ -7,13 +7,14 @@ import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author noxx */ public class ExquisiteBloodTest extends CardTestPlayerBase { @Test - public void BasicCardTest() { + public void basicCardTest() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); @@ -45,10 +46,12 @@ public void BasicCardTest() { } /** - * Ajani, Inspiring leader does not trigger Exquisite Blood + Defiant Bloodlord #6464 + * Ajani, Inspiring leader does not trigger Exquisite Blood + Defiant Bloodlord #6464 */ @Test public void triggerCascadeTest() { + setStrictChooseMode(true); + // +2: You gain 2 life. Put two +1/+1 counters on up to one target creature. // −3: Exile target creature. Its controller gains 2 life. // −10: Creatures you control gain flying and double strike until end of turn. @@ -57,14 +60,12 @@ public void triggerCascadeTest() { // Flying // Whenever you gain life, target opponent loses that much life. addCard(Zone.BATTLEFIELD, playerA, "Defiant Bloodlord", 1); // Creature 4/5 {5}{B}{B} - + // Whenever an opponent loses life, you gain that much life. addCard(Zone.BATTLEFIELD, playerA, "Exquisite Blood", 1); // Enchantment {4}{B} activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+2:", "Defiant Bloodlord"); addTarget(playerA, playerB); // Target opponent of Defiant Bloodlord triggered ability (looping until opponent is dead) - addTarget(playerA, playerB); - addTarget(playerA, playerB); addTarget(playerA, playerB); addTarget(playerA, playerB); addTarget(playerA, playerB); @@ -72,7 +73,9 @@ public void triggerCascadeTest() { addTarget(playerA, playerB); addTarget(playerA, playerB); addTarget(playerA, playerB); - + addTarget(playerA, playerB); + addTarget(playerA, playerB); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -82,15 +85,17 @@ public void triggerCascadeTest() { assertLife(playerB, 0); // Player B is dead, game ends assertLife(playerA, 40); - - + + } /** - * Ajani, Inspiring leader does not trigger Exquisite Blood + Defiant Bloodlord #6464 + * Ajani, Inspiring leader does not trigger Exquisite Blood + Defiant Bloodlord #6464 */ @Test public void triggerCascadeAjaniSecondAbilityTest() { + setStrictChooseMode(true); + // +2: You gain 2 life. Put two +1/+1 counters on up to one target creature. // −3: Exile target creature. Its controller gains 2 life. // −10: Creatures you control gain flying and double strike until end of turn. @@ -100,14 +105,12 @@ public void triggerCascadeAjaniSecondAbilityTest() { // Flying // Whenever you gain life, target opponent loses that much life. addCard(Zone.BATTLEFIELD, playerA, "Defiant Bloodlord", 1); // Creature 4/5 {5}{B}{B} - + // Whenever an opponent loses life, you gain that much life. addCard(Zone.BATTLEFIELD, playerA, "Exquisite Blood", 1); // Enchantment {4}{B} activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-3:", "Silvercoat Lion"); addTarget(playerA, playerB); // Target opponent of Defiant Bloodlord triggered ability (looping until opponent is dead) - addTarget(playerA, playerB); - addTarget(playerA, playerB); addTarget(playerA, playerB); addTarget(playerA, playerB); addTarget(playerA, playerB); @@ -115,7 +118,9 @@ public void triggerCascadeAjaniSecondAbilityTest() { addTarget(playerA, playerB); addTarget(playerA, playerB); addTarget(playerA, playerB); - + addTarget(playerA, playerB); + addTarget(playerA, playerB); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -126,7 +131,28 @@ public void triggerCascadeAjaniSecondAbilityTest() { assertLife(playerB, 0); // Player B is dead, game ends assertLife(playerA, 40); - - + + + } + + @Test + public void attackWithTwoCreatures() { + setStrictChooseMode(true); + + // Whenever an opponent loses life, you gain that much life. + addCard(Zone.BATTLEFIELD, playerA, "Exquisite Blood", 1); + addCard(Zone.BATTLEFIELD, playerA, "Elite Vanguard"); + addCard(Zone.BATTLEFIELD, playerA, "Memnite"); + + attack(1, playerA, "Elite Vanguard", playerB); + attack(1, playerA, "Memnite", playerB); + + // no trigger stacking, only 1 trigger + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 2 - 1); + assertLife(playerA, 20 + 3); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ice/OathOfLimDulTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ice/OathOfLimDulTest.java new file mode 100644 index 000000000000..f71233c32be1 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ice/OathOfLimDulTest.java @@ -0,0 +1,116 @@ +package org.mage.test.cards.single.ice; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class OathOfLimDulTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.o.OathOfLimDul Oath of Lim-Dûl} {3}{B} + * Enchantment + * Whenever you lose life, for each 1 life you lost, sacrifice a permanent other than Oath of Lim-Dûl unless you discard a card. (Damage dealt to you causes you to lose life.) + * {B}{B}: Draw a card. + */ + private static final String oath = "Oath of Lim-Dul"; + + @Test + public void test_3Sacrifice() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, oath, 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + addCard(Zone.HAND, playerA, "Swamp", 5); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + setChoice(playerA, false); // No to discard on first instance. + setChoice(playerA, "Mountain"); // sacrifice Mountain + setChoice(playerA, false); // No to discard on second instance. + setChoice(playerA, "Mountain"); // sacrifice Mountain + setChoice(playerA, false); // No to discard on third instance. + setChoice(playerA, "Mountain"); // sacrifice Mountain + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerA, 20 - 3); + assertGraveyardCount(playerA, "Mountain", 3); + } + + @Test + public void test_3Discard() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, oath, 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + addCard(Zone.HAND, playerA, "Swamp", 5); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + setChoice(playerA, true); // Yes to discard on first instance. + setChoice(playerA, "Swamp"); // sacrifice Swamp + setChoice(playerA, true); // Yes to discard on second instance. + setChoice(playerA, "Swamp"); // sacrifice Swamp + setChoice(playerA, true); // Yes to discard on third instance. + setChoice(playerA, "Swamp"); // sacrifice Swamp + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerA, 20 - 3); + assertGraveyardCount(playerA, "Swamp", 3); + } + + @Test + public void test_1Sacrifice1Discard_NoOther() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, oath, 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.HAND, playerA, "Swamp", 1); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + setChoice(playerA, true); // Yes to discard on first instance. + setChoice(playerA, "Swamp"); // discard Swamp + // No more possibility to Discard + setChoice(playerA, "Mountain"); // sacrifice Mountain + // No more things to Sacrifice + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerA, 20 - 3); + assertGraveyardCount(playerA, "Mountain", 1); + assertGraveyardCount(playerA, "Swamp", 1); + } + + @Test + public void test_AllSacrificeNoDiscard_KeepCardInHand() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, oath, 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.HAND, playerA, "Swamp", 1); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + setChoice(playerA, false); // No to discard on first instance. + setChoice(playerA, "Mountain"); // sacrifice Mountain + setChoice(playerA, false); // No to discard on second instance. + setChoice(playerA, false); // No to discard on third instance. + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerA, 20 - 3); + assertPermanentCount(playerA, oath, 1); + assertGraveyardCount(playerA, "Mountain", 1); + assertHandCount(playerA, "Swamp", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/snc/VampireScrivenerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/snc/VampireScrivenerTest.java new file mode 100644 index 000000000000..ec5b050ce86a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/snc/VampireScrivenerTest.java @@ -0,0 +1,79 @@ +package org.mage.test.cards.single.snc; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class VampireScrivenerTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.v.VampireScrivener Vampire Scrivener} {4}{B} + * Creature — Vampire Warlock + * Flying + * Whenever you gain life during your turn, put a +1/+1 counter on Vampire Scrivener. + * Whenever you lose life during your turn, put a +1/+1 counter on Vampire Scrivener. + * 2/2 + */ + private static final String scrivener = "Vampire Scrivener"; + + @Test + public void test_LoseLife_Twice() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, scrivener, 1); + addCard(Zone.BATTLEFIELD, playerA, "Battlefield Forge"); // painland + addCard(Zone.HAND, playerA, "Lightning Bolt"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); // cause 1 trigger + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); // cause 1 trigger + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerA, 20 - 3 - 1); + assertCounterCount(playerA, scrivener, CounterType.P1P1, 2); + } + + @Test + public void test_RakdosCharm() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, scrivener, 1); + addCard(Zone.BATTLEFIELD, playerA, "Kobolds of Kher Keep", 3); + addCard(Zone.BATTLEFIELD, playerA, "Badlands", 2); + addCard(Zone.HAND, playerA, "Rakdos Charm"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rakdos Charm"); + setModeChoice(playerA, "3"); // Choose third mode + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerA, 20 - 4); + assertCounterCount(playerA, scrivener, CounterType.P1P1, 1); + } + + @Test + public void test_RakdosCharm_NotYourTurn() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, scrivener, 1); + addCard(Zone.BATTLEFIELD, playerA, "Kobolds of Kher Keep", 3); + addCard(Zone.BATTLEFIELD, playerA, "Badlands", 2); + addCard(Zone.HAND, playerA, "Rakdos Charm"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Rakdos Charm"); + setModeChoice(playerA, "3"); // Choose third mode + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerA, 20 - 4); + assertCounterCount(playerA, scrivener, CounterType.P1P1, 0); // No trigger, as not your turn. + } +} diff --git a/Mage/src/main/java/mage/abilities/common/GainLoseLifeYourTurnTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/GainLoseLifeYourTurnTriggeredAbility.java index ecab496079d1..1a366c33282b 100644 --- a/Mage/src/main/java/mage/abilities/common/GainLoseLifeYourTurnTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/GainLoseLifeYourTurnTriggeredAbility.java @@ -28,12 +28,12 @@ public GainLoseLifeYourTurnTriggeredAbility copy() { @Override public boolean checkEventType(GameEvent event, Game game) { return event.getType() == GameEvent.EventType.GAINED_LIFE - || event.getType() == GameEvent.EventType.LOST_LIFE; + || event.getType() == GameEvent.EventType.LOST_LIFE_BATCH_FOR_ONE_PLAYER; } @Override public boolean checkTrigger(GameEvent event, Game game) { return isControlledBy(game.getActivePlayerId()) - && isControlledBy(event.getPlayerId()); + && isControlledBy(event.getTargetId()); } } diff --git a/Mage/src/main/java/mage/abilities/common/LoseLifeFirstTimeEachTurnTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/LoseLifeFirstTimeEachTurnTriggeredAbility.java new file mode 100644 index 000000000000..4359304f4e55 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/LoseLifeFirstTimeEachTurnTriggeredAbility.java @@ -0,0 +1,51 @@ +package mage.abilities.common; + +import mage.abilities.effects.Effect; +import mage.constants.TargetController; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.watchers.common.LifeLostThisTurnWatcher; + +/** + * @author Susucr + */ +public class LoseLifeFirstTimeEachTurnTriggeredAbility extends LoseLifeTriggeredAbility { + + public LoseLifeFirstTimeEachTurnTriggeredAbility(Effect effect) { + this(effect, TargetController.YOU, false, false); + } + + public LoseLifeFirstTimeEachTurnTriggeredAbility(Effect effect, TargetController targetController, boolean optional, boolean setTargetPointer) { + super(effect, targetController, optional, setTargetPointer); + addWatcher(new LifeLostThisTurnWatcher()); + } + + protected LoseLifeFirstTimeEachTurnTriggeredAbility(final LoseLifeFirstTimeEachTurnTriggeredAbility ability) { + super(ability); + } + + @Override + public LoseLifeFirstTimeEachTurnTriggeredAbility copy() { + return new LoseLifeFirstTimeEachTurnTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + LifeLostThisTurnWatcher watcher = game.getState().getWatcher(LifeLostThisTurnWatcher.class); + return watcher != null + && watcher.timesLostLifeThisTurn(event.getTargetId()) <= 1 + && super.checkTrigger(event, game); + } + + @Override + protected String generateTriggerPhrase() { + switch (targetController) { + case YOU: + return "Whenever you lose life for the first time each turn, "; + case OPPONENT: + return "Whenever an opponent loses life for the first time each turn, "; + default: + throw new IllegalArgumentException("Wrong code usage: not supported targetController: " + targetController); + } + } +} diff --git a/Mage/src/main/java/mage/abilities/common/LoseLifeTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/LoseLifeTriggeredAbility.java new file mode 100644 index 000000000000..e3b4d198b692 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/LoseLifeTriggeredAbility.java @@ -0,0 +1,85 @@ +package mage.abilities.common; + +import mage.abilities.BatchTriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.dynamicvalue.common.SavedLifeLossValue; +import mage.abilities.effects.Effect; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.LifeLostEvent; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author Susucr + */ +public class LoseLifeTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { + + protected final TargetController targetController; + private final boolean setTargetPointer; + + public LoseLifeTriggeredAbility(Effect effect) { + this(effect, TargetController.YOU, false, false); + } + + public LoseLifeTriggeredAbility(Effect effect, TargetController targetController, boolean optional, boolean setTargetPointer) { + super(Zone.BATTLEFIELD, effect, optional); + this.targetController = targetController; + this.setTargetPointer = setTargetPointer; + setTriggerPhrase(generateTriggerPhrase()); + } + + protected LoseLifeTriggeredAbility(final LoseLifeTriggeredAbility ability) { + super(ability); + this.targetController = ability.targetController; + this.setTargetPointer = ability.setTargetPointer; + } + + @Override + public LoseLifeTriggeredAbility copy() { + return new LoseLifeTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.LOST_LIFE_BATCH_FOR_ONE_PLAYER; + } + + private boolean filterPlayer(UUID playerId, Game game) { + switch (targetController) { + case YOU: + return isControlledBy(playerId); + case OPPONENT: + return game.getOpponents(getControllerId()).contains(playerId); + default: + throw new IllegalArgumentException("Wrong code usage: not supported targetController: " + targetController); + } + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!filterPlayer(event.getTargetId(), game)) { + return false; + } + // if target id matches, all events in the batch are relevant + this.getEffects().setValue(SavedLifeLossValue.getValueKey(), event.getAmount()); + if (setTargetPointer) { + this.getEffects().setTargetPointer(new FixedTarget(event.getTargetId())); + } + return true; + } + + protected String generateTriggerPhrase() { + switch (targetController) { + case YOU: + return "Whenever you lose life, "; + case OPPONENT: + return "Whenever an opponent loses life, "; + default: + throw new IllegalArgumentException("Wrong code usage: not supported targetController: " + targetController); + } + } +} diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/SavedLifeLossValue.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/SavedLifeLossValue.java new file mode 100644 index 000000000000..8df824f6664b --- /dev/null +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/SavedLifeLossValue.java @@ -0,0 +1,49 @@ +package mage.abilities.dynamicvalue.common; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.game.Game; + +/** + * @author Susucr + */ +public enum SavedLifeLossValue implements DynamicValue { + MANY("many"), + MUCH("much"); + + private final String message; + + private static final String key = "SavedLifeLoss"; + + /** + * value key used to store the amount of life lost + */ + public static String getValueKey() { + return key; + } + + SavedLifeLossValue(String message) { + this.message = "that " + message; + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return (Integer) effect.getValue(getValueKey()); + } + + @Override + public SavedLifeLossValue copy() { + return this; + } + + @Override + public String toString() { + return message; + } + + @Override + public String getMessage() { + return ""; + } +} diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index 702ff0efe0b8..e0c3d41d452b 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -975,12 +975,17 @@ public void addSimultaneousLifeLossToBatch(LifeLostEvent lifeLossEvent, Game gam // Combine multiple life loss events in the single event (batch) // see GameEvent.LOST_LIFE_BATCH - // existing batch + // existing batchs boolean isLifeLostBatchUsed = false; + boolean isSingleBatchUsed = false; for (GameEvent event : simultaneousEvents) { if (event instanceof LifeLostBatchEvent) { ((LifeLostBatchEvent) event).addEvent(lifeLossEvent); isLifeLostBatchUsed = true; + } else if (event instanceof LifeLostBatchForOnePlayerEvent + && event.getTargetId().equals(lifeLossEvent.getTargetId())) { + ((LifeLostBatchForOnePlayerEvent) event).addEvent(lifeLossEvent); + isSingleBatchUsed = true; } } @@ -988,6 +993,9 @@ public void addSimultaneousLifeLossToBatch(LifeLostEvent lifeLossEvent, Game gam if (!isLifeLostBatchUsed) { addSimultaneousEvent(new LifeLostBatchEvent(lifeLossEvent), game); } + if (!isSingleBatchUsed) { + addSimultaneousEvent(new LifeLostBatchForOnePlayerEvent(lifeLossEvent), game); + } } public void addSimultaneousTappedToBatch(TappedEvent tappedEvent, Game game) { diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index 55103889cd4b..90a34cf5ee37 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -166,7 +166,6 @@ combine all damage events from a single source to a single batch (event) DAMAGE_CAUSES_LIFE_LOSS, PLAYER_LIFE_CHANGE, GAIN_LIFE, GAINED_LIFE, - LOSE_LIFE, LOST_LIFE, /* LOSE_LIFE + LOST_LIFE targetId the id of the player loosing life sourceId sourceId of the ability which caused the lose @@ -174,10 +173,17 @@ combine all damage events from a single source to a single batch (event) amount amount of life loss flag true = from combat damage - other from non combat damage */ - LOST_LIFE_BATCH(true), + + LOSE_LIFE, LOST_LIFE, + /* LOST_LIFE_BATCH_FOR_ONE_PLAYER + combines all life lost events for a player to a single batch (event) + */ + LOST_LIFE_BATCH_FOR_ONE_PLAYER(true), /* LOST_LIFE_BATCH combines all player life lost events to a single batch (event) */ + LOST_LIFE_BATCH(true), + PLAY_LAND, LAND_PLAYED, CREATURE_CHAMPIONED, /* CREATURE_CHAMPIONED diff --git a/Mage/src/main/java/mage/game/events/LifeLostBatchForOnePlayerEvent.java b/Mage/src/main/java/mage/game/events/LifeLostBatchForOnePlayerEvent.java new file mode 100644 index 000000000000..fe521928a6d2 --- /dev/null +++ b/Mage/src/main/java/mage/game/events/LifeLostBatchForOnePlayerEvent.java @@ -0,0 +1,11 @@ +package mage.game.events; + +/** + * @author Susucr + */ +public class LifeLostBatchForOnePlayerEvent extends BatchEvent { + + public LifeLostBatchForOnePlayerEvent(LifeLostEvent firstEvent) { + super(EventType.LOST_LIFE_BATCH_FOR_ONE_PLAYER, true, false, false, firstEvent); + } +} diff --git a/Mage/src/main/java/mage/watchers/common/LifeLostThisTurnWatcher.java b/Mage/src/main/java/mage/watchers/common/LifeLostThisTurnWatcher.java new file mode 100644 index 000000000000..2f040b1628c2 --- /dev/null +++ b/Mage/src/main/java/mage/watchers/common/LifeLostThisTurnWatcher.java @@ -0,0 +1,42 @@ +package mage.watchers.common; + +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.util.CardUtil; +import mage.watchers.Watcher; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author Susucr + */ +public class LifeLostThisTurnWatcher extends Watcher { + + // player -> number of times (not amount!) that player lost life this turn. + private final Map playersLostLife = new HashMap<>(); + + public LifeLostThisTurnWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.LOST_LIFE_BATCH_FOR_ONE_PLAYER) { + playersLostLife.compute(event.getTargetId(), CardUtil::setOrIncrementValue); + } + } + + + @Override + public void reset() { + super.reset(); + playersLostLife.clear(); + } + + public int timesLostLifeThisTurn(UUID playerId) { + return playersLostLife.getOrDefault(playerId, 0); + } +}