Introduce new batch event for life lost for a specific player (#13071)

* Introduce new batch event for life lost for a specific player

closes #12202, fix #10805

* implement [DSC] Valgavoth, Harrower of Souls

* text fixes
This commit is contained in:
xenohedron 2024-11-19 23:41:34 -05:00 committed by GitHub
parent 95e986dee7
commit d6cf207a8b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 693 additions and 707 deletions

View file

@ -1,8 +1,8 @@
package mage.cards.b; package mage.cards.b;
import mage.MageInt; import mage.MageInt;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.LoseLifeTriggeredAbility;
import mage.abilities.dynamicvalue.common.SavedGainedLifeValue; import mage.abilities.dynamicvalue.common.SavedLifeLossValue;
import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.keyword.DeathtouchAbility; import mage.abilities.keyword.DeathtouchAbility;
import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.FlyingAbility;
@ -10,9 +10,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.Zone; import mage.constants.TargetController;
import mage.game.Game;
import mage.game.events.GameEvent;
import java.util.UUID; import java.util.UUID;
@ -36,7 +34,8 @@ public final class BloodthirstyConqueror extends CardImpl {
this.addAbility(DeathtouchAbility.getInstance()); this.addAbility(DeathtouchAbility.getInstance());
// Whenever an opponent loses life, you gain that much life. // 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) { private BloodthirstyConqueror(final BloodthirstyConqueror card) {
@ -48,34 +47,3 @@ public final class BloodthirstyConqueror extends CardImpl {
return new BloodthirstyConqueror(this); 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;
}
}

View file

@ -41,7 +41,7 @@ public final class CuratorBeastie extends CardImpl {
// Colorless creatures you control enter with two additional +1/+1 counters on them. // Colorless creatures you control enter with two additional +1/+1 counters on them.
this.addAbility(new SimpleStaticAbility(new EntersWithCountersControlledEffect( this.addAbility(new SimpleStaticAbility(new EntersWithCountersControlledEffect(
filter, CounterType.P1P1.createInstance(2), false 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. // Whenever Curator Beastie enters or attacks, manifest dread.
this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new ManifestDreadEffect())); this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new ManifestDreadEffect()));

View file

@ -1,16 +1,14 @@
package mage.cards.e; package mage.cards.e;
import java.util.UUID; import mage.abilities.common.LoseLifeTriggeredAbility;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.dynamicvalue.common.SavedLifeLossValue;
import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.GainLifeEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Zone; import mage.constants.TargetController;
import mage.game.Game;
import mage.game.events.GameEvent; import java.util.UUID;
import mage.game.events.GameEvent.EventType;
/** /**
* @author noxx * @author noxx
@ -20,10 +18,9 @@ public final class ExquisiteBlood extends CardImpl {
public ExquisiteBlood(UUID ownerId, CardSetInfo setInfo) { 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. // Whenever an opponent loses life, you gain that much life.
ExquisiteBloodTriggeredAbility ability = new ExquisiteBloodTriggeredAbility(); this.addAbility(new LoseLifeTriggeredAbility(new GainLifeEffect(SavedLifeLossValue.MUCH),
this.addAbility(ability); TargetController.OPPONENT, false, false));
} }
private ExquisiteBlood(final ExquisiteBlood card) { private ExquisiteBlood(final ExquisiteBlood card) {
@ -35,39 +32,3 @@ public final class ExquisiteBlood extends CardImpl {
return new ExquisiteBlood(this); 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.";
}
}

View file

@ -46,7 +46,8 @@ public final class GlitchInterpreter extends CardImpl {
this.toughness = new MageInt(3); 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. // 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")); ability.addEffect(new ManifestDreadEffect().concatBy("and"));
this.addAbility(ability); this.addAbility(ability);

View file

@ -1,7 +1,7 @@
package mage.cards.g; package mage.cards.g;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.LoseLifeFirstTimeEachTurnTriggeredAbility;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.PayEnergyCost; import mage.abilities.costs.common.PayEnergyCost;
import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.common.SacrificeSourceCost;
@ -10,14 +10,7 @@ import mage.abilities.effects.common.counter.GetEnergyCountersControllerEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.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; import java.util.UUID;
/** /**
@ -29,8 +22,8 @@ public final class GontisMachinations extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{B}"); super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{B}");
// Whenever you lose life for the first time each turn, you get {E}. // Whenever you lose life for the first time each turn, you get {E}.
this.addAbility(new GontisMachinationsTriggeredAbility(), this.addAbility(new LoseLifeFirstTimeEachTurnTriggeredAbility(
new GontisMachinationsFirstLostLifeThisTurnWatcher()); 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. // 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( Ability ability = new SimpleActivatedAbility(
@ -50,68 +43,3 @@ public final class GontisMachinations extends CardImpl {
return new GontisMachinations(this); 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<UUID, Integer> 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);
}
}

View file

@ -1,12 +1,12 @@
package mage.cards.l; package mage.cards.l;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.GainLifeControllerTriggeredAbility; import mage.abilities.common.GainLifeControllerTriggeredAbility;
import mage.abilities.common.LeavesBattlefieldTriggeredAbility; import mage.abilities.common.LeavesBattlefieldTriggeredAbility;
import mage.abilities.common.LoseLifeTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.common.SavedGainedLifeValue; import mage.abilities.dynamicvalue.common.SavedGainedLifeValue;
import mage.abilities.dynamicvalue.common.SavedLifeLossValue;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
@ -56,7 +56,7 @@ public final class LichsMastery extends CardImpl {
)); ));
// Whenever you lose life, for each 1 life you lost, exile a permanent you control or a card from your hand or graveyard. // 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. // When Lich's Mastery leaves the battlefield, you lose the game.
this.addAbility(new LeavesBattlefieldTriggeredAbility(new LoseGameSourceControllerEffect(), false)); this.addAbility(new LeavesBattlefieldTriggeredAbility(new LoseGameSourceControllerEffect(), false));
@ -99,57 +99,15 @@ class LichsMasteryCantLoseEffect extends ContinuousRuleModifyingEffectImpl {
} }
} }
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 { class LichsMasteryLoseLifeEffect extends OneShotEffect {
private int amount = 0; LichsMasteryLoseLifeEffect() {
public LichsMasteryLoseLifeEffect() {
super(Outcome.Exile); 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) { private LichsMasteryLoseLifeEffect(final LichsMasteryLoseLifeEffect effect) {
super(effect); super(effect);
this.amount = effect.amount;
} }
@Override @Override
@ -165,7 +123,7 @@ class LichsMasteryLoseLifeEffect extends OneShotEffect {
} }
FilterPermanent filter = new FilterPermanent(); FilterPermanent filter = new FilterPermanent();
filter.add(new ControllerIdPredicate(controller.getId())); 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 handCount = controller.getHand().size();
int graveCount = controller.getGraveyard().size(); int graveCount = controller.getGraveyard().size();
int permCount = game.getBattlefield().getActivePermanents(filter, controller.getId(), game).size(); int permCount = game.getBattlefield().getActivePermanents(filter, controller.getId(), game).size();
@ -182,7 +140,7 @@ class LichsMasteryLoseLifeEffect extends OneShotEffect {
if (card != null) { if (card != null) {
controller.moveCards(card, Zone.EXILED, source, game); controller.moveCards(card, Zone.EXILED, source, game);
} }
} else if (graveCount > 0) { } else {
Target target = new TargetCardInYourGraveyard(1, 1, new FilterCard(), true); Target target = new TargetCardInYourGraveyard(1, 1, new FilterCard(), true);
target.choose(Outcome.Exile, source.getControllerId(), source.getSourceId(), source, game); target.choose(Outcome.Exile, source.getControllerId(), source.getSourceId(), source, game);
Card card = controller.getGraveyard().get(target.getFirstTarget(), game); Card card = controller.getGraveyard().get(target.getFirstTarget(), game);
@ -194,7 +152,4 @@ class LichsMasteryLoseLifeEffect extends OneShotEffect {
return true; return true;
} }
public void setAmount(int amount) {
this.amount = amount;
}
} }

View file

@ -1,25 +1,19 @@
package mage.cards.l; package mage.cards.l;
import java.util.UUID; import mage.abilities.common.LoseLifeTriggeredAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleStaticAbility; 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.SacrificeControllerEffect;
import mage.abilities.effects.common.SacrificeEffect;
import mage.abilities.effects.common.continuous.DontLoseByZeroOrLessLifeEffect; import mage.abilities.effects.common.continuous.DontLoseByZeroOrLessLifeEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.Zone; import mage.filter.StaticFilters;
import mage.filter.FilterPermanent;
import mage.game.Game; import java.util.UUID;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
/** /**
*
* @author emerald000 * @author emerald000
*/ */
public final class LichsTomb extends CardImpl { public final class LichsTomb extends CardImpl {
@ -31,7 +25,9 @@ public final class LichsTomb extends CardImpl {
this.addAbility(new SimpleStaticAbility(new DontLoseByZeroOrLessLifeEffect(Duration.WhileOnBattlefield))); this.addAbility(new SimpleStaticAbility(new DontLoseByZeroOrLessLifeEffect(Duration.WhileOnBattlefield)));
// Whenever you lose life, sacrifice a permanent for each 1 life you lost. // 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) { private LichsTomb(final LichsTomb card) {
@ -43,38 +39,3 @@ public final class LichsTomb extends CardImpl {
return new LichsTomb(this); 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.";
}
}

View file

@ -1,16 +1,16 @@
package mage.cards.m; package mage.cards.m;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.GainLifeControllerTriggeredAbility; import mage.abilities.common.GainLifeControllerTriggeredAbility;
import mage.abilities.common.LoseLifeTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.common.CastFromEverywhereSourceCondition; import mage.abilities.condition.common.CastFromEverywhereSourceCondition;
import mage.abilities.condition.common.HellbentCondition; import mage.abilities.condition.common.HellbentCondition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.dynamicvalue.common.SavedDamageValue;
import mage.abilities.dynamicvalue.common.SavedGainedLifeValue; import mage.abilities.dynamicvalue.common.SavedGainedLifeValue;
import mage.abilities.dynamicvalue.common.SavedLifeLossValue;
import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.LoseGameSourceControllerEffect; import mage.abilities.effects.common.LoseGameSourceControllerEffect;
import mage.abilities.effects.common.continuous.DontLoseByZeroOrLessLifeEffect; import mage.abilities.effects.common.continuous.DontLoseByZeroOrLessLifeEffect;
@ -21,9 +21,6 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.SuperType; import mage.constants.SuperType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import java.util.UUID; import java.util.UUID;
@ -58,7 +55,12 @@ public final class MarinaVendrellsGrimoire extends CardImpl {
)); ));
// Whenever you lose life, discard that many cards. Then if you have no cards in hand, you lose the game. // 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) { private MarinaVendrellsGrimoire(final MarinaVendrellsGrimoire card) {
@ -70,38 +72,3 @@ public final class MarinaVendrellsGrimoire extends CardImpl {
return new MarinaVendrellsGrimoire(this); 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;
}
}

View file

@ -1,21 +1,13 @@
package mage.cards.m; package mage.cards.m;
import mage.abilities.Ability; import mage.abilities.common.LoseLifeTriggeredAbility;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.dynamicvalue.common.SavedLifeLossValue;
import mage.abilities.effects.Effect; import mage.abilities.effects.common.MillCardsTargetEffect;
import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.TargetController;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
/** /**
@ -26,9 +18,11 @@ public final class Mindcrank extends CardImpl {
public Mindcrank(UUID ownerId, CardSetInfo setInfo) { public Mindcrank(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); 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. // Whenever an opponent loses life, that player mills that many cards.
// (Damage dealt by sources without infect causes loss of life.) this.addAbility(new LoseLifeTriggeredAbility(
this.addAbility(new MindcrankTriggeredAbility()); new MillCardsTargetEffect(SavedLifeLossValue.MANY),
TargetController.OPPONENT, false, true
));
} }
private Mindcrank(final Mindcrank card) { private Mindcrank(final Mindcrank card) {
@ -40,71 +34,3 @@ public final class Mindcrank extends CardImpl {
return new Mindcrank(this); 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<UUID> 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;
}
}

View file

@ -1,31 +1,28 @@
package mage.cards.o; package mage.cards.o;
import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.LoseLifeTriggeredAbility;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.Cost; import mage.abilities.costs.common.DiscardCardCost;
import mage.abilities.costs.common.DiscardTargetCost;
import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.common.SavedLifeLossValue;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DoUnlessControllerPaysEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.SacrificeControllerEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone; import mage.filter.StaticFilters;
import mage.filter.FilterCard;
import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.mageobject.AnotherPredicate; import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.common.TargetCardInHand;
import mage.target.common.TargetControlledPermanent; import java.util.UUID;
/** /**
*
* @author jeffwadsworth * @author jeffwadsworth
*/ */
public final class OathOfLimDul extends CardImpl { public final class OathOfLimDul extends CardImpl {
@ -34,7 +31,7 @@ public final class OathOfLimDul extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{B}"); 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. // 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. // {B}{B}: Draw a card.
this.addAbility(new SimpleActivatedAbility(new DrawCardSourceControllerEffect(1), new ManaCostsImpl<>("{B}{B}"))); this.addAbility(new SimpleActivatedAbility(new DrawCardSourceControllerEffect(1), new ManaCostsImpl<>("{B}{B}")));
@ -51,51 +48,17 @@ public final class OathOfLimDul extends CardImpl {
} }
} }
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 { 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 { static {
filter.add(AnotherPredicate.instance); filter.add(AnotherPredicate.instance);
} }
public OathOfLimDulEffect() { OathOfLimDulEffect() {
super(Outcome.Neutral); 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) { private OathOfLimDulEffect(final OathOfLimDulEffect effect) {
@ -104,34 +67,19 @@ class OathOfLimDulEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
boolean sacrificeDone = false; int amountDamage = SavedLifeLossValue.MANY.calculate(game, source, this);
int numberSacrificed = 0;
int numberToDiscard = 0;
int numberOfControlledPermanents = 0;
Player controller = game.getPlayer(source.getControllerId()); Player controller = game.getPlayer(source.getControllerId());
int amountDamage = (int) game.getState().getValue(source.getSourceId().toString() + "oathOfLimDul"); if (amountDamage <= 0 || controller == null) {
if (amountDamage > 0 return false;
&& 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;
} }
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 didSomething;
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
}
}
return sacrificeDone;
} }
@Override @Override

View file

@ -1,32 +1,32 @@
package mage.cards.t; package mage.cards.t;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.StateTriggeredAbility; import mage.abilities.StateTriggeredAbility;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.LoseLifeTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.Effect; import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.OneShotEffect; 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.LoseGameSourceControllerEffect;
import mage.abilities.effects.common.continuous.DontLoseByZeroOrLessLifeEffect; import mage.abilities.effects.common.continuous.DontLoseByZeroOrLessLifeEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone; import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.players.Player; import mage.players.Player;
import java.util.UUID;
/** /**
*
* @author emerald000 * @author emerald000
*/ */
public final class Transcendence extends CardImpl { public final class Transcendence extends CardImpl {
private static final DynamicValue value = new MultipliedValue(SavedLifeLossValue.MUCH, 2);
public Transcendence(UUID ownerId, CardSetInfo setInfo) { 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}");
@ -37,7 +37,9 @@ public final class Transcendence extends CardImpl {
this.addAbility(new TranscendenceStateTriggeredAbility()); this.addAbility(new TranscendenceStateTriggeredAbility());
// Whenever you lose life, you gain 2 life for each 1 life you lost. // 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) { private Transcendence(final Transcendence card) {
@ -79,76 +81,3 @@ class TranscendenceStateTriggeredAbility extends StateTriggeredAbility {
return "When you have 20 or more life, you lose the game."; 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;
}
}

View file

@ -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);
}
}

View file

@ -1,8 +1,8 @@
package mage.cards.v; package mage.cards.v;
import mage.MageInt; import mage.MageInt;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.GainLifeControllerTriggeredAbility; import mage.abilities.common.GainLifeControllerTriggeredAbility;
import mage.abilities.common.LoseLifeTriggeredAbility;
import mage.abilities.condition.common.MyTurnCondition; import mage.abilities.condition.common.MyTurnCondition;
import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.decorator.ConditionalTriggeredAbility;
import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect;
@ -11,7 +11,6 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.Zone;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
@ -54,10 +53,11 @@ public final class VampireScrivener extends CardImpl {
} }
} }
class VampireScrivenerTriggeredAbility extends TriggeredAbilityImpl { class VampireScrivenerTriggeredAbility extends LoseLifeTriggeredAbility {
VampireScrivenerTriggeredAbility() { 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) { private VampireScrivenerTriggeredAbility(final VampireScrivenerTriggeredAbility ability) {
@ -69,18 +69,9 @@ class VampireScrivenerTriggeredAbility extends TriggeredAbilityImpl {
return new VampireScrivenerTriggeredAbility(this); return new VampireScrivenerTriggeredAbility(this);
} }
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.LOST_LIFE;
}
@Override @Override
public boolean checkTrigger(GameEvent event, Game game) { 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}.";
}
} }

View file

@ -1,21 +1,14 @@
package mage.cards.v; package mage.cards.v;
import mage.MageInt; import mage.MageInt;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.LoseLifeFirstTimeEachTurnTriggeredAbility;
import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.WatcherScope;
import mage.constants.Zone;
import mage.counters.CounterType; 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; import java.util.UUID;
/** /**
@ -32,7 +25,8 @@ public final class VengefulWarchief extends CardImpl {
this.toughness = new MageInt(4); this.toughness = new MageInt(4);
// Whenever you lose life for the first time each turn, put a +1/+1 counter on Vengeful Warchief. // 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) { private VengefulWarchief(final VengefulWarchief card) {
@ -44,62 +38,3 @@ public final class VengefulWarchief extends CardImpl {
return new VengefulWarchief(this); 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<UUID, Integer> 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);
}
}

View file

@ -2,10 +2,11 @@ package mage.cards.v;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.LoseLifeTriggeredAbility;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.PayLifeCost; import mage.abilities.costs.common.PayLifeCost;
import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.common.SavedLifeLossValue;
import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.FlyingAbility;
@ -14,9 +15,6 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.SuperType; import mage.constants.SuperType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetCreaturePermanent;
import java.util.UUID; import java.util.UUID;
@ -46,7 +44,7 @@ public final class VilisBrokerOfBlood extends CardImpl {
this.addAbility(ability); this.addAbility(ability);
// Whenever you lose life, draw that many cards. // 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) { private VilisBrokerOfBlood(final VilisBrokerOfBlood card) {
@ -58,39 +56,3 @@ public final class VilisBrokerOfBlood extends CardImpl {
return new VilisBrokerOfBlood(this); 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.";
}
}

View file

@ -283,6 +283,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet {
cards.add(new SetCardInfo("Twilight Mire", 320, Rarity.RARE, mage.cards.t.TwilightMire.class)); 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("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("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("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("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)); cards.add(new SetCardInfo("Vial Smasher the Fierce", 239, Rarity.MYTHIC, mage.cards.v.VialSmasherTheFierce.class));

View file

@ -1,4 +1,4 @@
package org.mage.test.cards.watchers; package org.mage.test.cards.single.aer;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
import mage.constants.Zone; import mage.constants.Zone;
@ -7,7 +7,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase; import org.mage.test.serverside.base.CardTestPlayerBase;
/** /**
*
* @author escplan9 * @author escplan9
*/ */
public class GontisMachinationsTest extends CardTestPlayerBase { public class GontisMachinationsTest extends CardTestPlayerBase {
@ -22,10 +21,11 @@ Pay {E}{E}, Sacrifice Gonti's Machinations: Each opponent loses 3 life. You gain
/* /*
* 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. * 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 @Test
public void machinations_ThreeCreaturesCombatDamage_OneTrigger() { public void machinations_ThreeCreaturesCombatDamage_OneTrigger() {
setStrictChooseMode(true);
String memnite = "Memnite"; // {0} 1/1 String memnite = "Memnite"; // {0} 1/1
String gBears = "Grizzly Bears"; // {1}{G} 2/2 String gBears = "Grizzly Bears"; // {1}{G} 2/2
@ -52,10 +52,11 @@ Pay {E}{E}, Sacrifice Gonti's Machinations: Each opponent loses 3 life. You gain
/* /*
* 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. * 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 @Test
public void machinations_NonCombatDamageThreeTimes_OneTrigger() { public void machinations_NonCombatDamageThreeTimes_OneTrigger() {
setStrictChooseMode(true);
String bolt = "Lightning Bolt"; // {R} deal 3 String bolt = "Lightning Bolt"; // {R} deal 3

View file

@ -7,13 +7,14 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase; import org.mage.test.serverside.base.CardTestPlayerBase;
/** /**
*
* @author noxx * @author noxx
*/ */
public class ExquisiteBloodTest extends CardTestPlayerBase { public class ExquisiteBloodTest extends CardTestPlayerBase {
@Test @Test
public void BasicCardTest() { public void basicCardTest() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
@ -49,6 +50,8 @@ public class ExquisiteBloodTest extends CardTestPlayerBase {
*/ */
@Test @Test
public void triggerCascadeTest() { public void triggerCascadeTest() {
setStrictChooseMode(true);
// +2: You gain 2 life. Put two +1/+1 counters on up to one target creature. // +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. // 3: Exile target creature. Its controller gains 2 life.
// 10: Creatures you control gain flying and double strike until end of turn. // 10: Creatures you control gain flying and double strike until end of turn.
@ -91,6 +94,8 @@ public class ExquisiteBloodTest extends CardTestPlayerBase {
*/ */
@Test @Test
public void triggerCascadeAjaniSecondAbilityTest() { public void triggerCascadeAjaniSecondAbilityTest() {
setStrictChooseMode(true);
// +2: You gain 2 life. Put two +1/+1 counters on up to one target creature. // +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. // 3: Exile target creature. Its controller gains 2 life.
// 10: Creatures you control gain flying and double strike until end of turn. // 10: Creatures you control gain flying and double strike until end of turn.
@ -129,4 +134,25 @@ public class ExquisiteBloodTest extends CardTestPlayerBase {
} }
@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);
}
} }

View file

@ -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);
}
}

View file

@ -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.
}
}

View file

@ -28,12 +28,12 @@ public class GainLoseLifeYourTurnTriggeredAbility extends TriggeredAbilityImpl {
@Override @Override
public boolean checkEventType(GameEvent event, Game game) { public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.GAINED_LIFE return event.getType() == GameEvent.EventType.GAINED_LIFE
|| event.getType() == GameEvent.EventType.LOST_LIFE; || event.getType() == GameEvent.EventType.LOST_LIFE_BATCH_FOR_ONE_PLAYER;
} }
@Override @Override
public boolean checkTrigger(GameEvent event, Game game) { public boolean checkTrigger(GameEvent event, Game game) {
return isControlledBy(game.getActivePlayerId()) return isControlledBy(game.getActivePlayerId())
&& isControlledBy(event.getPlayerId()); && isControlledBy(event.getTargetId());
} }
} }

View file

@ -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);
}
}
}

View file

@ -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<LifeLostEvent> {
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);
}
}
}

View file

@ -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 "";
}
}

View file

@ -975,12 +975,17 @@ public class GameState implements Serializable, Copyable<GameState> {
// Combine multiple life loss events in the single event (batch) // Combine multiple life loss events in the single event (batch)
// see GameEvent.LOST_LIFE_BATCH // see GameEvent.LOST_LIFE_BATCH
// existing batch // existing batchs
boolean isLifeLostBatchUsed = false; boolean isLifeLostBatchUsed = false;
boolean isSingleBatchUsed = false;
for (GameEvent event : simultaneousEvents) { for (GameEvent event : simultaneousEvents) {
if (event instanceof LifeLostBatchEvent) { if (event instanceof LifeLostBatchEvent) {
((LifeLostBatchEvent) event).addEvent(lifeLossEvent); ((LifeLostBatchEvent) event).addEvent(lifeLossEvent);
isLifeLostBatchUsed = true; isLifeLostBatchUsed = true;
} else if (event instanceof LifeLostBatchForOnePlayerEvent
&& event.getTargetId().equals(lifeLossEvent.getTargetId())) {
((LifeLostBatchForOnePlayerEvent) event).addEvent(lifeLossEvent);
isSingleBatchUsed = true;
} }
} }
@ -988,6 +993,9 @@ public class GameState implements Serializable, Copyable<GameState> {
if (!isLifeLostBatchUsed) { if (!isLifeLostBatchUsed) {
addSimultaneousEvent(new LifeLostBatchEvent(lifeLossEvent), game); addSimultaneousEvent(new LifeLostBatchEvent(lifeLossEvent), game);
} }
if (!isSingleBatchUsed) {
addSimultaneousEvent(new LifeLostBatchForOnePlayerEvent(lifeLossEvent), game);
}
} }
public void addSimultaneousTappedToBatch(TappedEvent tappedEvent, Game game) { public void addSimultaneousTappedToBatch(TappedEvent tappedEvent, Game game) {

View file

@ -166,7 +166,6 @@ public class GameEvent implements Serializable {
DAMAGE_CAUSES_LIFE_LOSS, DAMAGE_CAUSES_LIFE_LOSS,
PLAYER_LIFE_CHANGE, PLAYER_LIFE_CHANGE,
GAIN_LIFE, GAINED_LIFE, GAIN_LIFE, GAINED_LIFE,
LOSE_LIFE, LOST_LIFE,
/* LOSE_LIFE + LOST_LIFE /* LOSE_LIFE + LOST_LIFE
targetId the id of the player loosing life targetId the id of the player loosing life
sourceId sourceId of the ability which caused the lose sourceId sourceId of the ability which caused the lose
@ -174,10 +173,17 @@ public class GameEvent implements Serializable {
amount amount of life loss amount amount of life loss
flag true = from combat damage - other from non combat damage 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 /* LOST_LIFE_BATCH
combines all player life lost events to a single batch (event) combines all player life lost events to a single batch (event)
*/ */
LOST_LIFE_BATCH(true),
PLAY_LAND, LAND_PLAYED, PLAY_LAND, LAND_PLAYED,
CREATURE_CHAMPIONED, CREATURE_CHAMPIONED,
/* CREATURE_CHAMPIONED /* CREATURE_CHAMPIONED

View file

@ -0,0 +1,11 @@
package mage.game.events;
/**
* @author Susucr
*/
public class LifeLostBatchForOnePlayerEvent extends BatchEvent<LifeLostEvent> {
public LifeLostBatchForOnePlayerEvent(LifeLostEvent firstEvent) {
super(EventType.LOST_LIFE_BATCH_FOR_ONE_PLAYER, true, false, false, firstEvent);
}
}

View file

@ -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<UUID, Integer> 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);
}
}