diff --git a/Mage.Sets/src/mage/cards/a/AegarTheFreezingFlame.java b/Mage.Sets/src/mage/cards/a/AegarTheFreezingFlame.java index ab3c5d2ed06..a7c488b6e39 100644 --- a/Mage.Sets/src/mage/cards/a/AegarTheFreezingFlame.java +++ b/Mage.Sets/src/mage/cards/a/AegarTheFreezingFlame.java @@ -3,6 +3,7 @@ package mage.cards.a; import mage.MageInt; import mage.MageObject; import mage.MageObjectReference; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.cards.CardImpl; @@ -11,7 +12,9 @@ import mage.constants.*; import mage.game.Game; import mage.game.events.DamagedBatchForOnePermanentEvent; import mage.game.events.DamagedEvent; +import mage.game.events.DamagedPermanentEvent; import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; import mage.watchers.Watcher; import java.util.*; @@ -44,7 +47,7 @@ public final class AegarTheFreezingFlame extends CardImpl { } } -class AegarTheFreezingFlameTriggeredAbility extends TriggeredAbilityImpl { +class AegarTheFreezingFlameTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { AegarTheFreezingFlameTriggeredAbility() { super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1)); @@ -62,17 +65,15 @@ class AegarTheFreezingFlameTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchForOnePermanentEvent dEvent = (DamagedBatchForOnePermanentEvent) event; - - int excess = dEvent.getEvents() + // all events in the batch are always relevant if triggers at all + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); + if (permanent == null || !game.getOpponents(getControllerId()).contains(permanent.getControllerId())) { + return false; + } + if (getFilteredEvents((DamagedBatchForOnePermanentEvent) event, game) .stream() .mapToInt(DamagedEvent::getExcess) - .sum(); - - boolean controlledByOpponent = - game.getOpponents(getControllerId()).contains(game.getControllerId(event.getTargetId())); - - if (excess < 1 || !controlledByOpponent) { + .sum() < 1) { return false; } AegarTheFreezingFlameWatcher watcher = game.getState().getWatcher(AegarTheFreezingFlameWatcher.class); @@ -105,7 +106,6 @@ class AegarTheFreezingFlameWatcher extends Watcher { if (event.getType() != GameEvent.EventType.DAMAGED_PERMANENT) { return; } - DamagedEvent dEvent = (DamagedEvent) event; MageObject sourceObject = game.getObject(event.getSourceId()); if (sourceObject == null) { return; diff --git a/Mage.Sets/src/mage/cards/a/AjaniNacatlPariah.java b/Mage.Sets/src/mage/cards/a/AjaniNacatlPariah.java index 6631af0ccc3..9320de6fd90 100644 --- a/Mage.Sets/src/mage/cards/a/AjaniNacatlPariah.java +++ b/Mage.Sets/src/mage/cards/a/AjaniNacatlPariah.java @@ -2,7 +2,7 @@ package mage.cards.a; import mage.MageInt; import mage.constants.Pronoun; -import mage.abilities.common.DiesOneOrMoreCreatureTriggeredAbility; +import mage.abilities.common.DiesOneOrMoreTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.ExileAndReturnSourceEffect; @@ -44,7 +44,7 @@ public final class AjaniNacatlPariah extends CardImpl { // Whenever one or more other Cats you control die, you may exile Ajani, then return him to the battlefield transformed under his owner's control. this.addAbility(new TransformAbility()); - this.addAbility(new DiesOneOrMoreCreatureTriggeredAbility( + this.addAbility(new DiesOneOrMoreTriggeredAbility( new ExileAndReturnSourceEffect(PutCards.BATTLEFIELD_TRANSFORMED, Pronoun.HE), filter, true)); diff --git a/Mage.Sets/src/mage/cards/a/AlelaCunningConqueror.java b/Mage.Sets/src/mage/cards/a/AlelaCunningConqueror.java index 2ead5282316..35a70741ddc 100644 --- a/Mage.Sets/src/mage/cards/a/AlelaCunningConqueror.java +++ b/Mage.Sets/src/mage/cards/a/AlelaCunningConqueror.java @@ -2,7 +2,7 @@ package mage.cards.a; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.FirstSpellOpponentsTurnTriggeredAbility; import mage.abilities.effects.Effect; import mage.abilities.effects.common.CreateTokenEffect; @@ -46,7 +46,7 @@ public final class AlelaCunningConqueror extends CardImpl { // Whenever one or more Faeries you control deal combat damage to a player, goad target creature that player controls. Effect effect = new GoadTargetEffect().setText("goad target creature that player controls"); - Ability ability = new DealCombatDamageControlledTriggeredAbility(Zone.BATTLEFIELD, effect, faerieFilter, SetTargetPointer.PLAYER, false); + Ability ability = new OneOrMoreCombatDamagePlayerTriggeredAbility(Zone.BATTLEFIELD, effect, faerieFilter, SetTargetPointer.PLAYER, false); ability.addTarget(new TargetPermanent(filter)); ability.setTargetAdjuster(new DamagedPlayerControlsTargetAdjuster()); diff --git a/Mage.Sets/src/mage/cards/a/AngelOfDeliverance.java b/Mage.Sets/src/mage/cards/a/AngelOfDeliverance.java index 961d738a22e..7744b4ffe5e 100644 --- a/Mage.Sets/src/mage/cards/a/AngelOfDeliverance.java +++ b/Mage.Sets/src/mage/cards/a/AngelOfDeliverance.java @@ -1,25 +1,20 @@ package mage.cards.a; -import java.util.UUID; - import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; import mage.abilities.condition.common.DeliriumCondition; -import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.ExileTargetEffect; import mage.abilities.hint.common.CardTypesInGraveyardHint; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.filter.StaticFilters; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.target.common.TargetCreaturePermanent; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; /** * @author fireshoes @@ -37,14 +32,11 @@ public final class AngelOfDeliverance extends CardImpl { // Delirium — Whenever Angel of Deliverance deals damage, if there are four or more card types among cards in your graveyard, // exile target creature an opponent controls. - Ability ability = new ConditionalInterveningIfTriggeredAbility( - new AngelOfDeliveranceDealsDamageTriggeredAbility(), - DeliriumCondition.instance, - "Delirium — Whenever {this} deals damage, if there are four or more card types among cards in your graveyard, exile target creature an opponent controls" - ); - ability.addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE)); + Ability ability = new DealsDamageSourceTriggeredAbility(new ExileTargetEffect()) + .withInterveningIf(DeliriumCondition.instance); + ability.addTarget(new TargetOpponentsCreaturePermanent()); ability.addHint(CardTypesInGraveyardHint.YOU); - this.addAbility(ability); + this.addAbility(ability.setAbilityWord(AbilityWord.DELIRIUM)); } private AngelOfDeliverance(final AngelOfDeliverance card) { @@ -56,36 +48,3 @@ public final class AngelOfDeliverance extends CardImpl { return new AngelOfDeliverance(this); } } - -class AngelOfDeliveranceDealsDamageTriggeredAbility extends TriggeredAbilityImpl { - - public AngelOfDeliveranceDealsDamageTriggeredAbility() { - super(Zone.BATTLEFIELD, new ExileTargetEffect(), false); - } - - private AngelOfDeliveranceDealsDamageTriggeredAbility(final AngelOfDeliveranceDealsDamageTriggeredAbility ability) { - super(ability); - } - - @Override - public AngelOfDeliveranceDealsDamageTriggeredAbility copy() { - return new AngelOfDeliveranceDealsDamageTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER - || event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getSourceId().equals(this.getSourceId())) { - for (Effect effect : this.getEffects()) { - effect.setValue("damage", event.getAmount()); - } - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/a/AngelheartVial.java b/Mage.Sets/src/mage/cards/a/AngelheartVial.java index 1d14580be2e..bfdf7b03712 100644 --- a/Mage.Sets/src/mage/cards/a/AngelheartVial.java +++ b/Mage.Sets/src/mage/cards/a/AngelheartVial.java @@ -1,24 +1,19 @@ - package mage.cards.a; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.YoureDealtDamageTriggeredAbility; import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Zone; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; import java.util.UUID; @@ -31,7 +26,8 @@ public final class AngelheartVial extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{5}"); // Whenever you're dealt damage, you may put that many charge counters on Angelheart Vial. - this.addAbility(new AngelheartVialTriggeredAbility()); + this.addAbility(new YoureDealtDamageTriggeredAbility(new AddCountersSourceEffect( + CounterType.CHARGE.createInstance(), SavedDamageValue.MANY), true)); // {2}, {tap}, Remove four charge counters from Angelheart Vial: You gain 2 life and draw a card. Ability ability = new SimpleActivatedAbility(new GainLifeEffect(2), new GenericManaCost(2)); @@ -50,63 +46,3 @@ public final class AngelheartVial extends CardImpl { return new AngelheartVial(this); } } - -class AngelheartVialTriggeredAbility extends TriggeredAbilityImpl { - - public AngelheartVialTriggeredAbility() { - super(Zone.BATTLEFIELD, new AngelheartVialEffect(), true); - } - - private AngelheartVialTriggeredAbility(final AngelheartVialTriggeredAbility ability) { - super(ability); - } - - @Override - public AngelheartVialTriggeredAbility copy() { - return new AngelheartVialTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(this.getControllerId())) { - this.getEffects().get(0).setValue("damageAmount", event.getAmount()); - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever you're dealt damage, you may put that many charge counters on {this}."; - } -} - -class AngelheartVialEffect extends OneShotEffect { - - AngelheartVialEffect() { - super(Outcome.Benefit); - } - - private AngelheartVialEffect(final AngelheartVialEffect effect) { - super(effect); - } - - @Override - public AngelheartVialEffect copy() { - return new AngelheartVialEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getSourceId()); - if (permanent != null) { - permanent.addCounters(CounterType.CHARGE.createInstance((Integer) this.getValue("damageAmount")), source.getControllerId(), source, game); - } - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/a/AnowonTheRuinThief.java b/Mage.Sets/src/mage/cards/a/AnowonTheRuinThief.java index f67c0e38339..2a4ddc49c02 100644 --- a/Mage.Sets/src/mage/cards/a/AnowonTheRuinThief.java +++ b/Mage.Sets/src/mage/cards/a/AnowonTheRuinThief.java @@ -2,7 +2,7 @@ package mage.cards.a; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.OneShotEffect; @@ -39,7 +39,7 @@ public final class AnowonTheRuinThief extends CardImpl { )); // Whenever one or more Rogues you control deal combat damage to a player, that player mills a card for each 1 damage dealt to them. If the player mills at least one creature card this way, you draw a card. - this.addAbility(new DealCombatDamageControlledTriggeredAbility(Zone.BATTLEFIELD, new AnowonTheRuinThiefEffect(), + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility(Zone.BATTLEFIELD, new AnowonTheRuinThiefEffect(), filter, SetTargetPointer.PLAYER, false)); } diff --git a/Mage.Sets/src/mage/cards/a/Arcbond.java b/Mage.Sets/src/mage/cards/a/Arcbond.java index a7e99622ba0..db4a4c3cfda 100644 --- a/Mage.Sets/src/mage/cards/a/Arcbond.java +++ b/Mage.Sets/src/mage/cards/a/Arcbond.java @@ -1,10 +1,10 @@ - package mage.cards.a; import java.util.UUID; import mage.MageObject; import mage.MageObjectReference; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.Effect; @@ -21,6 +21,7 @@ import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.permanent.PermanentIdPredicate; import mage.game.Game; +import mage.game.events.DamagedPermanentEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; @@ -49,11 +50,11 @@ public final class Arcbond extends CardImpl { } } -class ArcbondDelayedTriggeredAbility extends DelayedTriggeredAbility { +class ArcbondDelayedTriggeredAbility extends DelayedTriggeredAbility implements BatchTriggeredAbility { MageObjectReference targetObject; - public ArcbondDelayedTriggeredAbility() { + ArcbondDelayedTriggeredAbility() { super(new ArcbondEffect(), Duration.EndOfTurn, false); } @@ -65,6 +66,7 @@ class ArcbondDelayedTriggeredAbility extends DelayedTriggeredAbility { @Override public void init(Game game) { // because target can already be gone from battlefield if triggered ability resolves, we need to hold an own object reference + // TODO: this doesn't seem like it can actually work as described, wtf targetObject = new MageObjectReference(getTargets().getFirstTarget(), game); if (targetObject != null) { for (Effect effect : this.getEffects()) { @@ -91,9 +93,7 @@ class ArcbondDelayedTriggeredAbility extends DelayedTriggeredAbility { public boolean checkTrigger(GameEvent event, Game game) { if (event.getTargetId().equals(targetObject.getSourceId()) && targetObject.getPermanentOrLKIBattlefield(game) != null) { - for (Effect effect : this.getEffects()) { - effect.setValue("damage", event.getAmount()); - } + getEffects().setValue("damage", event.getAmount()); return true; } return false; diff --git a/Mage.Sets/src/mage/cards/a/AutomatedAssemblyLine.java b/Mage.Sets/src/mage/cards/a/AutomatedAssemblyLine.java index 6d7d037d2e9..3bf19b3aff3 100644 --- a/Mage.Sets/src/mage/cards/a/AutomatedAssemblyLine.java +++ b/Mage.Sets/src/mage/cards/a/AutomatedAssemblyLine.java @@ -2,7 +2,7 @@ package mage.cards.a; import java.util.UUID; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.PayEnergyCost; import mage.abilities.effects.common.CreateTokenEffect; @@ -22,7 +22,7 @@ public final class AutomatedAssemblyLine extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{W}"); // Whenever one or more artifact creatures you control deal combat damage to a player, you get {E}. - this.addAbility(new DealCombatDamageControlledTriggeredAbility( + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility( new GetEnergyCountersControllerEffect(1), StaticFilters.FILTER_PERMANENTS_ARTIFACT_CREATURE)); // Pay {E}{E}{E}: Create a tapped 3/3 colorless Robot artifact creature token. diff --git a/Mage.Sets/src/mage/cards/b/BindingAgony.java b/Mage.Sets/src/mage/cards/b/BindingAgony.java index c8c3ab3cac3..76aeaa8cc90 100644 --- a/Mage.Sets/src/mage/cards/b/BindingAgony.java +++ b/Mage.Sets/src/mage/cards/b/BindingAgony.java @@ -29,7 +29,7 @@ public final class BindingAgony extends CardImpl { this.addAbility(new EnchantAbility(auraTarget)); // Whenever enchanted creature is dealt damage, Binding Agony deals that much damage to that creature's controller. - this.addAbility(new DealtDamageAttachedTriggeredAbility(new DamageAttachedControllerEffect(SavedDamageValue.MUCH), false)); + this.addAbility(new DealtDamageAttachedTriggeredAbility(new DamageAttachedControllerEffect(SavedDamageValue.MUCH))); } private BindingAgony(final BindingAgony card) { diff --git a/Mage.Sets/src/mage/cards/b/BlazeCommando.java b/Mage.Sets/src/mage/cards/b/BlazeCommando.java index bff9dcae2ba..6ff8bd88cc4 100644 --- a/Mage.Sets/src/mage/cards/b/BlazeCommando.java +++ b/Mage.Sets/src/mage/cards/b/BlazeCommando.java @@ -1,42 +1,35 @@ - - package mage.cards.b; import mage.MageInt; -import mage.MageObject; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SpellControlledDealsDamageTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.CardImpl; 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.filter.StaticFilters; import mage.game.permanent.token.SoldierTokenWithHaste; -import java.util.ArrayList; -import java.util.List; import java.util.UUID; /** - * * @author LevelX2 */ - - public final class BlazeCommando extends CardImpl { - public BlazeCommando (UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{R}{W}"); + public BlazeCommando(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{W}"); this.subtype.add(SubType.MINOTAUR, SubType.SOLDIER); - this.power = new MageInt(5); this.toughness = new MageInt(3); // Whenever an instant or sorcery spell you control deals damage, create two 1/1 red and white Soldier creature tokens with haste. - this.addAbility(new BlazeCommandoTriggeredAbility()); + this.addAbility(new SpellControlledDealsDamageTriggeredAbility(Zone.BATTLEFIELD, + new CreateTokenEffect(new SoldierTokenWithHaste(), 2), + StaticFilters.FILTER_SPELL_INSTANT_OR_SORCERY, false + )); } @@ -50,57 +43,3 @@ public final class BlazeCommando extends CardImpl { } } - -class BlazeCommandoTriggeredAbility extends TriggeredAbilityImpl { - - private final List handledStackObjects = new ArrayList<>(); - - public BlazeCommandoTriggeredAbility() { - super(Zone.BATTLEFIELD, new CreateTokenEffect(new SoldierTokenWithHaste(), 2), false); - setTriggerPhrase("Whenever an instant or sorcery spell you control deals damage, "); - } - - private BlazeCommandoTriggeredAbility(final BlazeCommandoTriggeredAbility ability) { - super(ability); - } - - @Override - public BlazeCommandoTriggeredAbility copy() { - return new BlazeCommandoTriggeredAbility(this); - } - - @Override - public void reset(Game game) { - /** - * Blaze Commando's ability triggers each time an instant or sorcery spell you control - * deals damage (or, put another way, the number of times the word "deals" appears in - * its instructions), no matter how much damage is dealt or how many players or permanents - * are dealt damage. For example, if you cast Punish the Enemy and it "deals 3 damage to - * target player and 3 damage to target creature," Blaze Commando's ability will trigger - * once and you'll get two Soldier tokens. - */ - handledStackObjects.clear(); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT - || event.getType() == GameEvent.EventType.DAMAGED_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (isControlledBy(game.getControllerId(event.getSourceId()))) { - MageObject damageSource = game.getObject(event.getSourceId()); - if (damageSource != null) { - if (damageSource.isInstantOrSorcery(game)) { - if (!handledStackObjects.contains(damageSource.getId())) { - handledStackObjects.add(damageSource.getId()); - return true; - } - } - } - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/b/BlazingSunsteel.java b/Mage.Sets/src/mage/cards/b/BlazingSunsteel.java index 39b2519aa44..c70726dda4a 100644 --- a/Mage.Sets/src/mage/cards/b/BlazingSunsteel.java +++ b/Mage.Sets/src/mage/cards/b/BlazingSunsteel.java @@ -1,8 +1,10 @@ package mage.cards.b; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.dynamicvalue.common.OpponentsCount; import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.OneShotEffect; @@ -15,16 +17,14 @@ import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedEvent; -import mage.game.events.DamagedBatchForPermanentsEvent; +import mage.game.events.DamagedPermanentEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetControlledCreaturePermanent; import java.util.UUID; -import mage.abilities.costs.mana.GenericManaCost; -import mage.target.common.TargetControlledCreaturePermanent; /** * @author TheElk801 @@ -32,7 +32,7 @@ import mage.target.common.TargetControlledCreaturePermanent; public final class BlazingSunsteel extends CardImpl { public BlazingSunsteel(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[] { CardType.ARTIFACT }, "{1}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{R}"); this.subtype.add(SubType.EQUIPMENT); @@ -40,8 +40,7 @@ public final class BlazingSunsteel extends CardImpl { this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(OpponentsCount.instance, StaticValue.get(0)) .setText("equipped creature gets +1/+0 for each opponent you have"))); - // Whenever equipped creature is dealt damage, it deals that much damage to any - // target. + // Whenever equipped creature is dealt damage, it deals that much damage to any target. this.addAbility(new BlazingSunsteelTriggeredAbility()); // Equip {4} @@ -58,7 +57,7 @@ public final class BlazingSunsteel extends CardImpl { } } -class BlazingSunsteelTriggeredAbility extends TriggeredAbilityImpl { +class BlazingSunsteelTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { BlazingSunsteelTriggeredAbility() { super(Zone.BATTLEFIELD, new BlazingSunsteelEffect(), false); @@ -76,40 +75,18 @@ class BlazingSunsteelTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_PERMANENTS; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override public boolean checkTrigger(GameEvent event, Game game) { - Permanent equipment = game.getPermanent(this.getSourceId()); - if (equipment == null) { + Permanent equipment = getSourcePermanentIfItStillExists(game); + if (equipment == null || !event.getTargetId().equals(equipment.getAttachedTo())) { return false; } - - UUID attachedCreature = equipment.getAttachedTo(); - if (attachedCreature == null) { - return false; - } - - int damage = 0; - DamagedBatchForPermanentsEvent dEvent = (DamagedBatchForPermanentsEvent) event; - for (DamagedEvent damagedEvent : dEvent.getEvents()) { - UUID targetID = damagedEvent.getTargetId(); - if (targetID == null) { - continue; - } - - if (targetID == attachedCreature) { - damage += damagedEvent.getAmount(); - } - } - - if (damage > 0) { - this.getEffects().setValue("equipped", attachedCreature); - this.getEffects().setValue("damage", damage); - return true; - } - return false; + this.getEffects().setValue("equipped", equipment.getAttachedTo()); + this.getEffects().setValue("damage", event.getAmount()); + return true; } @Override @@ -136,12 +113,12 @@ class BlazingSunsteelEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Permanent creature = game.getPermanentOrLKIBattlefield((UUID) getValue("equipped")); - Integer damage = (Integer)getValue("damage"); + Integer damage = (Integer) getValue("damage"); - if (creature == null || damage == null || damage < 1) { + if (creature == null || damage == null || damage < 1) { return false; } - + Permanent permanent = game.getPermanent(source.getFirstTarget()); if (permanent != null) { permanent.damage(damage, creature.getId(), source, game); diff --git a/Mage.Sets/src/mage/cards/b/BloodHound.java b/Mage.Sets/src/mage/cards/b/BloodHound.java index a04de89872a..dd8e04a0846 100644 --- a/Mage.Sets/src/mage/cards/b/BloodHound.java +++ b/Mage.Sets/src/mage/cards/b/BloodHound.java @@ -1,18 +1,16 @@ package mage.cards.b; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.abilities.common.YoureDealtDamageTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.common.RemoveAllCountersSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.cards.CardImpl; 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; import java.util.UUID; @@ -29,7 +27,8 @@ public final class BloodHound extends CardImpl { this.toughness = new MageInt(1); // Whenever you're dealt damage, you may put that many +1/+1 counters on Blood Hound. - this.addAbility(new BloodHoundTriggeredAbility()); + this.addAbility(new YoureDealtDamageTriggeredAbility(new AddCountersSourceEffect( + CounterType.P1P1.createInstance(), SavedDamageValue.MANY), true)); // At the beginning of your end step, remove all +1/+1 counters from Blood Hound. this.addAbility(new BeginningOfEndStepTriggeredAbility( @@ -47,41 +46,3 @@ public final class BloodHound extends CardImpl { return new BloodHound(this); } } - -class BloodHoundTriggeredAbility extends TriggeredAbilityImpl { - - BloodHoundTriggeredAbility() { - super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance()), true); - } - - private BloodHoundTriggeredAbility(final BloodHoundTriggeredAbility ability) { - super(ability); - } - - @Override - public BloodHoundTriggeredAbility copy() { - return new BloodHoundTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(this.getControllerId()) && event.getAmount() > 0) { - this.getEffects().clear(); - if (event.getAmount() > 0) { - this.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance(event.getAmount()))); - } - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever you're dealt damage, you may put that many +1/+1 counters on {this}."; - } -} diff --git a/Mage.Sets/src/mage/cards/b/BloodSpatterAnalysis.java b/Mage.Sets/src/mage/cards/b/BloodSpatterAnalysis.java index 6957e645a74..5047c19b5ec 100644 --- a/Mage.Sets/src/mage/cards/b/BloodSpatterAnalysis.java +++ b/Mage.Sets/src/mage/cards/b/BloodSpatterAnalysis.java @@ -1,7 +1,7 @@ package mage.cards.b; import mage.abilities.Ability; -import mage.abilities.common.DiesOneOrMoreCreaturesTriggeredAbility; +import mage.abilities.common.DiesOneOrMoreTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.delayed.ReflexiveTriggeredAbility; import mage.abilities.condition.Condition; @@ -41,7 +41,8 @@ public final class BloodSpatterAnalysis extends CardImpl { // Whenever one or more creatures die, mill a card and put a bloodstain counter on Blood Spatter Analysis. // Then sacrifice it if it has five or more bloodstain counters on it. // When you do, return target creature card from your graveyard to your hand. - ability = new DiesOneOrMoreCreaturesTriggeredAbility(new MillCardsControllerEffect(1)); + ability = new DiesOneOrMoreTriggeredAbility(new MillCardsControllerEffect(1), + StaticFilters.FILTER_PERMANENT_CREATURES, false); ability.addEffect(new AddCountersSourceEffect(CounterType.BLOODSTAIN.createInstance()).concatBy("and")); ReflexiveTriggeredAbility returnAbility = new ReflexiveTriggeredAbility(new ReturnFromGraveyardToHandTargetEffect(), false); returnAbility.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); diff --git a/Mage.Sets/src/mage/cards/b/BloodthirstyConqueror.java b/Mage.Sets/src/mage/cards/b/BloodthirstyConqueror.java index f0b3929fe97..7b79ec2d326 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.CardImpl; 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 final class BloodthirstyConqueror extends CardImpl { 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 final class BloodthirstyConqueror extends CardImpl { 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/b/BreechesBrazenPlunderer.java b/Mage.Sets/src/mage/cards/b/BreechesBrazenPlunderer.java index 088fbd4f397..7714d6abab0 100644 --- a/Mage.Sets/src/mage/cards/b/BreechesBrazenPlunderer.java +++ b/Mage.Sets/src/mage/cards/b/BreechesBrazenPlunderer.java @@ -2,6 +2,7 @@ package mage.cards.b; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.MenaceAbility; @@ -10,7 +11,7 @@ import mage.cards.*; import mage.constants.*; import mage.game.Game; import mage.game.events.DamagedBatchForPlayersEvent; -import mage.game.events.DamagedEvent; +import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; @@ -55,7 +56,7 @@ public final class BreechesBrazenPlunderer extends CardImpl { } } -class BreechesBrazenPlundererTriggeredAbility extends TriggeredAbilityImpl { +class BreechesBrazenPlundererTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { BreechesBrazenPlundererTriggeredAbility() { super(Zone.BATTLEFIELD, null); @@ -70,19 +71,24 @@ class BreechesBrazenPlundererTriggeredAbility extends TriggeredAbilityImpl { return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_PLAYERS; } + @Override + public boolean checkEvent(DamagedPlayerEvent event, Game game) { + if (!game.getOpponents(getControllerId()).contains(event.getTargetId())) { + return false; + } + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); + return permanent != null + && permanent.isControlledBy(getControllerId()) + && permanent.hasSubtype(SubType.PIRATE, game); + } + @Override public boolean checkTrigger(GameEvent event, Game game) { Set opponents = new HashSet<>(); - for (DamagedEvent damagedEvent : ((DamagedBatchForPlayersEvent) event).getEvents()) { - Permanent permanent = game.getPermanent(damagedEvent.getSourceId()); - if (permanent == null - || !permanent.isControlledBy(getControllerId()) - || !permanent.hasSubtype(SubType.PIRATE, game) - || !game.getOpponents(getControllerId()).contains(damagedEvent.getTargetId())) { - continue; - } - opponents.add(damagedEvent.getTargetId()); - } + getFilteredEvents((DamagedBatchForPlayersEvent) event, game) + .stream() + .map(GameEvent::getTargetId) + .forEach(opponents::add); if (opponents.isEmpty()) { return false; } diff --git a/Mage.Sets/src/mage/cards/c/Chainsaw.java b/Mage.Sets/src/mage/cards/c/Chainsaw.java index d32662b6843..8477a05584a 100644 --- a/Mage.Sets/src/mage/cards/c/Chainsaw.java +++ b/Mage.Sets/src/mage/cards/c/Chainsaw.java @@ -1,7 +1,7 @@ package mage.cards.c; import mage.abilities.Ability; -import mage.abilities.common.DiesOneOrMoreCreaturesTriggeredAbility; +import mage.abilities.common.DiesOneOrMoreTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.DynamicValue; @@ -16,6 +16,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; +import mage.filter.StaticFilters; import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -38,7 +39,8 @@ public final class Chainsaw extends CardImpl { this.addAbility(ability); // Whenever one or more creatures die, put a rev counter on Chainsaw. - this.addAbility(new DiesOneOrMoreCreaturesTriggeredAbility(new AddCountersSourceEffect(CounterType.REV.createInstance()))); + this.addAbility(new DiesOneOrMoreTriggeredAbility(new AddCountersSourceEffect(CounterType.REV.createInstance()), + StaticFilters.FILTER_PERMANENT_CREATURES, false)); // Equipped creature gets +X/+0, where X is the number of rev counters on Chainsaw. this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(xValue, StaticValue.get(0)))); diff --git a/Mage.Sets/src/mage/cards/c/ChandrasSpitfire.java b/Mage.Sets/src/mage/cards/c/ChandrasSpitfire.java index 2d3800e8eb7..2cce0459bc4 100644 --- a/Mage.Sets/src/mage/cards/c/ChandrasSpitfire.java +++ b/Mage.Sets/src/mage/cards/c/ChandrasSpitfire.java @@ -1,21 +1,16 @@ - - package mage.cards.c; -import java.util.UUID; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.OpponentDealtNoncombatDamageTriggeredAbility; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Duration; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.DamagedBatchForOnePlayerEvent; -import mage.game.events.GameEvent; +import mage.constants.SubType; + +import java.util.UUID; /** * @@ -30,8 +25,11 @@ public final class ChandrasSpitfire extends CardImpl { this.power = new MageInt(1); this.toughness = new MageInt(3); + // Flying this.addAbility(FlyingAbility.getInstance()); - this.addAbility(new ChandrasSpitfireAbility()); + + // Whenever an opponent is dealt noncombat damage, Chandra’s Spitfire gets +3/+0 until end of turn. + this.addAbility(new OpponentDealtNoncombatDamageTriggeredAbility(new BoostSourceEffect(3, 0, Duration.EndOfTurn))); } private ChandrasSpitfire(final ChandrasSpitfire card) { @@ -44,36 +42,3 @@ public final class ChandrasSpitfire extends CardImpl { } } - -class ChandrasSpitfireAbility extends TriggeredAbilityImpl { - - public ChandrasSpitfireAbility() { - super(Zone.BATTLEFIELD, new BoostSourceEffect(3, 0, Duration.EndOfTurn), false); - } - - private ChandrasSpitfireAbility(final ChandrasSpitfireAbility ability) { - super(ability); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchForOnePlayerEvent dEvent = (DamagedBatchForOnePlayerEvent) event; - return !dEvent.isCombatDamage() && dEvent.getAmount() > 0 && game.getOpponents(controllerId).contains(dEvent.getTargetId()); - } - - @Override - public String getRule() { - return "Whenever an opponent is dealt noncombat damage, {this} gets +3/+0 until end of turn."; - } - - @Override - public ChandrasSpitfireAbility copy() { - return new ChandrasSpitfireAbility(this); - } - -} diff --git a/Mage.Sets/src/mage/cards/c/ContaminantGrafter.java b/Mage.Sets/src/mage/cards/c/ContaminantGrafter.java index 0921b22bda9..fa54288ffef 100644 --- a/Mage.Sets/src/mage/cards/c/ContaminantGrafter.java +++ b/Mage.Sets/src/mage/cards/c/ContaminantGrafter.java @@ -2,8 +2,8 @@ package mage.cards.c; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.abilities.condition.common.CorruptedCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.DrawCardSourceControllerEffect; @@ -11,6 +11,7 @@ import mage.abilities.effects.common.PutCardFromHandOntoBattlefieldEffect; import mage.abilities.effects.common.counter.ProliferateEffect; import mage.abilities.keyword.ToxicAbility; import mage.abilities.keyword.TrampleAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.AbilityWord; @@ -19,8 +20,8 @@ import mage.constants.SubType; import mage.constants.Zone; import mage.filter.StaticFilters; import mage.game.Game; -import mage.game.events.DamagedEvent; import mage.game.events.DamagedBatchForPlayersEvent; +import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -69,7 +70,7 @@ public final class ContaminantGrafter extends CardImpl { } } -class ContaminantGrafterTriggeredAbility extends TriggeredAbilityImpl { +class ContaminantGrafterTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { ContaminantGrafterTriggeredAbility() { super(Zone.BATTLEFIELD, new ProliferateEffect(false), false); @@ -86,18 +87,19 @@ class ContaminantGrafterTriggeredAbility extends TriggeredAbilityImpl { } @Override - public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchForPlayersEvent dEvent = (DamagedBatchForPlayersEvent) event; - for (DamagedEvent damagedEvent : dEvent.getEvents()) { - if (!damagedEvent.isCombatDamage()) { - continue; - } - Permanent permanent = game.getPermanent(damagedEvent.getSourceId()); - if (permanent != null && permanent.isControlledBy(getControllerId())) { - return true; - } + public boolean checkEvent(DamagedPlayerEvent event, Game game) { + if (!event.isCombatDamage()) { + return false; } - return false; + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); + return permanent != null + && permanent.isCreature(game) + && permanent.isControlledBy(getControllerId()); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return !getFilteredEvents((DamagedBatchForPlayersEvent) event, game).isEmpty(); } @Override diff --git a/Mage.Sets/src/mage/cards/c/ContestedGameBall.java b/Mage.Sets/src/mage/cards/c/ContestedGameBall.java index c9493559c85..0ab9c54c6cc 100644 --- a/Mage.Sets/src/mage/cards/c/ContestedGameBall.java +++ b/Mage.Sets/src/mage/cards/c/ContestedGameBall.java @@ -1,6 +1,7 @@ package mage.cards.c; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.condition.common.SourceHasCounterCondition; @@ -23,6 +24,7 @@ import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; import mage.game.events.DamagedBatchForOnePlayerEvent; +import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.permanent.token.TreasureToken; @@ -66,7 +68,7 @@ public final class ContestedGameBall extends CardImpl { } } -class ContestedGameBallTriggeredAbility extends TriggeredAbilityImpl { +class ContestedGameBallTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { ContestedGameBallTriggeredAbility() { super(Zone.BATTLEFIELD, new ContestedGameBallEffect()); diff --git a/Mage.Sets/src/mage/cards/c/CuratorBeastie.java b/Mage.Sets/src/mage/cards/c/CuratorBeastie.java new file mode 100644 index 00000000000..32259b1911e --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CuratorBeastie.java @@ -0,0 +1,58 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.EntersWithCountersControlledEffect; +import mage.abilities.effects.keyword.ManifestDreadEffect; +import mage.abilities.keyword.ReachAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.ColorlessPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CuratorBeastie extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("colorless creatures"); + + static { + filter.add(ColorlessPredicate.instance); + } + + public CuratorBeastie(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{G}"); + + this.subtype.add(SubType.BEAST); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // 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())); + } + + private CuratorBeastie(final CuratorBeastie card) { + super(card); + } + + @Override + public CuratorBeastie copy() { + return new CuratorBeastie(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DarienKingOfKjeldor.java b/Mage.Sets/src/mage/cards/d/DarienKingOfKjeldor.java index ac94d207915..814073cb8fc 100644 --- a/Mage.Sets/src/mage/cards/d/DarienKingOfKjeldor.java +++ b/Mage.Sets/src/mage/cards/d/DarienKingOfKjeldor.java @@ -1,23 +1,17 @@ - package mage.cards.d; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.common.YoureDealtDamageTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Outcome; import mage.constants.SuperType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; import mage.game.permanent.token.SoldierToken; -import mage.players.Player; + +import java.util.UUID; /** * @@ -35,7 +29,8 @@ public final class DarienKingOfKjeldor extends CardImpl { this.toughness = new MageInt(3); // Whenever you're dealt damage, you may create that many 1/1 white Soldier creature tokens. - this.addAbility(new DarienKingOfKjeldorTriggeredAbility()); + this.addAbility(new YoureDealtDamageTriggeredAbility(new CreateTokenEffect( + new SoldierToken(), SavedDamageValue.MANY), true)); } private DarienKingOfKjeldor(final DarienKingOfKjeldor card) { @@ -47,64 +42,3 @@ public final class DarienKingOfKjeldor extends CardImpl { return new DarienKingOfKjeldor(this); } } - -class DarienKingOfKjeldorTriggeredAbility extends TriggeredAbilityImpl { - - public DarienKingOfKjeldorTriggeredAbility() { - super(Zone.BATTLEFIELD, new DarienKingOfKjeldorEffect(), true); - } - - private DarienKingOfKjeldorTriggeredAbility(final DarienKingOfKjeldorTriggeredAbility ability) { - super(ability); - } - - @Override - public DarienKingOfKjeldorTriggeredAbility copy() { - return new DarienKingOfKjeldorTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if ((event.getTargetId().equals(this.getControllerId()))) { - this.getEffects().get(0).setValue("damageAmount", event.getAmount()); - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever you're dealt damage, you may create that many 1/1 white Soldier creature tokens."; - } -} - -class DarienKingOfKjeldorEffect extends OneShotEffect { - - DarienKingOfKjeldorEffect() { - super(Outcome.Benefit); - } - - private DarienKingOfKjeldorEffect(final DarienKingOfKjeldorEffect effect) { - super(effect); - } - - @Override - public DarienKingOfKjeldorEffect copy() { - return new DarienKingOfKjeldorEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - int damage = (Integer) this.getValue("damageAmount"); - return new CreateTokenEffect(new SoldierToken(), damage).apply(game, source); - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/d/DeathPitsOfRath.java b/Mage.Sets/src/mage/cards/d/DeathPitsOfRath.java index daf71a52cfc..0a378e36f4b 100644 --- a/Mage.Sets/src/mage/cards/d/DeathPitsOfRath.java +++ b/Mage.Sets/src/mage/cards/d/DeathPitsOfRath.java @@ -1,16 +1,12 @@ - package mage.cards.d; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.DealtDamageAnyTriggeredAbility; import mage.abilities.effects.common.DestroyTargetEffect; 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.permanent.Permanent; -import mage.target.targetpointer.FixedTarget; +import mage.constants.SetTargetPointer; +import mage.filter.StaticFilters; import java.util.UUID; @@ -23,7 +19,9 @@ public final class DeathPitsOfRath extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{B}{B}"); // Whenever a creature is dealt damage, destroy it. It can't be regenerated. - this.addAbility(new DeathPitsOfRathTriggeredAbility()); + this.addAbility(new DealtDamageAnyTriggeredAbility(new DestroyTargetEffect(true) + .setText("destroy it. It can't be regenerated"), + StaticFilters.FILTER_PERMANENT_A_CREATURE, SetTargetPointer.PERMANENT, false)); } private DeathPitsOfRath(final DeathPitsOfRath card) { @@ -35,39 +33,3 @@ public final class DeathPitsOfRath extends CardImpl { return new DeathPitsOfRath(this); } } - -class DeathPitsOfRathTriggeredAbility extends TriggeredAbilityImpl { - - public DeathPitsOfRathTriggeredAbility() { - super(Zone.BATTLEFIELD, new DestroyTargetEffect(true)); - } - - private DeathPitsOfRathTriggeredAbility(final DeathPitsOfRathTriggeredAbility effect) { - super(effect); - } - - @Override - public DeathPitsOfRathTriggeredAbility copy() { - return new DeathPitsOfRathTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - Permanent permanent = game.getPermanent(event.getTargetId()); - if (permanent == null || !permanent.isCreature(game)) { - return false; - } - getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); - return true; - } - - @Override - public String getRule() { - return "Whenever a creature is dealt damage, destroy it. It can't be regenerated."; - } -} diff --git a/Mage.Sets/src/mage/cards/d/DescendantsFury.java b/Mage.Sets/src/mage/cards/d/DescendantsFury.java index 68fb6ca171e..33c354efaf6 100644 --- a/Mage.Sets/src/mage/cards/d/DescendantsFury.java +++ b/Mage.Sets/src/mage/cards/d/DescendantsFury.java @@ -1,7 +1,7 @@ package mage.cards.d; import mage.abilities.Ability; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.CostImpl; import mage.abilities.costs.SacrificeCost; @@ -17,9 +17,7 @@ import mage.filter.predicate.permanent.PermanentReferenceInCollectionPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetSacrifice; -import mage.target.targetpointer.TargetPointer; import mage.watchers.common.DamagedPlayerThisCombatWatcher; import java.util.UUID; @@ -33,7 +31,7 @@ public final class DescendantsFury extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{R}"); // Whenever one or more creatures you control deal combat damage to a player, you may sacrifice one of them. If you do, reveal cards from the top of your library until you reveal a creature card that shares a creature type with the sacrificed creature. Put that card onto the battlefield and the rest on the bottom of your library in a random order. - Ability ability = new DealCombatDamageControlledTriggeredAbility( + Ability ability = new OneOrMoreCombatDamagePlayerTriggeredAbility( new DoIfCostPaid( new DescendantsFuryEffect(), new DescendantsFurySacrificeCost() diff --git a/Mage.Sets/src/mage/cards/d/Diresight.java b/Mage.Sets/src/mage/cards/d/Diresight.java index f5e49fd0516..286ac1ebe5d 100644 --- a/Mage.Sets/src/mage/cards/d/Diresight.java +++ b/Mage.Sets/src/mage/cards/d/Diresight.java @@ -19,7 +19,7 @@ public final class Diresight extends CardImpl { // Surveil 2, then draw two cards. You lose 2 life. this.getSpellAbility().addEffect(new SurveilEffect(2, false)); - this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1).concatBy(", then")); + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2).concatBy(", then")); this.getSpellAbility().addEffect(new LoseLifeSourceControllerEffect(2)); } diff --git a/Mage.Sets/src/mage/cards/d/DisaTheRestless.java b/Mage.Sets/src/mage/cards/d/DisaTheRestless.java index c874777fcaa..4e02d1fbd70 100644 --- a/Mage.Sets/src/mage/cards/d/DisaTheRestless.java +++ b/Mage.Sets/src/mage/cards/d/DisaTheRestless.java @@ -1,7 +1,7 @@ package mage.cards.d; import mage.MageInt; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.PutCardIntoGraveFromAnywhereAllTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; @@ -35,7 +35,7 @@ public final class DisaTheRestless extends CardImpl { this.addAbility(new DisaTheRestlessTriggeredAbility()); // Whenever one or more creatures you control deal combat damage to a player, create a Tarmogoyf token. - this.addAbility(new DealCombatDamageControlledTriggeredAbility( + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility( new CreateTokenEffect(new TarmogoyfToken()) )); } diff --git a/Mage.Sets/src/mage/cards/d/DonnaNoble.java b/Mage.Sets/src/mage/cards/d/DonnaNoble.java index f3ad5f05598..d1c06baf48f 100644 --- a/Mage.Sets/src/mage/cards/d/DonnaNoble.java +++ b/Mage.Sets/src/mage/cards/d/DonnaNoble.java @@ -1,33 +1,36 @@ package mage.cards.d; -import java.util.UUID; import mage.MageInt; import mage.MageObjectReference; -import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.common.DamageTargetEffect; -import mage.constants.*; -import mage.abilities.keyword.SoulbondAbility; import mage.abilities.keyword.DoctorsCompanionAbility; +import mage.abilities.keyword.SoulbondAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedBatchForOnePermanentEvent; +import mage.game.events.DamagedPermanentEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.common.TargetOpponent; import mage.util.CardUtil; +import java.util.UUID; + /** - * * @author jimga150 */ public final class DonnaNoble extends CardImpl { public DonnaNoble(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); - + this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.HUMAN); this.power = new MageInt(2); @@ -59,9 +62,10 @@ public final class DonnaNoble extends CardImpl { return new DonnaNoble(this); } } + // Based on DealtDamageToSourceTriggeredAbility, except this uses DamagedBatchForOnePermanentEvent, // which batches all damage dealt at the same time on a permanent-by-permanent basis -class DonnaNobleTriggeredAbility extends TriggeredAbilityImpl { +class DonnaNobleTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { DonnaNobleTriggeredAbility() { super(Zone.BATTLEFIELD, new DamageTargetEffect(SavedDamageValue.MUCH)); @@ -85,10 +89,8 @@ class DonnaNobleTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchForOnePermanentEvent dEvent = (DamagedBatchForOnePermanentEvent) event; - // check if the permanent is Donna or its paired card - if (!CardUtil.getEventTargets(dEvent).contains(getSourceId())){ + if (!CardUtil.getEventTargets(event).contains(getSourceId())) { Permanent paired; Permanent permanent = game.getPermanent(getSourceId()); if (permanent != null && permanent.getPairedCard() != null) { @@ -99,12 +101,12 @@ class DonnaNobleTriggeredAbility extends TriggeredAbilityImpl { } else { return false; } - if (!CardUtil.getEventTargets(dEvent).contains(paired.getId())){ + if (!CardUtil.getEventTargets(event).contains(paired.getId())) { return false; } } - int damage = dEvent.getAmount(); + int damage = event.getAmount(); if (damage < 1) { return false; } diff --git a/Mage.Sets/src/mage/cards/d/DoubtlessOne.java b/Mage.Sets/src/mage/cards/d/DoubtlessOne.java index cc254f4ac1f..cc6903dbae7 100644 --- a/Mage.Sets/src/mage/cards/d/DoubtlessOne.java +++ b/Mage.Sets/src/mage/cards/d/DoubtlessOne.java @@ -1,11 +1,11 @@ - package mage.cards.d; -import java.util.UUID; import mage.MageInt; -import mage.abilities.common.DealsDamageGainLifeSourceTriggeredAbility; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -14,20 +14,21 @@ import mage.constants.SubType; import mage.constants.Zone; import mage.filter.FilterPermanent; +import java.util.UUID; + /** - * * @author fireshoes */ public final class DoubtlessOne extends CardImpl { - + static final FilterPermanent filter = new FilterPermanent("Clerics on the battlefield"); - static { - filter.add(SubType.CLERIC.getPredicate()); - } + static { + filter.add(SubType.CLERIC.getPredicate()); + } public DoubtlessOne(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); this.subtype.add(SubType.CLERIC); this.subtype.add(SubType.AVATAR); this.power = new MageInt(0); @@ -35,9 +36,9 @@ public final class DoubtlessOne extends CardImpl { // Doubtless One's power and toughness are each equal to the number of Clerics on the battlefield. this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(new PermanentsOnBattlefieldCount(filter)))); - + // Whenever Doubtless One deals damage, you gain that much life. - this.addAbility(new DealsDamageGainLifeSourceTriggeredAbility()); + this.addAbility(new DealsDamageSourceTriggeredAbility(new GainLifeEffect(SavedDamageValue.MUCH))); } private DoubtlessOne(final DoubtlessOne card) { @@ -48,4 +49,4 @@ public final class DoubtlessOne extends CardImpl { public DoubtlessOne copy() { return new DoubtlessOne(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/d/DourPortMage.java b/Mage.Sets/src/mage/cards/d/DourPortMage.java index 596501b027d..34cf369b0b7 100644 --- a/Mage.Sets/src/mage/cards/d/DourPortMage.java +++ b/Mage.Sets/src/mage/cards/d/DourPortMage.java @@ -2,6 +2,7 @@ package mage.cards.d; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; @@ -18,9 +19,9 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeBatchEvent; import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; import mage.target.TargetPermanent; -import java.util.Objects; import java.util.UUID; /** @@ -56,7 +57,7 @@ public final class DourPortMage extends CardImpl { } } -class DourPortMageTriggeredAbility extends TriggeredAbilityImpl { +class DourPortMageTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { DourPortMageTriggeredAbility() { super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1)); @@ -77,17 +78,17 @@ class DourPortMageTriggeredAbility extends TriggeredAbilityImpl { return event.getType() == GameEvent.EventType.ZONE_CHANGE_BATCH; } + @Override + public boolean checkEvent(ZoneChangeEvent event, Game game) { + if (event.getFromZone() != Zone.BATTLEFIELD || event.getToZone() == Zone.GRAVEYARD || event.getTargetId().equals(getSourceId())) { + return false; + } + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); + return permanent != null && permanent.isCreature(game) && permanent.isControlledBy(getControllerId()); + } + @Override public boolean checkTrigger(GameEvent event, Game game) { - return ((ZoneChangeBatchEvent) event) - .getEvents() - .stream() - .filter(zoneChangeEvent -> Zone.BATTLEFIELD.match(zoneChangeEvent.getFromZone())) - .filter(zoneChangeEvent -> !Zone.GRAVEYARD.match(zoneChangeEvent.getToZone())) - .map(ZoneChangeEvent::getTargetId) - .filter(uuid -> !getSourceId().equals(uuid)) - .map(game::getPermanentOrLKIBattlefield) - .filter(Objects::nonNull) - .anyMatch(p -> p.isCreature(game) && p.isControlledBy(getControllerId())); + return !getFilteredEvents((ZoneChangeBatchEvent) event, game).isEmpty(); } } diff --git a/Mage.Sets/src/mage/cards/e/ElHajjaj.java b/Mage.Sets/src/mage/cards/e/ElHajjaj.java index 265a9391e30..fbea4812606 100644 --- a/Mage.Sets/src/mage/cards/e/ElHajjaj.java +++ b/Mage.Sets/src/mage/cards/e/ElHajjaj.java @@ -1,29 +1,30 @@ - package mage.cards.e; -import java.util.UUID; import mage.MageInt; -import mage.abilities.common.DealsDamageGainLifeSourceTriggeredAbility; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; +import java.util.UUID; + /** - * * @author fireshoes */ public final class ElHajjaj extends CardImpl { public ElHajjaj(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{B}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{B}"); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.WIZARD); this.power = new MageInt(1); this.toughness = new MageInt(1); // Whenever El-Hajjâj deals damage, you gain that much life. - this.addAbility(new DealsDamageGainLifeSourceTriggeredAbility()); + this.addAbility(new DealsDamageSourceTriggeredAbility(new GainLifeEffect(SavedDamageValue.MUCH))); } private ElHajjaj(final ElHajjaj card) { @@ -34,4 +35,4 @@ public final class ElHajjaj extends CardImpl { public ElHajjaj copy() { return new ElHajjaj(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/e/EmberwildeCaliph.java b/Mage.Sets/src/mage/cards/e/EmberwildeCaliph.java index f13ad0beecf..23529edaaca 100644 --- a/Mage.Sets/src/mage/cards/e/EmberwildeCaliph.java +++ b/Mage.Sets/src/mage/cards/e/EmberwildeCaliph.java @@ -1,26 +1,20 @@ package mage.cards.e; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.AttacksEachCombatStaticAbility; -import mage.abilities.effects.Effect; -import mage.abilities.effects.OneShotEffect; -import mage.constants.SubType; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.TrampleAbility; 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.constants.SubType; + +import java.util.UUID; /** - * * @author TheElk801 */ public final class EmberwildeCaliph extends CardImpl { @@ -42,7 +36,7 @@ public final class EmberwildeCaliph extends CardImpl { this.addAbility(new AttacksEachCombatStaticAbility()); // Whenever Emberwilde Caliph deals damage, you lose that much life. - this.addAbility(new EmberwildeCaliphTriggeredAbility()); + this.addAbility(new DealsDamageSourceTriggeredAbility(new LoseLifeSourceControllerEffect(SavedDamageValue.MUCH))); } private EmberwildeCaliph(final EmberwildeCaliph card) { @@ -54,70 +48,3 @@ public final class EmberwildeCaliph extends CardImpl { return new EmberwildeCaliph(this); } } - -class EmberwildeCaliphTriggeredAbility extends TriggeredAbilityImpl { - - public EmberwildeCaliphTriggeredAbility() { - super(Zone.BATTLEFIELD, new EmberwildeCaliphEffect(), false); - } - - private EmberwildeCaliphTriggeredAbility(final EmberwildeCaliphTriggeredAbility ability) { - super(ability); - } - - @Override - public EmberwildeCaliphTriggeredAbility copy() { - return new EmberwildeCaliphTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT - || event.getType() == GameEvent.EventType.DAMAGED_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getSourceId().equals(this.getSourceId())) { - for (Effect effect : this.getEffects()) { - effect.setValue("damage", event.getAmount()); - } - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever {this} deals damage, you lose that much life."; - } -} - -class EmberwildeCaliphEffect extends OneShotEffect { - - EmberwildeCaliphEffect() { - super(Outcome.LoseLife); - } - - private EmberwildeCaliphEffect(final EmberwildeCaliphEffect effect) { - super(effect); - } - - @Override - public EmberwildeCaliphEffect copy() { - return new EmberwildeCaliphEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - int amount = (Integer) getValue("damage"); - if (amount > 0) { - controller.loseLife(amount, game, source, false); - } - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/e/EmbodimentOfFlame.java b/Mage.Sets/src/mage/cards/e/EmbodimentOfFlame.java index 59e10e88be5..c98e0c8cdb0 100644 --- a/Mage.Sets/src/mage/cards/e/EmbodimentOfFlame.java +++ b/Mage.Sets/src/mage/cards/e/EmbodimentOfFlame.java @@ -2,8 +2,8 @@ package mage.cards.e; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SpellControlledDealsDamageTriggeredAbility; import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; @@ -15,9 +15,7 @@ import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.stack.Spell; +import mage.filter.StaticFilters; import java.util.UUID; @@ -37,7 +35,10 @@ public final class EmbodimentOfFlame extends CardImpl { this.nightCard = true; // Whenever a spell you control deals damage, put a flame counter on Embodiment of Flame. - this.addAbility(new EmbodimentOfFlameTriggeredAbility()); + this.addAbility(new SpellControlledDealsDamageTriggeredAbility(Zone.BATTLEFIELD, + new AddCountersSourceEffect(CounterType.FLAME.createInstance()), + StaticFilters.FILTER_SPELL, false + )); // {1}, Remove a flame counter from Embodiment of Flame: Exile the top card of your library. You may play that card this turn. Ability ability = new SimpleActivatedAbility( @@ -57,36 +58,3 @@ public final class EmbodimentOfFlame extends CardImpl { return new EmbodimentOfFlame(this); } } - -class EmbodimentOfFlameTriggeredAbility extends TriggeredAbilityImpl { - - EmbodimentOfFlameTriggeredAbility() { - super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.FLAME.createInstance())); - } - - private EmbodimentOfFlameTriggeredAbility(final EmbodimentOfFlameTriggeredAbility ability) { - super(ability); - } - - @Override - public EmbodimentOfFlameTriggeredAbility copy() { - return new EmbodimentOfFlameTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT - || event.getType() == GameEvent.EventType.DAMAGED_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - Spell spell = game.getSpellOrLKIStack(event.getSourceId()); - return spell != null && isControlledBy(spell.getControllerId()) && spell.isInstantOrSorcery(game); - } - - @Override - public String getRule() { - return "Whenever a spell you control deals damage, put a flame counter on {this}."; - } -} diff --git a/Mage.Sets/src/mage/cards/e/EssenceSliver.java b/Mage.Sets/src/mage/cards/e/EssenceSliver.java index 3dc7c6e3dfc..6469c6199bc 100644 --- a/Mage.Sets/src/mage/cards/e/EssenceSliver.java +++ b/Mage.Sets/src/mage/cards/e/EssenceSliver.java @@ -1,30 +1,26 @@ package mage.cards.e; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.Effect; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.common.DealsDamageToAnyTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.GainLifeTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; +import mage.constants.SetTargetPointer; import mage.constants.SubType; import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.players.Player; +import mage.filter.FilterPermanent; import java.util.UUID; -import mage.target.targetpointer.FixedTarget; /** - * * @author cbt33 */ public final class EssenceSliver extends CardImpl { + private static final FilterPermanent filter = new FilterPermanent(SubType.SLIVER, "a Sliver"); + public EssenceSliver(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); this.subtype.add(SubType.SLIVER); @@ -33,7 +29,10 @@ public final class EssenceSliver extends CardImpl { this.toughness = new MageInt(3); // Whenever a Sliver deals damage, its controller gains that much life. - this.addAbility(new EssenceSliverTriggeredAbility()); + this.addAbility(new DealsDamageToAnyTriggeredAbility(Zone.BATTLEFIELD, + new GainLifeTargetEffect(SavedDamageValue.MUCH).setText("its controller gains that much life"), + filter, SetTargetPointer.PLAYER, false, false + )); } @@ -46,71 +45,3 @@ public final class EssenceSliver extends CardImpl { return new EssenceSliver(this); } } - -class EssenceSliverTriggeredAbility extends TriggeredAbilityImpl { - - public EssenceSliverTriggeredAbility() { - super(Zone.BATTLEFIELD, new EssenceSliverEffect(), false); - setTriggerPhrase("Whenever a Sliver deals damage, "); - } - - private EssenceSliverTriggeredAbility(final EssenceSliverTriggeredAbility ability) { - super(ability); - } - - @Override - public EssenceSliverTriggeredAbility copy() { - return new EssenceSliverTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT - || event.getType() == GameEvent.EventType.DAMAGED_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - Permanent sliver = game.getPermanent(event.getSourceId()); - if (sliver != null - && sliver.hasSubtype(SubType.SLIVER, game) - && sliver.getControllerId() != null) { - for (Effect effect : this.getEffects()) { - effect.setValue("damage", event.getAmount()); - effect.setTargetPointer(new FixedTarget(sliver.getControllerId())); - } - return true; - } - return false; - } -} - -class EssenceSliverEffect extends OneShotEffect { - - EssenceSliverEffect() { - super(Outcome.GainLife); - this.staticText = "its controller gains that much life"; - } - - private EssenceSliverEffect(final EssenceSliverEffect effect) { - super(effect); - } - - @Override - public EssenceSliverEffect copy() { - return new EssenceSliverEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controllerOfSliver = game.getPlayer(getTargetPointer().getFirst(game, source)); - if (controllerOfSliver != null) { - int amount = (Integer) getValue("damage"); - if (amount > 0) { - controllerOfSliver.gainLife(amount, game, source); - } - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/e/ExaltedAngel.java b/Mage.Sets/src/mage/cards/e/ExaltedAngel.java index 1722ba88b63..35d4782824b 100644 --- a/Mage.Sets/src/mage/cards/e/ExaltedAngel.java +++ b/Mage.Sets/src/mage/cards/e/ExaltedAngel.java @@ -1,10 +1,10 @@ - package mage.cards.e; -import java.util.UUID; import mage.MageInt; -import mage.abilities.common.DealsDamageGainLifeSourceTriggeredAbility; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.MorphAbility; import mage.cards.CardImpl; @@ -12,14 +12,15 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class ExaltedAngel extends CardImpl { public ExaltedAngel(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{W}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}{W}"); this.subtype.add(SubType.ANGEL); this.power = new MageInt(4); @@ -27,8 +28,10 @@ public final class ExaltedAngel extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); + // Whenever Exalted Angel deals damage, you gain that much life. - this.addAbility(new DealsDamageGainLifeSourceTriggeredAbility()); + this.addAbility(new DealsDamageSourceTriggeredAbility(new GainLifeEffect(SavedDamageValue.MUCH))); + // Morph {2}{W}{W} this.addAbility(new MorphAbility(this, new ManaCostsImpl<>("{2}{W}{W}"))); } diff --git a/Mage.Sets/src/mage/cards/e/ExpeditedInheritance.java b/Mage.Sets/src/mage/cards/e/ExpeditedInheritance.java index 9a3a5351604..9f8176990c1 100644 --- a/Mage.Sets/src/mage/cards/e/ExpeditedInheritance.java +++ b/Mage.Sets/src/mage/cards/e/ExpeditedInheritance.java @@ -1,27 +1,25 @@ package mage.cards.e; -import java.util.Set; -import java.util.UUID; -import mage.MageObjectReference; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.DealtDamageAnyTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; +import mage.filter.StaticFilters; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; +import java.util.Set; +import java.util.UUID; + /** - * * @author kleese */ public final class ExpeditedInheritance extends CardImpl { @@ -30,7 +28,8 @@ public final class ExpeditedInheritance extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{R}{R}"); // Whenever a creature is dealt damage, its controller may exile that many cards from the top of their library. They may play those cards until the end of their next turn. - this.addAbility(new ExpeditedInheritanceTriggeredAbility(new ExpeditedInheritanceExileEffect())); + this.addAbility(new DealtDamageAnyTriggeredAbility(new ExpeditedInheritanceExileEffect(), + StaticFilters.FILTER_PERMANENT_A_CREATURE, SetTargetPointer.PLAYER, false)); } private ExpeditedInheritance(final ExpeditedInheritance card) { @@ -43,52 +42,12 @@ public final class ExpeditedInheritance extends CardImpl { } } -class ExpeditedInheritanceTriggeredAbility extends TriggeredAbilityImpl { - - static final String IMPULSE_DRAW_AMOUNT_KEY = "playerDamage"; - static final String TRIGGERING_CREATURE_KEY = "triggeringCreature"; - - public ExpeditedInheritanceTriggeredAbility(Effect effect) { - super(Zone.BATTLEFIELD, effect); - } - - private ExpeditedInheritanceTriggeredAbility(final ExpeditedInheritanceTriggeredAbility ability) { - super(ability); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - Permanent permanent = game.getPermanent(event.getTargetId()); - if (permanent == null || !permanent.isCreature(game)) { - return false; - } - getEffects().setValue(IMPULSE_DRAW_AMOUNT_KEY, event.getAmount()); - getEffects().setValue(TRIGGERING_CREATURE_KEY, new MageObjectReference(event.getTargetId(), game)); - return true; - } - - @Override - public String getRule() { - return "Whenever a creature is dealt damage, its controller may exile that many cards from the top of their library. They may play those cards until the end of their next turn."; - } - - @Override - public ExpeditedInheritanceTriggeredAbility copy() { - return new ExpeditedInheritanceTriggeredAbility(this); - } -} - class ExpeditedInheritanceExileEffect extends OneShotEffect { ExpeditedInheritanceExileEffect() { super(Outcome.Benefit); - staticText = "Exile that many cards from the top of your library. " + - "Until the end of your next turn, you may play those cards."; + staticText = "its controller may exile that many cards from the top of their library." + + " They may play those cards until the end of their next turn."; } private ExpeditedInheritanceExileEffect(final ExpeditedInheritanceExileEffect effect) { @@ -97,31 +56,26 @@ class ExpeditedInheritanceExileEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Integer impulseDrawAmount = (Integer) this.getValue(ExpeditedInheritanceTriggeredAbility.IMPULSE_DRAW_AMOUNT_KEY); - MageObjectReference mor = (MageObjectReference) this.getValue(ExpeditedInheritanceTriggeredAbility.TRIGGERING_CREATURE_KEY); - if (impulseDrawAmount != null && mor != null) { - Permanent creature = mor.getPermanentOrLKIBattlefield(game); - if (creature != null) { - UUID playerId = creature.getControllerId(); - Player player = game.getPlayer(playerId); - String message = impulseDrawAmount > 1 ? - "Exile " + CardUtil.numberToText(impulseDrawAmount) + " cards from the top of your library. Until the end of your next turn, you may play those cards." - : "Exile the top card of your library. Until the end of your next turn, you may play that card."; - if (player != null && player.chooseUse(outcome, message, source, game)) { - Set cards = player.getLibrary().getTopCards(game, impulseDrawAmount); - if (!cards.isEmpty()) { - player.moveCards(cards, Zone.EXILED, source, game); - for (Card card:cards){ - ContinuousEffect effect = new ExpeditedInheritanceMayPlayEffect(playerId); - effect.setTargetPointer(new FixedTarget(card.getId(), game)); - game.addEffect(effect, source); - } - } + int amount = SavedDamageValue.MANY.calculate(game, source, this); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (amount <= 0 || player == null) { + return false; + } + String message = amount > 1 ? + "Exile " + CardUtil.numberToText(amount) + " cards from the top of your library. Until the end of your next turn, you may play those cards." + : "Exile the top card of your library. Until the end of your next turn, you may play that card."; + if (player.chooseUse(outcome, message, source, game)) { + Set cards = player.getLibrary().getTopCards(game, amount); + if (!cards.isEmpty()) { + player.moveCards(cards, Zone.EXILED, source, game); + for (Card card : cards) { + ContinuousEffect effect = new ExpeditedInheritanceMayPlayEffect(player.getId()); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + game.addEffect(effect, source); } } - return true; } - return false; + return true; } @Override @@ -132,8 +86,8 @@ class ExpeditedInheritanceExileEffect extends OneShotEffect { class ExpeditedInheritanceMayPlayEffect extends AsThoughEffectImpl { - private int triggeredOnTurn = 0; private final UUID cardOwnerId; + private int triggeredOnTurn = 0; ExpeditedInheritanceMayPlayEffect(UUID playerId) { super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit); diff --git a/Mage.Sets/src/mage/cards/e/ExquisiteBlood.java b/Mage.Sets/src/mage/cards/e/ExquisiteBlood.java index c84713ae9c0..62a3d573b9d 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 @@ import mage.game.events.GameEvent.EventType; 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 final class ExquisiteBlood extends CardImpl { 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/e/EzioAuditoreDaFirenze.java b/Mage.Sets/src/mage/cards/e/EzioAuditoreDaFirenze.java new file mode 100644 index 00000000000..e95c8337892 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EzioAuditoreDaFirenze.java @@ -0,0 +1,89 @@ +package mage.cards.e; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.DoWhenCostPaid; +import mage.abilities.effects.common.LoseGameTargetPlayerEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledSpellsEffect; +import mage.abilities.keyword.FreerunningAbility; +import mage.constants.*; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.filter.common.FilterNonlandCard; +import mage.game.Game; +import mage.players.Player; + +/** + * + * @author Kr4u7 + */ +public final class EzioAuditoreDaFirenze extends CardImpl { + + private static final FilterNonlandCard filter = new FilterNonlandCard("assassin spells you cast"); + + static { + filter.add(SubType.ASSASSIN.getPredicate()); + } + + public EzioAuditoreDaFirenze(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Menace + this.addAbility(new MenaceAbility(false)); + + // Assassin spells you cast have freerunning {B}{B}. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GainAbilityControlledSpellsEffect(new FreerunningAbility("{B}{B}"), filter))); + // Whenever Ezio deals combat damage to a player, you may pay {W}{U}{B}{R}{G} if that player has 10 or less life. When you do, that player loses the game. + this.addAbility( + new DealsCombatDamageToAPlayerTriggeredAbility( + new ConditionalOneShotEffect(new DoWhenCostPaid( + new ReflexiveTriggeredAbility(new LoseGameTargetPlayerEffect(), false, "that player loses the game."), new ManaCostsImpl<>("{W}{U}{B}{R}{G}"), "you may pay {W}{U}{B}{R}{G} if that player has 10 or less life. When you do, that player loses the game." + ), EzioAuditoreDaFirenzeCondition.instance, "you may pay {W}{U}{B}{R}{G} if that player has 10 or less life. When you do, that player loses the game"), false, true + ) + ); + + } + + private EzioAuditoreDaFirenze(final EzioAuditoreDaFirenze card) { + super(card); + } + + @Override + public EzioAuditoreDaFirenze copy() { + return new EzioAuditoreDaFirenze(this); + } + +} + +enum EzioAuditoreDaFirenzeCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + UUID playerId = source.getEffects().get(0).getTargetPointer().getFirst(game, source); + if (playerId == null) { + return false; + } + Player player = game.getPlayer(playerId); + return player != null && player.getLife() <= 10; + } + + @Override + public String toString() { + return "that player has 10 or less life"; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FaerieBladecrafter.java b/Mage.Sets/src/mage/cards/f/FaerieBladecrafter.java index 88063caad83..a1292be49df 100644 --- a/Mage.Sets/src/mage/cards/f/FaerieBladecrafter.java +++ b/Mage.Sets/src/mage/cards/f/FaerieBladecrafter.java @@ -2,7 +2,7 @@ package mage.cards.f; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.DiesSourceTriggeredAbility; import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; import mage.abilities.effects.common.GainLifeEffect; @@ -38,7 +38,7 @@ public final class FaerieBladecrafter extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Whenever one or more Faeries you control deal combat damage to a player, put a +1/+1 counter on Faerie Bladecrafter. - this.addAbility(new DealCombatDamageControlledTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filter)); + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filter)); // When Faerie Bladecrafter dies, each opponent loses X life and you gain X life, where X is its power. Ability ability = new DiesSourceTriggeredAbility(new LoseLifeOpponentsEffect(SourcePermanentPowerValue.NOT_NEGATIVE).setText("each opponent loses X life")); diff --git a/Mage.Sets/src/mage/cards/f/FallOfCairAndros.java b/Mage.Sets/src/mage/cards/f/FallOfCairAndros.java index 26d3c005deb..b583768dffd 100644 --- a/Mage.Sets/src/mage/cards/f/FallOfCairAndros.java +++ b/Mage.Sets/src/mage/cards/f/FallOfCairAndros.java @@ -1,6 +1,7 @@ package mage.cards.f; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; @@ -48,7 +49,7 @@ public final class FallOfCairAndros extends CardImpl { } } -class FallOfCairAndrosTriggeredAbility extends TriggeredAbilityImpl { +class FallOfCairAndrosTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { FallOfCairAndrosTriggeredAbility() { super(Zone.BATTLEFIELD, null); @@ -70,6 +71,7 @@ class FallOfCairAndrosTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { + // all events in the batch are always relevant if triggers at all Permanent permanent = game.getPermanent(event.getTargetId()); if (permanent == null || !permanent.isCreature(game) || !game.getOpponents(getControllerId()).contains(permanent.getControllerId())) { diff --git a/Mage.Sets/src/mage/cards/f/FelineSovereign.java b/Mage.Sets/src/mage/cards/f/FelineSovereign.java index 305f05d98fa..c682dac3761 100644 --- a/Mage.Sets/src/mage/cards/f/FelineSovereign.java +++ b/Mage.Sets/src/mage/cards/f/FelineSovereign.java @@ -2,7 +2,7 @@ package mage.cards.f; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DestroyTargetEffect; @@ -65,7 +65,7 @@ public final class FelineSovereign extends CardImpl { } } -class FelineSovereignTriggeredAbility extends DealCombatDamageControlledTriggeredAbility { +class FelineSovereignTriggeredAbility extends OneOrMoreCombatDamagePlayerTriggeredAbility { private static final FilterCreaturePermanent catFilter = new FilterCreaturePermanent(SubType.CAT, "Cats"); diff --git a/Mage.Sets/src/mage/cards/f/FelixFiveBoots.java b/Mage.Sets/src/mage/cards/f/FelixFiveBoots.java index c4e9fb401f0..a2055c0eca2 100644 --- a/Mage.Sets/src/mage/cards/f/FelixFiveBoots.java +++ b/Mage.Sets/src/mage/cards/f/FelixFiveBoots.java @@ -2,6 +2,8 @@ package mage.cards.f; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; +import mage.abilities.TriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.ReplacementEffectImpl; @@ -16,7 +18,6 @@ import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; import mage.game.events.NumberOfTriggersEvent; import mage.game.permanent.Permanent; -import mage.players.Player; import java.util.UUID; @@ -77,49 +78,38 @@ class FelixFiveBootsEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - NumberOfTriggersEvent numberOfTriggersEvent = (NumberOfTriggersEvent) event; - Permanent sourcePermanent = game.getPermanent(numberOfTriggersEvent.getSourceId()); - if (sourcePermanent == null || !sourcePermanent.isControlledBy(source.getControllerId())) { + // Permanent whose ability is being triggered (and will be retriggered) + Permanent triggeringPermanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); + if (triggeringPermanent == null || !triggeringPermanent.isControlledBy(source.getControllerId())) { return false; } - - GameEvent sourceEvent = numberOfTriggersEvent.getSourceEvent(); - if (sourceEvent == null) { - return false; - } - + GameEvent sourceEvent = ((NumberOfTriggersEvent) event).getSourceEvent(); + // check all damage events and damage batch events if (sourceEvent instanceof DamagedEvent) { return checkDamagedEvent((DamagedEvent) sourceEvent, source.getControllerId(), game); } else if (sourceEvent instanceof BatchEvent) { - for (Object singleEventAsObject : ((BatchEvent) sourceEvent).getEvents()) { - if (singleEventAsObject instanceof DamagedEvent - && checkDamagedEvent((DamagedEvent) singleEventAsObject, source.getControllerId(), game) - ) { - // For batch events, if one of the event inside the condition match the condition, - // the effect applies to the whole batch events. + TriggeredAbility sourceTrigger = ((NumberOfTriggersEvent) event).getSourceTrigger(); + for (Object o : sourceTrigger instanceof BatchTriggeredAbility + ? ((BatchTriggeredAbility) sourceTrigger).getFilteredEvents((BatchEvent) sourceEvent, game) + : ((BatchEvent) sourceEvent).getEvents()) { + if (o instanceof DamagedEvent && checkDamagedEvent((DamagedEvent) o, source.getControllerId(), game)) { return true; } } } - return false; } // Checks that a given DamagedEvent matches with // "If a creature you control dealing combat damage to a player" private static boolean checkDamagedEvent(DamagedEvent event, UUID controllerId, Game game) { - if (event == null) { + if (!event.isCombatDamage() || game.getPlayer(event.getTargetId()) == null) { return false; } - UUID sourceId = event.getSourceId(); - Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(sourceId); - UUID targetId = event.getTargetId(); - Player playerDealtDamage = game.getPlayer(targetId); + Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); return sourcePermanent != null && sourcePermanent.isCreature(game) - && sourcePermanent.isControlledBy(controllerId) - && event.isCombatDamage() - && playerDealtDamage != null; + && sourcePermanent.isControlledBy(controllerId); } @Override diff --git a/Mage.Sets/src/mage/cards/f/FeywildVisitor.java b/Mage.Sets/src/mage/cards/f/FeywildVisitor.java index 2ae8b0c1b1b..3988d4fbf3a 100644 --- a/Mage.Sets/src/mage/cards/f/FeywildVisitor.java +++ b/Mage.Sets/src/mage/cards/f/FeywildVisitor.java @@ -1,6 +1,6 @@ package mage.cards.f; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.continuous.GainAbilityAllEffect; @@ -25,7 +25,7 @@ public final class FeywildVisitor extends CardImpl { // Commander creatures you own have "Whenever one or more nontoken creatures you control deal combat damage to a player, you create a 1/1 blue Faerie Dragon creature token with flying." this.addAbility(new SimpleStaticAbility(new GainAbilityAllEffect( - new DealCombatDamageControlledTriggeredAbility(new CreateTokenEffect(new FaerieDragonToken()), StaticFilters.FILTER_CREATURE_NON_TOKEN) + new OneOrMoreCombatDamagePlayerTriggeredAbility(new CreateTokenEffect(new FaerieDragonToken()), StaticFilters.FILTER_CREATURE_NON_TOKEN) .setTriggerPhrase("Whenever one or more nontoken creatures you control deal combat damage to a player, you "), Duration.WhileOnBattlefield, StaticFilters.FILTER_CREATURES_OWNED_COMMANDER diff --git a/Mage.Sets/src/mage/cards/f/Fiendlash.java b/Mage.Sets/src/mage/cards/f/Fiendlash.java index b7aa24b320e..93336947b8f 100644 --- a/Mage.Sets/src/mage/cards/f/Fiendlash.java +++ b/Mage.Sets/src/mage/cards/f/Fiendlash.java @@ -1,8 +1,7 @@ package mage.cards.f; -import java.util.UUID; - import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCostsImpl; @@ -13,22 +12,18 @@ import mage.abilities.keyword.EquipAbility; import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AttachmentType; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.constants.Zone; +import mage.constants.*; import mage.game.Game; -import mage.game.events.DamagedEvent; -import mage.game.events.DamagedBatchForPermanentsEvent; +import mage.game.events.DamagedPermanentEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetPlayerOrPlaneswalker; +import java.util.UUID; + /** - * * @author zeffirojoe */ public final class Fiendlash extends CardImpl { @@ -44,8 +39,7 @@ public final class Fiendlash extends CardImpl { .setText("and has reach")); this.addAbility(staticAbility); - // Whenever equipped creature is dealt damage, it deals damage equal to its - // power to target player or planeswalker. + // Whenever equipped creature is dealt damage, it deals damage equal to its power to target player or planeswalker. this.addAbility(new FiendlashTriggeredAbility()); // Equip {2}{R} @@ -62,7 +56,7 @@ public final class Fiendlash extends CardImpl { } } -class FiendlashTriggeredAbility extends TriggeredAbilityImpl { +class FiendlashTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { FiendlashTriggeredAbility() { super(Zone.BATTLEFIELD, new FiendlashEffect(), false); @@ -80,36 +74,17 @@ class FiendlashTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_PERMANENTS; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override public boolean checkTrigger(GameEvent event, Game game) { - Permanent equipment = game.getPermanent(this.getSourceId()); - if (equipment == null) { + Permanent equipment = getSourcePermanentIfItStillExists(game); + if (equipment == null || !event.getTargetId().equals(equipment.getAttachedTo())) { return false; } - - UUID attachedCreature = equipment.getAttachedTo(); - if (attachedCreature == null) { - return false; - } - - game.getState().setValue("Fiendlash" + equipment.getId(), attachedCreature); - - DamagedBatchForPermanentsEvent dEvent = (DamagedBatchForPermanentsEvent) event; - for (DamagedEvent damagedEvent : dEvent.getEvents()) { - UUID targetID = damagedEvent.getTargetId(); - if (targetID == null) { - continue; - } - - if (targetID == attachedCreature) { - return true; - } - } - - return false; + game.getState().setValue("Fiendlash" + equipment.getId(), equipment.getAttachedTo()); + return true; } @Override @@ -147,7 +122,7 @@ class FiendlashEffect extends OneShotEffect { } Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (permanent != null && (permanent.isPlaneswalker(game))) { + if (permanent != null) { permanent.damage(damage, creature.getId(), source, game); return true; } diff --git a/Mage.Sets/src/mage/cards/f/FilthyCur.java b/Mage.Sets/src/mage/cards/f/FilthyCur.java index d983778da5d..05129425dc9 100644 --- a/Mage.Sets/src/mage/cards/f/FilthyCur.java +++ b/Mage.Sets/src/mage/cards/f/FilthyCur.java @@ -1,35 +1,32 @@ - package mage.cards.f; -import java.util.UUID; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.Effect; +import mage.abilities.common.DealtDamageToSourceTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.common.LoseLifeSourceControllerEffect; import mage.cards.CardImpl; 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.game.events.GameEvent.EventType; + +import java.util.UUID; /** - * * @author cbt33 */ public final class FilthyCur extends CardImpl { public FilthyCur(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); this.subtype.add(SubType.DOG); this.power = new MageInt(2); this.toughness = new MageInt(2); // Whenever Filthy Cur is dealt damage, you lose that much life. - this.addAbility(new DealtDamageLoseLifeTriggeredAbility(Zone.BATTLEFIELD, new LoseLifeSourceControllerEffect(0), false)); + this.addAbility(new DealtDamageToSourceTriggeredAbility( + new LoseLifeSourceControllerEffect(SavedDamageValue.MUCH), false + )); } @@ -42,39 +39,3 @@ public final class FilthyCur extends CardImpl { return new FilthyCur(this); } } - -class DealtDamageLoseLifeTriggeredAbility extends TriggeredAbilityImpl { - - public DealtDamageLoseLifeTriggeredAbility(Zone zone, Effect effect, boolean optional) { - super(zone, effect, optional); - } - - private DealtDamageLoseLifeTriggeredAbility(final DealtDamageLoseLifeTriggeredAbility ability) { - super(ability); - } - - @Override - public DealtDamageLoseLifeTriggeredAbility copy() { - return new DealtDamageLoseLifeTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(this.sourceId)) { - this.getEffects().clear(); - this.addEffect(new LoseLifeSourceControllerEffect(event.getAmount())); - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever {this} is dealt damage, you lose that much life."; - } -} diff --git a/Mage.Sets/src/mage/cards/f/FiveAlarmFire.java b/Mage.Sets/src/mage/cards/f/FiveAlarmFire.java index 5395ff067aa..b31dea7fa35 100644 --- a/Mage.Sets/src/mage/cards/f/FiveAlarmFire.java +++ b/Mage.Sets/src/mage/cards/f/FiveAlarmFire.java @@ -1,8 +1,7 @@ - package mage.cards.f; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.DealsDamageToAnyTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.effects.common.DamageTargetEffect; @@ -10,33 +9,30 @@ import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SetTargetPointer; import mage.constants.Zone; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.game.Game; -import mage.game.events.DamagedEvent; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; +import mage.filter.StaticFilters; import mage.target.common.TargetAnyTarget; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; /** - * * @author Plopman */ public final class FiveAlarmFire extends CardImpl { public FiveAlarmFire(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{1}{R}{R}"); - + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}{R}"); + // Whenever a creature you control deals combat damage, put a blaze counter on Five-Alarm Fire. + this.addAbility(new DealsDamageToAnyTriggeredAbility(Zone.BATTLEFIELD, + new AddCountersSourceEffect(CounterType.BLAZE.createInstance()), + StaticFilters.FILTER_CONTROLLED_A_CREATURE, + SetTargetPointer.NONE, true, false + )); - //Whenever a creature you control deals combat damage, put a blaze counter on Five-Alarm Fire. - this.addAbility(new FiveAlarmFireTriggeredAbility()); - //Remove five blaze counters from Five-Alarm Fire: Five-Alarm Fire deals 5 damage to any target. + // Remove five blaze counters from Five-Alarm Fire: Five-Alarm Fire deals 5 damage to any target. Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(5, "it"), new RemoveCountersSourceCost(CounterType.BLAZE.createInstance(5))); ability.addTarget(new TargetAnyTarget()); this.addAbility(ability); @@ -51,54 +47,3 @@ public final class FiveAlarmFire extends CardImpl { return new FiveAlarmFire(this); } } - - -class FiveAlarmFireTriggeredAbility extends TriggeredAbilityImpl { - - // Because a creature that is blocked by multiple creatures it deals damage to, only causes to add one counter to , - // it's neccessary to remember which creature already triggered - Set triggeringCreatures = new HashSet<>(); - - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); - - public FiveAlarmFireTriggeredAbility() { - super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.BLAZE.createInstance()), false); - setTriggerPhrase("Whenever a creature you control deals combat damage, "); - } - - private FiveAlarmFireTriggeredAbility(final FiveAlarmFireTriggeredAbility ability) { - super(ability); - triggeringCreatures.addAll(ability.triggeringCreatures); - } - - @Override - public FiveAlarmFireTriggeredAbility copy() { - return new FiveAlarmFireTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT - || event.getType() == GameEvent.EventType.DAMAGED_PLAYER - || event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_PRE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.DAMAGED_PERMANENT - || event.getType() == GameEvent.EventType.DAMAGED_PLAYER) { - if (((DamagedEvent) event).isCombatDamage() && !triggeringCreatures.contains(event.getSourceId())) { - Permanent permanent = game.getPermanent(event.getSourceId()); - if (permanent != null && filter.match(permanent, controllerId, this, game)) { - triggeringCreatures.add(event.getSourceId()); - return true; - } - } - } - // reset the remembered creatures for every combat damage step - if (event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_PRE) { - triggeringCreatures.clear(); - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/f/FlameChanneler.java b/Mage.Sets/src/mage/cards/f/FlameChanneler.java index 8bce9da31b1..363d8c7c634 100644 --- a/Mage.Sets/src/mage/cards/f/FlameChanneler.java +++ b/Mage.Sets/src/mage/cards/f/FlameChanneler.java @@ -1,7 +1,7 @@ package mage.cards.f; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SpellControlledDealsDamageTriggeredAbility; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; @@ -9,9 +9,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.game.stack.Spell; +import mage.filter.StaticFilters; import java.util.UUID; @@ -31,7 +29,9 @@ public final class FlameChanneler extends CardImpl { // When a spell you control deals damage, transform Flame Channeler. this.addAbility(new TransformAbility()); - this.addAbility(new FlameChannelerTriggeredAbility()); + this.addAbility(new SpellControlledDealsDamageTriggeredAbility(Zone.BATTLEFIELD, + new TransformSourceEffect(), StaticFilters.FILTER_SPELL, false + ).setTriggerPhrase("When a spell you control deals damage, ")); } private FlameChanneler(final FlameChanneler card) { @@ -43,36 +43,3 @@ public final class FlameChanneler extends CardImpl { return new FlameChanneler(this); } } - -class FlameChannelerTriggeredAbility extends TriggeredAbilityImpl { - - FlameChannelerTriggeredAbility() { - super(Zone.BATTLEFIELD, new TransformSourceEffect()); - } - - private FlameChannelerTriggeredAbility(final FlameChannelerTriggeredAbility ability) { - super(ability); - } - - @Override - public FlameChannelerTriggeredAbility copy() { - return new FlameChannelerTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT - || event.getType() == GameEvent.EventType.DAMAGED_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - Spell spell = game.getSpellOrLKIStack(event.getSourceId()); - return spell != null && isControlledBy(spell.getControllerId()) && spell.isInstantOrSorcery(game); - } - - @Override - public String getRule() { - return "When a spell you control deals damage, transform {this}."; - } -} diff --git a/Mage.Sets/src/mage/cards/f/FlorianVoldarenScion.java b/Mage.Sets/src/mage/cards/f/FlorianVoldarenScion.java index 18f0f661296..2fc86e1dcbe 100644 --- a/Mage.Sets/src/mage/cards/f/FlorianVoldarenScion.java +++ b/Mage.Sets/src/mage/cards/f/FlorianVoldarenScion.java @@ -3,6 +3,9 @@ package mage.cards.f; import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.hint.ValueHint; import mage.abilities.triggers.BeginningOfPostcombatMainTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect; @@ -35,7 +38,7 @@ public final class FlorianVoldarenScion extends CardImpl { // At the beginning of your postcombat main phase, look at the top X cards of your library, where X is the total amount of life your opponents lost this turn. // Exile one of those cards and put the rest on the bottom of your library in a random order. You may play the exiled card this turn. - this.addAbility(new BeginningOfPostcombatMainTriggeredAbility(new FlorianVoldarenScionEffect(), false)); + this.addAbility(new BeginningOfPostcombatMainTriggeredAbility(new FlorianVoldarenScionEffect(), false).addHint(FlorianValue.getHint())); } private FlorianVoldarenScion(final FlorianVoldarenScion card) { @@ -48,6 +51,35 @@ public final class FlorianVoldarenScion extends CardImpl { } } +enum FlorianValue implements DynamicValue { + instance; + private static final ValueHint hint = new ValueHint("Total amount of life your opponents lost this turn", instance); + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + Player controller = game.getPlayer(sourceAbility.getControllerId()); + PlayerLostLifeWatcher watcher = game.getState().getWatcher(PlayerLostLifeWatcher.class); + if (controller != null && watcher != null) { + return watcher.getAllOppLifeLost(controller.getId(), game); + } + return 0; + } + + @Override + public FlorianValue copy() { + return instance; + } + + @Override + public String getMessage() { + return ""; + } + + public static ValueHint getHint() { + return hint; + } +} + class FlorianVoldarenScionEffect extends OneShotEffect { FlorianVoldarenScionEffect() { diff --git a/Mage.Sets/src/mage/cards/f/FormlessGenesis.java b/Mage.Sets/src/mage/cards/f/FormlessGenesis.java new file mode 100644 index 00000000000..67ae762731f --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FormlessGenesis.java @@ -0,0 +1,84 @@ +package mage.cards.f; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.ChangelingAbility; +import mage.abilities.keyword.RetraceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.token.ShapeshifterDeathtouchToken; +import mage.players.Player; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FormlessGenesis extends CardImpl { + + private static final Hint hint = new ValueHint( + "Lands in your graveyard", new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_LAND) + ); + + public FormlessGenesis(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.KINDRED, CardType.SORCERY}, "{2}{G}"); + + this.subtype.add(SubType.SHAPESHIFTER); + + // Changeling + this.addAbility(new ChangelingAbility()); + + // Create an X/X colorless Shapeshifter creature token with changeling and deathtouch, where X is the number of land cards in your graveyard. + this.getSpellAbility().addEffect(new FormlessGenesisEffect()); + this.getSpellAbility().addHint(hint); + + // Retrace + this.addAbility(new RetraceAbility(this)); + } + + private FormlessGenesis(final FormlessGenesis card) { + super(card); + } + + @Override + public FormlessGenesis copy() { + return new FormlessGenesis(this); + } +} + +class FormlessGenesisEffect extends OneShotEffect { + + FormlessGenesisEffect() { + super(Outcome.Benefit); + staticText = "create an X/X colorless Shapeshifter creature token with changeling and deathtouch, " + + "where X is the number of land cards in your graveyard"; + } + + private FormlessGenesisEffect(final FormlessGenesisEffect effect) { + super(effect); + } + + @Override + public FormlessGenesisEffect copy() { + return new FormlessGenesisEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + int amount = Optional + .ofNullable(game.getPlayer(source.getControllerId())) + .map(Player::getGraveyard) + .map(g -> g.count(StaticFilters.FILTER_CARD_LAND, game)) + .orElse(0); + return new ShapeshifterDeathtouchToken(amount).putOntoBattlefield(1, game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/f/ForthEorlingas.java b/Mage.Sets/src/mage/cards/f/ForthEorlingas.java index af939cbdd69..aeef5bba216 100644 --- a/Mage.Sets/src/mage/cards/f/ForthEorlingas.java +++ b/Mage.Sets/src/mage/cards/f/ForthEorlingas.java @@ -1,7 +1,6 @@ package mage.cards.f; -import java.util.UUID; - +import mage.abilities.BatchTriggeredAbility; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.effects.common.BecomesMonarchSourceEffect; @@ -13,14 +12,15 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.game.Game; -import mage.game.events.DamagedEvent; import mage.game.events.DamagedBatchForPlayersEvent; +import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.permanent.token.HumanKnightToken; +import java.util.UUID; + /** - * * @author Susucr */ public final class ForthEorlingas extends CardImpl { @@ -50,7 +50,7 @@ public final class ForthEorlingas extends CardImpl { } } -class ForthEorlingasTriggeredAbility extends DelayedTriggeredAbility { +class ForthEorlingasTriggeredAbility extends DelayedTriggeredAbility implements BatchTriggeredAbility { public ForthEorlingasTriggeredAbility() { super(new BecomesMonarchSourceEffect(), Duration.EndOfTurn, false); @@ -68,18 +68,19 @@ class ForthEorlingasTriggeredAbility extends DelayedTriggeredAbility { } @Override - public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchForPlayersEvent dEvent = (DamagedBatchForPlayersEvent) event; - for (DamagedEvent damagedEvent : dEvent.getEvents()) { - if (!damagedEvent.isCombatDamage()) { - continue; - } - Permanent permanent = game.getPermanent(damagedEvent.getSourceId()); - if (permanent != null && permanent.isControlledBy(getControllerId())) { - return true; - } + public boolean checkEvent(DamagedPlayerEvent event, Game game) { + if (!event.isCombatDamage()) { + return false; } - return false; + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); + return permanent != null + && permanent.isCreature(game) + && permanent.isControlledBy(getControllerId()); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return !getFilteredEvents((DamagedBatchForPlayersEvent) event, game).isEmpty(); } @Override diff --git a/Mage.Sets/src/mage/cards/f/FranciscoFowlMarauder.java b/Mage.Sets/src/mage/cards/f/FranciscoFowlMarauder.java index bbe283cceef..9aa0cc81928 100644 --- a/Mage.Sets/src/mage/cards/f/FranciscoFowlMarauder.java +++ b/Mage.Sets/src/mage/cards/f/FranciscoFowlMarauder.java @@ -2,7 +2,7 @@ package mage.cards.f; import mage.MageInt; import mage.abilities.common.CantBlockAbility; -import mage.abilities.common.OneOrMoreDealDamageTriggeredAbility; +import mage.abilities.common.OneOrMoreDamagePlayerTriggeredAbility; import mage.abilities.effects.keyword.ExploreSourceEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.PartnerAbility; @@ -38,7 +38,7 @@ public final class FranciscoFowlMarauder extends CardImpl { this.addAbility(new CantBlockAbility()); // Whenever one or more Pirates you control deal damage to a player, Francisco explores. - this.addAbility(new OneOrMoreDealDamageTriggeredAbility( + this.addAbility(new OneOrMoreDamagePlayerTriggeredAbility( new ExploreSourceEffect(false, "{this}"), filter, false, true )); diff --git a/Mage.Sets/src/mage/cards/f/FranticScapegoat.java b/Mage.Sets/src/mage/cards/f/FranticScapegoat.java index feb8fe2daeb..9ced7f745c1 100644 --- a/Mage.Sets/src/mage/cards/f/FranticScapegoat.java +++ b/Mage.Sets/src/mage/cards/f/FranticScapegoat.java @@ -3,6 +3,7 @@ package mage.cards.f; import mage.MageInt; import mage.MageObjectReference; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.OneShotEffect; @@ -64,7 +65,7 @@ public final class FranticScapegoat extends CardImpl { } //Based on Lightmine Field -class FranticScapegoatTriggeredAbility extends TriggeredAbilityImpl { +class FranticScapegoatTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { FranticScapegoatTriggeredAbility() { super(Zone.BATTLEFIELD, new FranticScapegoatSuspectEffect(), true); @@ -82,18 +83,24 @@ class FranticScapegoatTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkInterveningIfClause(Game game) { Permanent source = getSourcePermanentIfItStillExists(game); - return (source != null && source.isSuspected()); + return source != null && source.isSuspected(); + } + + @Override + public boolean checkEvent(ZoneChangeEvent event, Game game) { + if (event.getToZone() != Zone.BATTLEFIELD) { + return false; + } + Permanent permanent = event.getTarget(); + return permanent != null && permanent.isCreature(game) && permanent.isControlledBy(getControllerId()); } @Override public boolean checkTrigger(GameEvent event, Game game) { - ZoneChangeBatchEvent zEvent = (ZoneChangeBatchEvent) event; - Set enteringCreatures = zEvent.getEvents().stream() - .filter(z -> z.getToZone() == Zone.BATTLEFIELD) - .filter(z -> this.controllerId.equals(z.getPlayerId())) + Set enteringCreatures = getFilteredEvents((ZoneChangeBatchEvent) event, game) + .stream() .map(ZoneChangeEvent::getTarget) .filter(Objects::nonNull) - .filter(permanent -> permanent.isCreature(game)) .map(p -> new MageObjectReference(p, game)) .collect(Collectors.toSet()); if (!enteringCreatures.isEmpty()) { diff --git a/Mage.Sets/src/mage/cards/f/FrozenSolid.java b/Mage.Sets/src/mage/cards/f/FrozenSolid.java index 90e30f24e3f..44367c26837 100644 --- a/Mage.Sets/src/mage/cards/f/FrozenSolid.java +++ b/Mage.Sets/src/mage/cards/f/FrozenSolid.java @@ -14,7 +14,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Outcome; -import mage.constants.Zone; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; @@ -37,7 +36,7 @@ public final class FrozenSolid extends CardImpl { // Enchanted creature doesn't untap during its controller's untap step. this.addAbility(new SimpleStaticAbility(new DontUntapInControllersUntapStepEnchantedEffect())); // When enchanted creature is dealt damage, destroy it. - this.addAbility(new DealtDamageAttachedTriggeredAbility(new DestroyAttachedToEffect("it"), false) + this.addAbility(new DealtDamageAttachedTriggeredAbility(new DestroyAttachedToEffect("it")) .setTriggerPhrase("When enchanted creature is dealt damage, ")); } diff --git a/Mage.Sets/src/mage/cards/g/GenjuOfTheFields.java b/Mage.Sets/src/mage/cards/g/GenjuOfTheFields.java index 18551f9b1f2..14455a304aa 100644 --- a/Mage.Sets/src/mage/cards/g/GenjuOfTheFields.java +++ b/Mage.Sets/src/mage/cards/g/GenjuOfTheFields.java @@ -1,15 +1,15 @@ - package mage.cards.g; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.DealsDamageGainLifeSourceTriggeredAbility; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; import mage.abilities.common.DiesAttachedTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.Effect; import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.ReturnToHandSourceEffect; import mage.abilities.effects.common.continuous.BecomesCreatureAttachedWithActivatedAbilityOrSpellEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; @@ -22,8 +22,9 @@ import mage.game.permanent.token.TokenImpl; import mage.target.TargetPermanent; import mage.target.common.TargetLandPermanent; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class GenjuOfTheFields extends CardImpl { @@ -31,7 +32,7 @@ public final class GenjuOfTheFields extends CardImpl { private static final FilterLandPermanent FILTER = new FilterLandPermanent(SubType.PLAINS, "Plains"); public GenjuOfTheFields(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{W}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{W}"); this.subtype.add(SubType.AURA); // Enchant Plains @@ -45,7 +46,7 @@ public final class GenjuOfTheFields extends CardImpl { Effect effect = new BecomesCreatureAttachedWithActivatedAbilityOrSpellEffect(new SpiritToken(), "Until end of turn, enchanted Plains becomes a 2/5 white Spirit creature", Duration.EndOfTurn); Ability ability2 = new SimpleActivatedAbility(effect, new GenericManaCost(2)); - effect = new GainAbilityAttachedEffect(new DealsDamageGainLifeSourceTriggeredAbility(), AttachmentType.AURA, Duration.EndOfTurn); + effect = new GainAbilityAttachedEffect(new DealsDamageSourceTriggeredAbility(new GainLifeEffect(SavedDamageValue.MUCH)), AttachmentType.AURA, Duration.EndOfTurn); effect.setText("with \"Whenever this creature deals damage, its controller gains that much life.\" It's still a land"); ability2.addEffect(effect); this.addAbility(ability2); @@ -76,6 +77,7 @@ public final class GenjuOfTheFields extends CardImpl { power = new MageInt(2); toughness = new MageInt(5); } + private SpiritToken(final SpiritToken token) { super(token); } diff --git a/Mage.Sets/src/mage/cards/g/GlitchInterpreter.java b/Mage.Sets/src/mage/cards/g/GlitchInterpreter.java new file mode 100644 index 00000000000..07ca760bfc9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GlitchInterpreter.java @@ -0,0 +1,66 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.ReturnToHandSourceEffect; +import mage.abilities.effects.keyword.ManifestDreadEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.card.FaceDownPredicate; +import mage.filter.predicate.mageobject.ColorlessPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GlitchInterpreter extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent("you control no face-down permanents"); + private static final FilterCreaturePermanent filter2 = new FilterCreaturePermanent("colorless creatures"); + + static { + filter.add(FaceDownPredicate.instance); + filter2.add(ColorlessPredicate.instance); + } + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter, ComparisonType.EQUAL_TO, 0); + + public GlitchInterpreter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + 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()) + .withRuleTextReplacement(false).withInterveningIf(condition); + ability.addEffect(new ManifestDreadEffect().concatBy("and")); + this.addAbility(ability); + + // Whenever one or more colorless creatures you control deal combat damage to a player, draw a card. + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility(new DrawCardSourceControllerEffect(1), filter2)); + } + + private GlitchInterpreter(final GlitchInterpreter card) { + super(card); + } + + @Override + public GlitchInterpreter copy() { + return new GlitchInterpreter(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GontiCannyAcquisitor.java b/Mage.Sets/src/mage/cards/g/GontiCannyAcquisitor.java index 00586428712..90796de334d 100644 --- a/Mage.Sets/src/mage/cards/g/GontiCannyAcquisitor.java +++ b/Mage.Sets/src/mage/cards/g/GontiCannyAcquisitor.java @@ -1,7 +1,7 @@ package mage.cards.g; import mage.MageInt; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.ExileFaceDownTopNLibraryYouMayPlayAsLongAsExiledTargetEffect; import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; @@ -36,7 +36,7 @@ public final class GontiCannyAcquisitor extends CardImpl { this.addAbility(new SimpleStaticAbility(new SpellsCostReductionControllerEffect(filter, 1))); // Whenever one or more creatures you control deal combat damage to a player, look at the top card of that player's library, then exile it face down. You may play that card for as long as it remains exiled, and mana of any type can be spent to cast that spell. - this.addAbility(new DealCombatDamageControlledTriggeredAbility( + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility( new ExileFaceDownTopNLibraryYouMayPlayAsLongAsExiledTargetEffect(false, CastManaAdjustment.AS_THOUGH_ANY_MANA_TYPE) .setText("look at the top card of that player's library, then exile it face down. " + "You may play that card for as long as it remains exiled, and mana of any type can be spent to cast that spell"), diff --git a/Mage.Sets/src/mage/cards/g/GontisMachinations.java b/Mage.Sets/src/mage/cards/g/GontisMachinations.java index 58ea48c619f..3503bdb83f1 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.abilities.effects.common.counter.GetEnergyCountersControllerEffect; 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 final class GontisMachinations extends CardImpl { 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 final class GontisMachinations extends CardImpl { 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/g/GoroGoroAndSatoru.java b/Mage.Sets/src/mage/cards/g/GoroGoroAndSatoru.java index b3e62bbeda4..47daab7f61e 100644 --- a/Mage.Sets/src/mage/cards/g/GoroGoroAndSatoru.java +++ b/Mage.Sets/src/mage/cards/g/GoroGoroAndSatoru.java @@ -1,7 +1,7 @@ package mage.cards.g; import mage.MageInt; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.CreateTokenEffect; @@ -40,7 +40,7 @@ public final class GoroGoroAndSatoru extends CardImpl { // Whenever one or more creatures you control that entered the battlefield this turn deal combat damage to a // player, create a 5/5 red Dragon Spirit creature token with flying. - this.addAbility(new DealCombatDamageControlledTriggeredAbility( + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility( new CreateTokenEffect(new DragonSpiritToken()), filter )); diff --git a/Mage.Sets/src/mage/cards/g/GossipsTalent.java b/Mage.Sets/src/mage/cards/g/GossipsTalent.java index c178159b7da..0d975785caa 100644 --- a/Mage.Sets/src/mage/cards/g/GossipsTalent.java +++ b/Mage.Sets/src/mage/cards/g/GossipsTalent.java @@ -2,7 +2,7 @@ package mage.cards.g; import mage.abilities.Ability; import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.ExileThenReturnTargetEffect; @@ -59,7 +59,7 @@ public final class GossipsTalent extends CardImpl { // Whenever a creature you control deals combat damage to a player, you may exile it, then return it to the battlefield under its owner's control. this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect( - new DealCombatDamageControlledTriggeredAbility( + new OneOrMoreCombatDamagePlayerTriggeredAbility( Zone.BATTLEFIELD, new ExileThenReturnTargetEffect(false, false) .setText("exile it, then return it to the battlefield under its owner's control"), diff --git a/Mage.Sets/src/mage/cards/g/GrazilaxxIllithidScholar.java b/Mage.Sets/src/mage/cards/g/GrazilaxxIllithidScholar.java index 1ae41f2dc84..f784ebb1698 100644 --- a/Mage.Sets/src/mage/cards/g/GrazilaxxIllithidScholar.java +++ b/Mage.Sets/src/mage/cards/g/GrazilaxxIllithidScholar.java @@ -3,7 +3,7 @@ package mage.cards.g; import java.util.UUID; import mage.MageInt; import mage.abilities.common.BecomesBlockedAllTriggeredAbility; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.ReturnToHandTargetEffect; import mage.constants.SubType; @@ -40,7 +40,7 @@ public final class GrazilaxxIllithidScholar extends CardImpl { )); // Whenever one or more creatures you control deal combat damage to a player, draw a card. - this.addAbility(new DealCombatDamageControlledTriggeredAbility(new DrawCardSourceControllerEffect(1))); + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility(new DrawCardSourceControllerEffect(1))); } private GrazilaxxIllithidScholar(final GrazilaxxIllithidScholar card) { diff --git a/Mage.Sets/src/mage/cards/g/GrievousWound.java b/Mage.Sets/src/mage/cards/g/GrievousWound.java index 83d179d33ba..53b9b56db0d 100644 --- a/Mage.Sets/src/mage/cards/g/GrievousWound.java +++ b/Mage.Sets/src/mage/cards/g/GrievousWound.java @@ -1,5 +1,6 @@ package mage.cards.g; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.AttachEffect; @@ -10,6 +11,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.game.Game; +import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.TargetPlayer; @@ -52,7 +54,7 @@ public final class GrievousWound extends CardImpl { } } -class GrievousWoundTriggeredAbility extends TriggeredAbilityImpl { +class GrievousWoundTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { GrievousWoundTriggeredAbility() { super(Zone.BATTLEFIELD, new LoseHalfLifeTargetEffect().setText("they lose half their life, rounded up")); diff --git a/Mage.Sets/src/mage/cards/g/GrimHireling.java b/Mage.Sets/src/mage/cards/g/GrimHireling.java index e50f4ea0fef..e7872aaf5b3 100644 --- a/Mage.Sets/src/mage/cards/g/GrimHireling.java +++ b/Mage.Sets/src/mage/cards/g/GrimHireling.java @@ -3,7 +3,7 @@ package mage.cards.g; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.ActivateAsSorceryActivatedAbility; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.costs.common.SacrificeXTargetCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.dynamicvalue.common.GetXValue; @@ -39,7 +39,7 @@ public final class GrimHireling extends CardImpl { this.toughness = new MageInt(2); // Whenever one or more creatures you control deal combat damage to a player, create two Treasure tokens. - this.addAbility(new DealCombatDamageControlledTriggeredAbility( + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility( new CreateTokenEffect(new TreasureToken(), 2) )); diff --git a/Mage.Sets/src/mage/cards/h/HedgeShredder.java b/Mage.Sets/src/mage/cards/h/HedgeShredder.java index bff6e50cef6..4a5c21dca1d 100644 --- a/Mage.Sets/src/mage/cards/h/HedgeShredder.java +++ b/Mage.Sets/src/mage/cards/h/HedgeShredder.java @@ -1,6 +1,7 @@ package mage.cards.h; import mage.MageInt; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.effects.common.MillCardsControllerEffect; @@ -55,7 +56,7 @@ public final class HedgeShredder extends CardImpl { } } -class HedgeShredderTriggeredAbility extends TriggeredAbilityImpl { +class HedgeShredderTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { HedgeShredderTriggeredAbility() { super(Zone.BATTLEFIELD, new ReturnFromGraveyardToBattlefieldTargetEffect(true)); @@ -75,17 +76,22 @@ class HedgeShredderTriggeredAbility extends TriggeredAbilityImpl { return event.getType() == GameEvent.EventType.ZONE_CHANGE_BATCH; } + @Override + public boolean checkEvent(ZoneChangeEvent event, Game game) { + if (event.getFromZone() != Zone.LIBRARY || event.getToZone() != Zone.GRAVEYARD) { + return false; + } + Card card = game.getCard(event.getTargetId()); + return card != null && card.isLand(game) && card.isOwnedBy(getControllerId()); + } + @Override public boolean checkTrigger(GameEvent event, Game game) { - Set set = ((ZoneChangeBatchEvent) event).getEvents() + Set set = getFilteredEvents((ZoneChangeBatchEvent) event, game) .stream() - .filter(e -> isControlledBy(e.getPlayerId())) - .filter(e -> e.getFromZone().match(Zone.LIBRARY)) - .filter(e -> e.getToZone().match(Zone.GRAVEYARD)) .map(ZoneChangeEvent::getTargetId) .map(game::getCard) .filter(Objects::nonNull) - .filter(card -> card.isLand(game)) .collect(Collectors.toSet()); if (set.isEmpty()) { return false; diff --git a/Mage.Sets/src/mage/cards/h/HordewingSkaab.java b/Mage.Sets/src/mage/cards/h/HordewingSkaab.java index 1ba5aa99c69..3d146bd3827 100644 --- a/Mage.Sets/src/mage/cards/h/HordewingSkaab.java +++ b/Mage.Sets/src/mage/cards/h/HordewingSkaab.java @@ -1,6 +1,7 @@ package mage.cards.h; import mage.MageInt; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.DrawDiscardControllerEffect; @@ -15,8 +16,8 @@ import mage.constants.Zone; import mage.filter.FilterPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; -import mage.game.events.DamagedEvent; import mage.game.events.DamagedBatchForPlayersEvent; +import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -61,7 +62,7 @@ public final class HordewingSkaab extends CardImpl { } } -class HordewingSkaabTriggeredAbility extends TriggeredAbilityImpl { +class HordewingSkaabTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { HordewingSkaabTriggeredAbility() { super(Zone.BATTLEFIELD, null, true); @@ -77,23 +78,24 @@ class HordewingSkaabTriggeredAbility extends TriggeredAbilityImpl { } @Override - public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchForPlayersEvent dEvent = (DamagedBatchForPlayersEvent) event; - Set opponents = new HashSet<>(); - for (DamagedEvent damagedEvent : dEvent.getEvents()) { - if (!damagedEvent.isCombatDamage()) { - continue; - } - Permanent permanent = game.getPermanent(damagedEvent.getSourceId()); - if (permanent == null - || !permanent.isControlledBy(getControllerId()) - || !permanent.hasSubtype(SubType.ZOMBIE, game) - || !game.getOpponents(getControllerId()).contains(damagedEvent.getTargetId())) { - continue; - } - opponents.add(damagedEvent.getTargetId()); + public boolean checkEvent(DamagedPlayerEvent event, Game game) { + if (!event.isCombatDamage() || !game.getOpponents(getControllerId()).contains(event.getTargetId())) { + return false; } - if (opponents.size() < 1) { + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); + return permanent != null + && permanent.isControlledBy(getControllerId()) + && permanent.hasSubtype(SubType.ZOMBIE, game); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Set opponents = new HashSet<>(); + getFilteredEvents((DamagedBatchForPlayersEvent) event, game) + .stream() + .map(GameEvent::getTargetId) + .forEach(opponents::add); + if (opponents.isEmpty()) { return false; } this.getEffects().clear(); diff --git a/Mage.Sets/src/mage/cards/h/HornedCheetah.java b/Mage.Sets/src/mage/cards/h/HornedCheetah.java index 49046d4671b..6683a7f5c52 100644 --- a/Mage.Sets/src/mage/cards/h/HornedCheetah.java +++ b/Mage.Sets/src/mage/cards/h/HornedCheetah.java @@ -1,28 +1,29 @@ - package mage.cards.h; -import java.util.UUID; import mage.MageInt; -import mage.abilities.common.DealsDamageGainLifeSourceTriggeredAbility; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; +import java.util.UUID; + /** - * * @author LoneFox */ public final class HornedCheetah extends CardImpl { public HornedCheetah(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{G}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{W}"); this.subtype.add(SubType.CAT); this.power = new MageInt(2); this.toughness = new MageInt(2); // Whenever Horned Cheetah deals damage, you gain that much life. - this.addAbility(new DealsDamageGainLifeSourceTriggeredAbility()); + this.addAbility(new DealsDamageSourceTriggeredAbility(new GainLifeEffect(SavedDamageValue.MUCH))); } private HornedCheetah(final HornedCheetah card) { diff --git a/Mage.Sets/src/mage/cards/h/HotSoup.java b/Mage.Sets/src/mage/cards/h/HotSoup.java index 0c8d50bab85..b7001d8e67a 100644 --- a/Mage.Sets/src/mage/cards/h/HotSoup.java +++ b/Mage.Sets/src/mage/cards/h/HotSoup.java @@ -1,8 +1,6 @@ package mage.cards.h; -import java.util.Objects; -import java.util.UUID; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.DealtDamageAttachedTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.DestroyTargetEffect; @@ -10,32 +8,28 @@ import mage.abilities.effects.common.combat.CantBeBlockedAttachedEffect; import mage.abilities.keyword.EquipAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AttachmentType; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Outcome; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.target.targetpointer.FixedTarget; +import mage.constants.*; + +import java.util.UUID; /** - * * @author emerald000 */ public final class HotSoup extends CardImpl { public HotSoup(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{1}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); this.subtype.add(SubType.EQUIPMENT); // Equipped creature can't be blocked. this.addAbility(new SimpleStaticAbility(new CantBeBlockedAttachedEffect(AttachmentType.EQUIPMENT))); - + // Whenever equipped creature is dealt damage, destroy it. - this.addAbility(new HotSoupTriggeredAbility()); - + this.addAbility(new DealtDamageAttachedTriggeredAbility( + Zone.BATTLEFIELD, new DestroyTargetEffect().setText("destroy it"), + false, SetTargetPointer.PERMANENT + ).setTriggerPhrase("Whenever equipped creature is dealt damage, ")); + // Equip {3} this.addAbility(new EquipAbility(Outcome.AddAbility, new GenericManaCost(3))); } @@ -49,37 +43,3 @@ public final class HotSoup extends CardImpl { return new HotSoup(this); } } - -class HotSoupTriggeredAbility extends TriggeredAbilityImpl { - - HotSoupTriggeredAbility() { - super(Zone.BATTLEFIELD, new DestroyTargetEffect().setText("destroy it"), false); - setTriggerPhrase("Whenever equipped creature is dealt damage, "); - } - - private HotSoupTriggeredAbility(final HotSoupTriggeredAbility ability) { - super(ability); - } - - @Override - public HotSoupTriggeredAbility copy() { - return new HotSoupTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - Permanent equipment = game.getPermanent(this.getSourceId()); - if (equipment != null && equipment.getAttachedTo() != null) { - if (Objects.equals(event.getTargetId(), equipment.getAttachedTo())) { - this.getEffects().setTargetPointer(new FixedTarget(equipment.getAttachedTo(), game)); - return true; - } - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/h/HowlpackAvenger.java b/Mage.Sets/src/mage/cards/h/HowlpackAvenger.java index de1d6947b17..f0ee5d31421 100644 --- a/Mage.Sets/src/mage/cards/h/HowlpackAvenger.java +++ b/Mage.Sets/src/mage/cards/h/HowlpackAvenger.java @@ -1,7 +1,8 @@ package mage.cards.h; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.Ability; +import mage.abilities.common.DealtDamageAnyTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.dynamicvalue.common.SavedDamageValue; @@ -12,11 +13,9 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; +import mage.constants.SetTargetPointer; import mage.constants.SubType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.DamagedBatchForPermanentsEvent; -import mage.game.events.GameEvent; +import mage.filter.StaticFilters; import mage.target.common.TargetAnyTarget; import java.util.UUID; @@ -36,7 +35,10 @@ public final class HowlpackAvenger extends CardImpl { this.nightCard = true; // Whenever a permanent you control is dealt damage, Howlpack Avenger deals that much damage to any target. - this.addAbility(new HowlpackAvengerTriggeredAbility()); + Ability ability = new DealtDamageAnyTriggeredAbility(new DamageTargetEffect(SavedDamageValue.MUCH), + StaticFilters.FILTER_CONTROLLED_A_PERMANENT, SetTargetPointer.NONE, false); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability); // {1}{R}: Howlpack Avenger gets +2/+0 until end of turn. this.addAbility(new SimpleActivatedAbility(new BoostSourceEffect( @@ -56,41 +58,3 @@ public final class HowlpackAvenger extends CardImpl { return new HowlpackAvenger(this); } } - -class HowlpackAvengerTriggeredAbility extends TriggeredAbilityImpl { - - HowlpackAvengerTriggeredAbility() { - super(Zone.BATTLEFIELD, new DamageTargetEffect(SavedDamageValue.MUCH)); - this.addTarget(new TargetAnyTarget()); - setTriggerPhrase("Whenever a permanent you control is dealt damage, "); - } - - private HowlpackAvengerTriggeredAbility(final HowlpackAvengerTriggeredAbility ability) { - super(ability); - } - - @Override - public HowlpackAvengerTriggeredAbility copy() { - return new HowlpackAvengerTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_PERMANENTS; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - int damage = ((DamagedBatchForPermanentsEvent) event) - .getEvents() - .stream() - .filter(damagedEvent -> isControlledBy(game.getControllerId(damagedEvent.getTargetId()))) - .mapToInt(GameEvent::getAmount) - .sum(); - if (damage < 1) { - return false; - } - this.getEffects().setValue("damage", damage); - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/i/ImodaneThePyrohammer.java b/Mage.Sets/src/mage/cards/i/ImodaneThePyrohammer.java index 0916091b992..8c5910e01ac 100644 --- a/Mage.Sets/src/mage/cards/i/ImodaneThePyrohammer.java +++ b/Mage.Sets/src/mage/cards/i/ImodaneThePyrohammer.java @@ -3,6 +3,7 @@ package mage.cards.i; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; @@ -14,9 +15,11 @@ import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.FilterSpell; import mage.filter.StaticFilters; +import mage.filter.common.FilterInstantOrSorcerySpell; import mage.filter.predicate.other.HasOnlySingleTargetPermanentPredicate; import mage.game.Game; import mage.game.events.DamagedBatchForPermanentsEvent; +import mage.game.events.DamagedPermanentEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.StackObject; @@ -51,9 +54,9 @@ public final class ImodaneThePyrohammer extends CardImpl { } } -class ImodaneThePyrohammerTriggeredAbility extends TriggeredAbilityImpl { +class ImodaneThePyrohammerTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { - private static final FilterSpell filter = new FilterSpell("instant or sorcery spell you control that targets only a single creature"); + private static final FilterSpell filter = new FilterInstantOrSorcerySpell("instant or sorcery spell you control that targets only a single creature"); static { filter.add(TargetController.YOU.getControllerPredicate()); @@ -83,29 +86,28 @@ class ImodaneThePyrohammerTriggeredAbility extends TriggeredAbilityImpl { return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_PERMANENTS; } + @Override + public boolean checkEvent(DamagedPermanentEvent event, Game game) { + MageObject sourceObject = game.getObject(event.getSourceId()); + Permanent target = game.getPermanentOrLKIBattlefield(event.getTargetId()); + // We keep only the events + // 1/ That have sourceId matching the spell filter + // 2/ That have targetId as the spell's only target + return target != null + && sourceObject instanceof StackObject + && filter.match((StackObject) sourceObject, getControllerId(), this, game) + && target.getId().equals(((StackObject) sourceObject).getStackAbility().getFirstTarget()); + } + @Override public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchForPermanentsEvent dEvent = (DamagedBatchForPermanentsEvent) event; - int damage = dEvent - .getEvents() + int damage = getFilteredEvents((DamagedBatchForPermanentsEvent) event, game) .stream() - .filter(damagedEvent -> { - MageObject sourceObject = game.getObject(damagedEvent.getSourceId()); - Permanent target = game.getPermanentOrLKIBattlefield(damagedEvent.getTargetId()); - // We keep only the events - // 1/ That have sourceId matching the spell filter - // 2/ That have targetId as the spell's only target - return sourceObject != null && target != null - && sourceObject instanceof StackObject - && filter.match((StackObject) sourceObject, controllerId, this, game) - && target.getId().equals(((StackObject) sourceObject).getStackAbility().getFirstTarget()); - }) .mapToInt(GameEvent::getAmount) .sum(); if (damage < 1) { return false; } - this.getEffects().setValue(ImodaneThePyrohammerDynamicValue.IMODANE_VALUE_KEY, damage); return true; } diff --git a/Mage.Sets/src/mage/cards/i/InnocentBystander.java b/Mage.Sets/src/mage/cards/i/InnocentBystander.java index 6efdbc1f578..7288ed0e954 100644 --- a/Mage.Sets/src/mage/cards/i/InnocentBystander.java +++ b/Mage.Sets/src/mage/cards/i/InnocentBystander.java @@ -1,13 +1,12 @@ package mage.cards.i; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.DealtDamageToSourceTriggeredAbility; import mage.abilities.effects.keyword.InvestigateEffect; import mage.cards.CardImpl; 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; @@ -20,7 +19,7 @@ public final class InnocentBystander extends CardImpl { public InnocentBystander(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); - + this.subtype.add(SubType.GOBLIN); this.subtype.add(SubType.CITIZEN); this.power = new MageInt(2); @@ -41,10 +40,10 @@ public final class InnocentBystander extends CardImpl { } } -class InnocentBystanderTriggeredAbility extends TriggeredAbilityImpl { +class InnocentBystanderTriggeredAbility extends DealtDamageToSourceTriggeredAbility { InnocentBystanderTriggeredAbility() { - super(Zone.BATTLEFIELD, new InvestigateEffect(), false); + super(new InvestigateEffect(), false); setTriggerPhrase("Whenever {this} is dealt 3 or more damage, "); } @@ -57,13 +56,8 @@ class InnocentBystanderTriggeredAbility extends TriggeredAbilityImpl { return new InnocentBystanderTriggeredAbility(this); } - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; - } - @Override public boolean checkTrigger(GameEvent event, Game game) { - return event.getTargetId().equals(getSourceId()) && event.getAmount() >= 3; + return event.getAmount() >= 3 && super.checkTrigger(event, game); } } diff --git a/Mage.Sets/src/mage/cards/j/JaceCunningCastaway.java b/Mage.Sets/src/mage/cards/j/JaceCunningCastaway.java index fbcadcb0dab..61852bd9eaa 100644 --- a/Mage.Sets/src/mage/cards/j/JaceCunningCastaway.java +++ b/Mage.Sets/src/mage/cards/j/JaceCunningCastaway.java @@ -1,10 +1,11 @@ - package mage.cards.j; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.LoyaltyAbility; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.CreateTokenCopyTargetEffect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.DrawDiscardControllerEffect; @@ -13,13 +14,12 @@ import mage.cards.CardSetInfo; import mage.constants.*; import mage.game.Game; import mage.game.events.DamagedBatchForOnePlayerEvent; +import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.permanent.token.JaceCunningCastawayIllusionToken; import mage.target.targetpointer.FixedTarget; -import java.util.ArrayList; -import java.util.List; import java.util.UUID; /** @@ -36,7 +36,7 @@ public final class JaceCunningCastaway extends CardImpl { this.setStartingLoyalty(3); // +1: Whenever one or more creatures you control deal combat damage to a player this turn, draw a card, then discard a card. - this.addAbility(new LoyaltyAbility(new JaceCunningCastawayEffect1(), 1)); + this.addAbility(new LoyaltyAbility(new CreateDelayedTriggeredAbilityEffect(new JaceCunningCastawayDamageTriggeredAbility()), 1)); // -2: Create a 2/2 blue Illusion creature token with "When this creature becomes the target of a spell, sacrifice it." this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new JaceCunningCastawayIllusionToken()), -2)); @@ -55,36 +55,11 @@ public final class JaceCunningCastaway extends CardImpl { } } -class JaceCunningCastawayEffect1 extends OneShotEffect { - - JaceCunningCastawayEffect1() { - super(Outcome.DrawCard); - this.staticText = "Whenever one or more creatures you control deal combat damage to a player this turn, draw a card, then discard a card"; - } - - private JaceCunningCastawayEffect1(final JaceCunningCastawayEffect1 effect) { - super(effect); - } - - @Override - public JaceCunningCastawayEffect1 copy() { - return new JaceCunningCastawayEffect1(this); - } - - @Override - public boolean apply(Game game, Ability source) { - DelayedTriggeredAbility delayedAbility = new JaceCunningCastawayDamageTriggeredAbility(); - game.addDelayedTriggeredAbility(delayedAbility, source); - return true; - } -} - -class JaceCunningCastawayDamageTriggeredAbility extends DelayedTriggeredAbility { - - private final List damagedPlayerIds = new ArrayList<>(); +class JaceCunningCastawayDamageTriggeredAbility extends DelayedTriggeredAbility implements BatchTriggeredAbility { JaceCunningCastawayDamageTriggeredAbility() { super(new DrawDiscardControllerEffect(1, 1), Duration.EndOfTurn, false); + setTriggerPhrase("Whenever one or more creatures you control deal combat damage to a player this turn, "); } private JaceCunningCastawayDamageTriggeredAbility(final JaceCunningCastawayDamageTriggeredAbility ability) { @@ -102,23 +77,19 @@ class JaceCunningCastawayDamageTriggeredAbility extends DelayedTriggeredAbility } @Override - public boolean checkTrigger(GameEvent event, Game game) { - - DamagedBatchForOnePlayerEvent dEvent = (DamagedBatchForOnePlayerEvent) event; - - int damageFromYours = dEvent.getEvents() - .stream() - .filter(ev -> ev.getSourceId().equals(controllerId)) - .mapToInt(GameEvent::getAmount) - .sum(); - - return dEvent.isCombatDamage() && damageFromYours > 0; + public boolean checkEvent(DamagedPlayerEvent event, Game game) { + if (!event.isCombatDamage()) { + return false; + } + Permanent permanent = game.getPermanent(event.getSourceId()); + return permanent != null && permanent.isCreature(game) && permanent.isControlledBy(getControllerId()); } @Override - public String getRule() { - return "Whenever one or more creatures you control deal combat damage to a player this turn, draw a card, then discard a card"; + public boolean checkTrigger(GameEvent event, Game game) { + return !getFilteredEvents((DamagedBatchForOnePlayerEvent) event, game).isEmpty(); } + } class JaceCunningCastawayCopyEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/j/Justice.java b/Mage.Sets/src/mage/cards/j/Justice.java index 5ed22c53c89..7fa08867c9c 100644 --- a/Mage.Sets/src/mage/cards/j/Justice.java +++ b/Mage.Sets/src/mage/cards/j/Justice.java @@ -2,18 +2,19 @@ package mage.cards.j; import mage.MageObject; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.SacrificeSourceUnlessPaysEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; 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.DamagedEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.Spell; @@ -35,7 +36,7 @@ public final class Justice extends CardImpl { this.addAbility(new BeginningOfUpkeepTriggeredAbility(new SacrificeSourceUnlessPaysEffect(new ManaCostsImpl<>("{W}{W}")))); // Whenever a red creature or spell deals damage, Justice deals that much damage to that creature's or spell's controller. - this.addAbility(new JusticeTriggeredAbility(new JusticeEffect())); + this.addAbility(new JusticeTriggeredAbility()); } private Justice(final Justice card) { @@ -48,10 +49,10 @@ public final class Justice extends CardImpl { } } -class JusticeTriggeredAbility extends TriggeredAbilityImpl { +class JusticeTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { - public JusticeTriggeredAbility(Effect effect) { - super(Zone.BATTLEFIELD, effect); + JusticeTriggeredAbility() { + super(Zone.BATTLEFIELD, new JusticeEffect()); } private JusticeTriggeredAbility(final JusticeTriggeredAbility ability) { @@ -65,8 +66,7 @@ class JusticeTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT - || event.getType() == GameEvent.EventType.DAMAGED_PLAYER; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_BY_SOURCE; } @Override diff --git a/Mage.Sets/src/mage/cards/k/KaitoDancingShadow.java b/Mage.Sets/src/mage/cards/k/KaitoDancingShadow.java index 5118415e419..163b71d9de1 100644 --- a/Mage.Sets/src/mage/cards/k/KaitoDancingShadow.java +++ b/Mage.Sets/src/mage/cards/k/KaitoDancingShadow.java @@ -2,7 +2,7 @@ package mage.cards.k; import mage.abilities.Ability; import mage.abilities.LoyaltyAbility; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; @@ -39,7 +39,7 @@ public final class KaitoDancingShadow extends CardImpl { this.setStartingLoyalty(3); // Whenever one or more creatures you control deal combat damage to a player, you may return one of them to its owner's hand. If you do, you may activate loyalty abilities of Kaito twice this turn rather than only once. - Ability ability = new DealCombatDamageControlledTriggeredAbility(new KaitoDancingShadowEffect(), SetTargetPointer.PLAYER); + Ability ability = new OneOrMoreCombatDamagePlayerTriggeredAbility(new KaitoDancingShadowEffect(), SetTargetPointer.PLAYER); ability.addWatcher(new DamagedPlayerThisCombatWatcher()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/k/KambalProfiteeringMayor.java b/Mage.Sets/src/mage/cards/k/KambalProfiteeringMayor.java index c16724b10ff..aba9c80a8ce 100644 --- a/Mage.Sets/src/mage/cards/k/KambalProfiteeringMayor.java +++ b/Mage.Sets/src/mage/cards/k/KambalProfiteeringMayor.java @@ -2,6 +2,7 @@ package mage.cards.k; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.EntersBattlefieldOneOrMoreTriggeredAbility; import mage.abilities.effects.OneShotEffect; @@ -19,12 +20,10 @@ import mage.game.events.ZoneChangeBatchEvent; import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentToken; -import mage.players.Player; import mage.target.targetpointer.FixedTarget; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.UUID; import java.util.stream.Collectors; @@ -69,7 +68,7 @@ public final class KambalProfiteeringMayor extends CardImpl { } } -class KambalProfiteeringMayorTriggeredAbility extends TriggeredAbilityImpl { +class KambalProfiteeringMayorTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { KambalProfiteeringMayorTriggeredAbility() { super(Zone.BATTLEFIELD, null); @@ -91,19 +90,22 @@ class KambalProfiteeringMayorTriggeredAbility extends TriggeredAbilityImpl { } @Override - public boolean checkTrigger(GameEvent event, Game game) { - ZoneChangeBatchEvent zEvent = (ZoneChangeBatchEvent) event; - Player controller = game.getPlayer(this.controllerId); - if (controller == null) { + public boolean checkEvent(ZoneChangeEvent event, Game game) { + if (event.getToZone() != Zone.BATTLEFIELD) { return false; } - List tokensIds = zEvent.getEvents() + if (!game.getOpponents(getControllerId()).contains(event.getPlayerId())) { + return false; + } + Permanent permanent = event.getTarget(); + return permanent instanceof PermanentToken; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + List tokensIds = getFilteredEvents((ZoneChangeBatchEvent) event, game) .stream() - .filter(zce -> zce.getToZone() == Zone.BATTLEFIELD // keep enter the battlefield - && controller.hasOpponent(zce.getPlayerId(), game)) // & under your opponent's control .map(ZoneChangeEvent::getTarget) - .filter(Objects::nonNull) - .filter(PermanentToken.class::isInstance) // collect only tokens .map(Permanent::getId) .collect(Collectors.toList()); if (tokensIds.isEmpty()) { diff --git a/Mage.Sets/src/mage/cards/k/KastralTheWindcrested.java b/Mage.Sets/src/mage/cards/k/KastralTheWindcrested.java index 654f73ced67..e704919c1b5 100644 --- a/Mage.Sets/src/mage/cards/k/KastralTheWindcrested.java +++ b/Mage.Sets/src/mage/cards/k/KastralTheWindcrested.java @@ -4,7 +4,7 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.Mode; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.counter.AddCountersAllEffect; @@ -47,7 +47,7 @@ public final class KastralTheWindcrested extends CardImpl { // Whenever one or more Birds you control deal combat damage to a player, choose one -- // * You may put a Bird creature card from your hand or graveyard onto the battlefield with a finality counter on it. - Ability ability = new DealCombatDamageControlledTriggeredAbility(Zone.BATTLEFIELD, + Ability ability = new OneOrMoreCombatDamagePlayerTriggeredAbility(Zone.BATTLEFIELD, new KastralTheWindcrestedEffect(), filter, SetTargetPointer.NONE, false); // * Put a +1/+1 counter on each Bird you control. @@ -116,4 +116,4 @@ class KastralTheWindcrestedEffect extends OneShotEffect { } return true; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/k/KayaSpiritsJustice.java b/Mage.Sets/src/mage/cards/k/KayaSpiritsJustice.java index 9ca1a445f87..f2d8dc1aa49 100644 --- a/Mage.Sets/src/mage/cards/k/KayaSpiritsJustice.java +++ b/Mage.Sets/src/mage/cards/k/KayaSpiritsJustice.java @@ -2,6 +2,7 @@ package mage.cards.k; import mage.MageObject; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.LoyaltyAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.OneShotEffect; @@ -34,11 +35,7 @@ import mage.target.targetpointer.FixedTargets; import mage.util.CardUtil; import mage.util.functions.CopyApplier; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; import java.util.UUID; -import java.util.stream.Collectors; /** * @author DominionSpy @@ -84,7 +81,7 @@ public final class KayaSpiritsJustice extends CardImpl { } } -class KayaSpiritsJusticeTriggeredAbility extends TriggeredAbilityImpl { +class KayaSpiritsJusticeTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { KayaSpiritsJusticeTriggeredAbility() { super(Zone.BATTLEFIELD, new KayaSpiritsJusticeCopyEffect(), false); @@ -107,44 +104,34 @@ class KayaSpiritsJusticeTriggeredAbility extends TriggeredAbilityImpl { return event.getType() == GameEvent.EventType.ZONE_CHANGE_BATCH; } + @Override + public boolean checkEvent(ZoneChangeEvent event, Game game) { + if (event.getToZone() != Zone.EXILED) { + return false; + } + switch (event.getFromZone()) { + case BATTLEFIELD: + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); + return permanent != null && StaticFilters.FILTER_CONTROLLED_CREATURE.match(permanent, getControllerId(), this, game); + case GRAVEYARD: + Card card = game.getCard(event.getTargetId()); + return card != null && card.isOwnedBy(getControllerId()) && StaticFilters.FILTER_CARD_CREATURE.match(card, getControllerId(), this, game); + default: + return false; + } + } + @Override public boolean checkTrigger(GameEvent event, Game game) { - ZoneChangeBatchEvent zEvent = (ZoneChangeBatchEvent) event; - - Set battlefieldCards = zEvent.getEvents() + Cards cards = new CardsImpl(); + getFilteredEvents((ZoneChangeBatchEvent) event, game) .stream() - .filter(e -> e.getFromZone() == Zone.BATTLEFIELD) - .filter(e -> e.getToZone() == Zone.EXILED) - .map(ZoneChangeEvent::getTargetId) - .filter(Objects::nonNull) - .map(game::getCard) - .filter(Objects::nonNull) - .filter(card -> { - Permanent permanent = game.getPermanentOrLKIBattlefield(card.getId()); - return StaticFilters.FILTER_PERMANENT_CREATURE - .match(permanent, getControllerId(), this, game); - }) - .collect(Collectors.toSet()); - - Set graveyardCards = zEvent.getEvents() - .stream() - .filter(e -> e.getFromZone() == Zone.GRAVEYARD) - .filter(e -> e.getToZone() == Zone.EXILED) - .map(ZoneChangeEvent::getTargetId) - .filter(Objects::nonNull) - .map(game::getCard) - .filter(Objects::nonNull) - .filter(card -> StaticFilters.FILTER_CARD_CREATURE - .match(card, getControllerId(), this, game)) - .collect(Collectors.toSet()); - - Set cards = new HashSet<>(battlefieldCards); - cards.addAll(graveyardCards); + .map(GameEvent::getTargetId) + .forEach(cards::add); if (cards.isEmpty()) { return false; } - - getEffects().setTargetPointer(new FixedTargets(new CardsImpl(cards), game)); + getEffects().setTargetPointer(new FixedTargets(cards, game)); return true; } } diff --git a/Mage.Sets/src/mage/cards/k/KazarovSengirPureblood.java b/Mage.Sets/src/mage/cards/k/KazarovSengirPureblood.java index 6dd6dacfceb..bc64a95a076 100644 --- a/Mage.Sets/src/mage/cards/k/KazarovSengirPureblood.java +++ b/Mage.Sets/src/mage/cards/k/KazarovSengirPureblood.java @@ -1,29 +1,26 @@ - package mage.cards.k; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.DealtDamageAnyTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; -import mage.constants.SubType; -import mage.constants.SuperType; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; +import mage.constants.SetTargetPointer; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; +import mage.filter.StaticFilters; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class KazarovSengirPureblood extends CardImpl { @@ -40,7 +37,8 @@ public final class KazarovSengirPureblood extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Whenever a creature an opponent controls is dealt damage, put a +1/+1 counter on Kazarov, Sengir Pureblood. - this.addAbility(new KazarovSengirPurebloodTriggeredAbility()); + this.addAbility(new DealtDamageAnyTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()), + StaticFilters.FILTER_OPPONENTS_PERMANENT_A_CREATURE, SetTargetPointer.NONE, false)); // {3}{R}: Kazarov deals 2 damage to target creature. Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(2), new ManaCostsImpl<>("{3}{R}")); @@ -57,37 +55,3 @@ public final class KazarovSengirPureblood extends CardImpl { return new KazarovSengirPureblood(this); } } - -class KazarovSengirPurebloodTriggeredAbility extends TriggeredAbilityImpl { - - public KazarovSengirPurebloodTriggeredAbility() { - super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance())); - } - - private KazarovSengirPurebloodTriggeredAbility(final KazarovSengirPurebloodTriggeredAbility effect) { - super(effect); - } - - @Override - public KazarovSengirPurebloodTriggeredAbility copy() { - return new KazarovSengirPurebloodTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); - return permanent!=null - && permanent.isCreature(game) - && game.getOpponents(permanent.getControllerId()).contains(this.getControllerId()); - } - - @Override - public String getRule() { - return "Whenever a creature an opponent controls is dealt damage, put a +1/+1 counter on {this}."; - } -} diff --git a/Mage.Sets/src/mage/cards/k/KeeperOfFables.java b/Mage.Sets/src/mage/cards/k/KeeperOfFables.java index a0e8ba955ee..ee01a3e7b32 100644 --- a/Mage.Sets/src/mage/cards/k/KeeperOfFables.java +++ b/Mage.Sets/src/mage/cards/k/KeeperOfFables.java @@ -1,7 +1,7 @@ package mage.cards.k; import mage.MageInt; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -30,7 +30,7 @@ public final class KeeperOfFables extends CardImpl { this.toughness = new MageInt(5); // Whenever one or more non-Human creatures you control deal combat damage to a player, draw a card. - this.addAbility(new DealCombatDamageControlledTriggeredAbility(new DrawCardSourceControllerEffect(1), filter)); + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility(new DrawCardSourceControllerEffect(1), filter)); } private KeeperOfFables(final KeeperOfFables card) { @@ -41,4 +41,4 @@ public final class KeeperOfFables extends CardImpl { public KeeperOfFables copy() { return new KeeperOfFables(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/k/KiyomaroFirstToStand.java b/Mage.Sets/src/mage/cards/k/KiyomaroFirstToStand.java index 302728cd3a3..48a897d7fd5 100644 --- a/Mage.Sets/src/mage/cards/k/KiyomaroFirstToStand.java +++ b/Mage.Sets/src/mage/cards/k/KiyomaroFirstToStand.java @@ -1,63 +1,48 @@ - package mage.cards.k; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.Condition; import mage.abilities.condition.common.CardsInHandCondition; import mage.abilities.decorator.ConditionalContinuousEffect; -import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; -import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.CardsInControllerHandCount; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; import mage.abilities.keyword.VigilanceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.ComparisonType; -import mage.constants.Duration; -import mage.constants.SuperType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; +import mage.constants.*; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class KiyomaroFirstToStand extends CardImpl { public KiyomaroFirstToStand(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{W}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{W}"); this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.SPIRIT); this.power = new MageInt(0); this.toughness = new MageInt(0); // Kiyomaro, First to Stand's power and toughness are each equal to the number of cards in your hand. - DynamicValue xValue= CardsInControllerHandCount.instance; - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(xValue))); - + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CardsInControllerHandCount.instance))); + // As long as you have four or more cards in hand, Kiyomaro has vigilance. - Condition condition = new CardsInHandCondition(ComparisonType.MORE_THAN,3); + Condition condition = new CardsInHandCondition(ComparisonType.MORE_THAN, 3); Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilitySourceEffect(VigilanceAbility.getInstance(), Duration.WhileOnBattlefield), condition, "As long as you have four or more cards in hand, {this} has vigilance")); this.addAbility(ability); - + // Whenever Kiyomaro deals damage, if you have seven or more cards in hand, you gain 7 life. - this.addAbility(new ConditionalInterveningIfTriggeredAbility( - new KiyomaroFirstToStandDealsDamageTriggeredAbility(), - new CardsInHandCondition(ComparisonType.MORE_THAN, 6), - "Whenever {this} deals damage, if you have seven or more cards in hand, you gain 7 life" - )); + this.addAbility(new DealsDamageSourceTriggeredAbility(new GainLifeEffect(7)) + .withInterveningIf(new CardsInHandCondition(ComparisonType.OR_GREATER, 7))); } private KiyomaroFirstToStand(final KiyomaroFirstToStand card) { @@ -69,37 +54,3 @@ public final class KiyomaroFirstToStand extends CardImpl { return new KiyomaroFirstToStand(this); } } - -class KiyomaroFirstToStandDealsDamageTriggeredAbility extends TriggeredAbilityImpl { - - public KiyomaroFirstToStandDealsDamageTriggeredAbility() { - super(Zone.BATTLEFIELD, new GainLifeEffect(7), false); - } - - private KiyomaroFirstToStandDealsDamageTriggeredAbility(final KiyomaroFirstToStandDealsDamageTriggeredAbility ability) { - super(ability); - } - - @Override - public KiyomaroFirstToStandDealsDamageTriggeredAbility copy() { - return new KiyomaroFirstToStandDealsDamageTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER - || event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getSourceId().equals(this.getSourceId())) { - for (Effect effect : this.getEffects()) { - effect.setValue("damage", event.getAmount()); - } - return true; - } - return false; - } - -} diff --git a/Mage.Sets/src/mage/cards/k/KjeldoranGargoyle.java b/Mage.Sets/src/mage/cards/k/KjeldoranGargoyle.java index 281936438ed..1fb3bb3be82 100644 --- a/Mage.Sets/src/mage/cards/k/KjeldoranGargoyle.java +++ b/Mage.Sets/src/mage/cards/k/KjeldoranGargoyle.java @@ -1,9 +1,9 @@ - package mage.cards.k; -import java.util.UUID; import mage.MageInt; -import mage.abilities.common.DealsDamageGainLifeSourceTriggeredAbility; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.keyword.FirstStrikeAbility; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; @@ -11,26 +11,27 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; +import java.util.UUID; + /** - * * @author fireshoes */ public final class KjeldoranGargoyle extends CardImpl { public KjeldoranGargoyle(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{5}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}"); this.subtype.add(SubType.GARGOYLE); this.power = new MageInt(3); this.toughness = new MageInt(3); // Flying this.addAbility(FlyingAbility.getInstance()); - + // First strike this.addAbility(FirstStrikeAbility.getInstance()); - + // Whenever Kjeldoran Gargoyle deals damage, you gain that much life. - this.addAbility(new DealsDamageGainLifeSourceTriggeredAbility()); + this.addAbility(new DealsDamageSourceTriggeredAbility(new GainLifeEffect(SavedDamageValue.MUCH))); } private KjeldoranGargoyle(final KjeldoranGargoyle card) { @@ -41,4 +42,4 @@ public final class KjeldoranGargoyle extends CardImpl { public KjeldoranGargoyle copy() { return new KjeldoranGargoyle(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/k/KutzilMalametExemplar.java b/Mage.Sets/src/mage/cards/k/KutzilMalametExemplar.java index e25ce1c9659..48e692eb443 100644 --- a/Mage.Sets/src/mage/cards/k/KutzilMalametExemplar.java +++ b/Mage.Sets/src/mage/cards/k/KutzilMalametExemplar.java @@ -1,7 +1,7 @@ package mage.cards.k; import mage.MageInt; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.ruleModifying.CantCastDuringYourTurnEffect; @@ -39,7 +39,7 @@ public final class KutzilMalametExemplar extends CardImpl { this.addAbility(new SimpleStaticAbility(new CantCastDuringYourTurnEffect())); // Whenever one or more creatures you control each with power greater than its base power deals combat damage to a player, draw a card. - this.addAbility(new DealCombatDamageControlledTriggeredAbility(new DrawCardSourceControllerEffect(1), filter) + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility(new DrawCardSourceControllerEffect(1), filter) .setTriggerPhrase("Whenever one or more creatures you control each with power greater than its base power" + " deals combat damage to a player, ") ); diff --git a/Mage.Sets/src/mage/cards/l/LaeliaTheBladeReforged.java b/Mage.Sets/src/mage/cards/l/LaeliaTheBladeReforged.java index 5bbad75c84c..393e9077827 100644 --- a/Mage.Sets/src/mage/cards/l/LaeliaTheBladeReforged.java +++ b/Mage.Sets/src/mage/cards/l/LaeliaTheBladeReforged.java @@ -1,6 +1,7 @@ package mage.cards.l; import mage.MageInt; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; @@ -16,7 +17,6 @@ import mage.game.events.GameEvent; import mage.game.events.ZoneChangeBatchEvent; import mage.game.events.ZoneChangeEvent; -import java.util.Objects; import java.util.UUID; /** @@ -58,7 +58,7 @@ public final class LaeliaTheBladeReforged extends CardImpl { } } -class LaeliaTheBladeReforgedAddCountersTriggeredAbility extends TriggeredAbilityImpl { +class LaeliaTheBladeReforgedAddCountersTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { LaeliaTheBladeReforgedAddCountersTriggeredAbility() { super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance()), false); @@ -78,18 +78,21 @@ class LaeliaTheBladeReforgedAddCountersTriggeredAbility extends TriggeredAbility return event.getType() == GameEvent.EventType.ZONE_CHANGE_BATCH; } + @Override + public boolean checkEvent(ZoneChangeEvent event, Game game) { + if (event.getToZone() != Zone.EXILED) { + return false; + } + if (event.getFromZone() != Zone.LIBRARY && event.getFromZone() != Zone.GRAVEYARD) { + return false; + } + Card card = game.getCard(event.getTargetId()); + return card != null && card.isOwnedBy(getControllerId()); + } + @Override public boolean checkTrigger(GameEvent event, Game game) { - ZoneChangeBatchEvent zEvent = (ZoneChangeBatchEvent) event; - return zEvent.getEvents() - .stream() - .filter(e -> e.getFromZone() == Zone.LIBRARY || e.getFromZone() == Zone.GRAVEYARD) - .filter(e -> e.getToZone() == Zone.EXILED) - .map(ZoneChangeEvent::getTargetId) - .map(game::getCard) - .filter(Objects::nonNull) - .map(Card::getOwnerId) - .anyMatch(this::isControlledBy); + return !getFilteredEvents((ZoneChangeBatchEvent) event, game).isEmpty(); } @Override diff --git a/Mage.Sets/src/mage/cards/l/Lich.java b/Mage.Sets/src/mage/cards/l/Lich.java index da1e22d33d2..9c7afe8ef73 100644 --- a/Mage.Sets/src/mage/cards/l/Lich.java +++ b/Mage.Sets/src/mage/cards/l/Lich.java @@ -1,14 +1,12 @@ - package mage.cards.l; -import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.common.PutIntoGraveFromBattlefieldSourceTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.YoureDealtDamageTriggeredAbility; import mage.abilities.dynamicvalue.common.ControllerLifeCount; -import mage.abilities.effects.Effect; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.common.LoseGameSourceControllerEffect; @@ -19,38 +17,36 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; -import mage.constants.Zone; import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.permanent.TokenPredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.Target; -import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetSacrifice; +import java.util.UUID; + /** - * * @author emerald000 */ public final class Lich extends CardImpl { public Lich(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{B}{B}{B}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{B}{B}{B}{B}"); // As Lich enters the battlefield, you lose life equal to your life total. this.addAbility(new EntersBattlefieldAbility(new LoseLifeSourceControllerEffect(ControllerLifeCount.instance), null, "As Lich enters the battlefield, you lose life equal to your life total.", null)); - + // You don't lose the game for having 0 or less life. this.addAbility(new SimpleStaticAbility(new DontLoseByZeroOrLessLifeEffect(Duration.WhileOnBattlefield))); - + // If you would gain life, draw that many cards instead. this.addAbility(new SimpleStaticAbility(new LichLifeGainReplacementEffect())); - + // Whenever you're dealt damage, sacrifice that many nontoken permanents. If you can't, you lose the game. - this.addAbility(new LichDamageTriggeredAbility()); - + this.addAbility(new YoureDealtDamageTriggeredAbility(new LichDamageEffect(), false)); + // When Lich is put into a graveyard from the battlefield, you lose the game. this.addAbility(new PutIntoGraveFromBattlefieldSourceTriggeredAbility(new LoseGameSourceControllerEffect())); } @@ -101,92 +97,46 @@ class LichLifeGainReplacementEffect extends ReplacementEffectImpl { } } -class LichDamageTriggeredAbility extends TriggeredAbilityImpl { - - LichDamageTriggeredAbility() { - super(Zone.BATTLEFIELD, new LichDamageEffect(), false); - } - - private LichDamageTriggeredAbility(final LichDamageTriggeredAbility ability) { - super(ability); - } - - @Override - public LichDamageTriggeredAbility copy() { - return new LichDamageTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(this.getControllerId())) { - for (Effect effect : this.getEffects()) { - if (effect instanceof LichDamageEffect) { - ((LichDamageEffect) effect).setAmount(event.getAmount()); - } - } - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever you're dealt damage, sacrifice that many nontoken permanents. If you can't, you lose the game."; - } -} - class LichDamageEffect extends OneShotEffect { - + private static final FilterControlledPermanent filter = new FilterControlledPermanent("nontoken permanent"); + static { filter.add(TokenPredicate.FALSE); } - - private int amount = 0; - + LichDamageEffect() { super(Outcome.Sacrifice); this.staticText = "sacrifice that many nontoken permanents. If you can't, you lose the game"; } - + private LichDamageEffect(final LichDamageEffect effect) { super(effect); - this.amount = effect.amount; } - + @Override public LichDamageEffect copy() { return new LichDamageEffect(this); } - + @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - TargetSacrifice target = new TargetSacrifice(amount, filter); - if (target.canChoose(controller.getId(), source, game)) { - if (controller.choose(Outcome.Sacrifice, target, source, game)) { - for (UUID targetId : target.getTargets()) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null) { - permanent.sacrifice(source, game); - } - } - return true; + if (controller == null) { + return false; + } + TargetSacrifice target = new TargetSacrifice(SavedDamageValue.MANY.calculate(game, source, this), filter); + if (target.canChoose(controller.getId(), source, game) && controller.choose(Outcome.Sacrifice, target, source, game)) { + for (UUID targetId : target.getTargets()) { + Permanent permanent = game.getPermanent(targetId); + if (permanent != null) { + permanent.sacrifice(source, game); } } - controller.lost(game); return true; } - return false; - } - - public void setAmount(int amount) { - this.amount = amount; + controller.lost(game); + return true; } + } diff --git a/Mage.Sets/src/mage/cards/l/LichsMastery.java b/Mage.Sets/src/mage/cards/l/LichsMastery.java index c62db9e6f64..b07d5c77b2e 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 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. - 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 @@ 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 { - 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 @@ class LichsMasteryLoseLifeEffect extends OneShotEffect { } 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 @@ class LichsMasteryLoseLifeEffect extends OneShotEffect { 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 @@ class LichsMasteryLoseLifeEffect extends OneShotEffect { 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 f1991509497..ce4d9f56978 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 final class LichsTomb extends CardImpl { 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/l/LivingArtifact.java b/Mage.Sets/src/mage/cards/l/LivingArtifact.java index 4df247332d8..da9644a7f66 100644 --- a/Mage.Sets/src/mage/cards/l/LivingArtifact.java +++ b/Mage.Sets/src/mage/cards/l/LivingArtifact.java @@ -1,39 +1,33 @@ - package mage.cards.l; -import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; -import mage.abilities.condition.common.SourceHasCounterCondition; +import mage.abilities.common.YoureDealtDamageTriggeredAbility; import mage.abilities.costs.common.RemoveCountersSourceCost; -import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.EnchantAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; -import mage.constants.Zone; +import mage.constants.SubType; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.events.GameEvent; import mage.target.TargetPermanent; import mage.target.common.TargetArtifactPermanent; +import java.util.UUID; + /** - * * @author LoneFox */ public final class LivingArtifact extends CardImpl { public LivingArtifact(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{G}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{G}"); this.subtype.add(SubType.AURA); // Enchant artifact @@ -42,13 +36,14 @@ public final class LivingArtifact extends CardImpl { this.getSpellAbility().addEffect(new AttachEffect(Outcome.Benefit)); Ability ability = new EnchantAbility(auraTarget); this.addAbility(ability); + // Whenever you're dealt damage, put that many vitality counters on Living Artifact. - this.addAbility(new LivingArtifactTriggeredAbility()); + this.addAbility(new YoureDealtDamageTriggeredAbility(new AddCountersSourceEffect( + CounterType.VITALITY.createInstance(), SavedDamageValue.MANY), false)); + // At the beginning of your upkeep, you may remove a vitality counter from Living Artifact. If you do, you gain 1 life. - //TODO make this a custom ability- it's really not intervening if because you should be able to add counters in response to this trigger - this.addAbility(new ConditionalInterveningIfTriggeredAbility(new BeginningOfUpkeepTriggeredAbility(new DoIfCostPaid(new GainLifeEffect(1), - new RemoveCountersSourceCost(CounterType.VITALITY.createInstance(1)))), - new SourceHasCounterCondition(CounterType.VITALITY, 1), "At the beginning of your upkeep, you may remove a vitality counter from {this}. If you do, you gain 1 life")); + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new DoIfCostPaid(new GainLifeEffect(1), + new RemoveCountersSourceCost(CounterType.VITALITY.createInstance(1))))); } private LivingArtifact(final LivingArtifact card) { @@ -60,59 +55,3 @@ public final class LivingArtifact extends CardImpl { return new LivingArtifact(this); } } - -class LivingArtifactTriggeredAbility extends TriggeredAbilityImpl { - - public LivingArtifactTriggeredAbility() { - super(Zone.BATTLEFIELD, new LivingArtifactEffect(), false); - } - - private LivingArtifactTriggeredAbility(final LivingArtifactTriggeredAbility ability) { - super(ability); - } - - @Override - public LivingArtifactTriggeredAbility copy() { - return new LivingArtifactTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(this.getControllerId())) { - this.getEffects().get(0).setValue("damageAmount", event.getAmount()); - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever you're dealt damage, put that many vitality counters on {this}."; - } -} - -class LivingArtifactEffect extends OneShotEffect { - - LivingArtifactEffect() { - super(Outcome.Benefit); - } - - private LivingArtifactEffect(final LivingArtifactEffect effect) { - super(effect); - } - - @Override - public LivingArtifactEffect copy() { - return new LivingArtifactEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - return new AddCountersSourceEffect(CounterType.VITALITY.createInstance((Integer) this.getValue("damageAmount"))).apply(game, source); - } -} diff --git a/Mage.Sets/src/mage/cards/l/LongRiverLurker.java b/Mage.Sets/src/mage/cards/l/LongRiverLurker.java index 8143d2b3bd1..7d76b8a7ec9 100644 --- a/Mage.Sets/src/mage/cards/l/LongRiverLurker.java +++ b/Mage.Sets/src/mage/cards/l/LongRiverLurker.java @@ -3,6 +3,7 @@ package mage.cards.l; import mage.MageInt; import mage.MageObjectReference; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; @@ -21,7 +22,7 @@ import mage.constants.Outcome; import mage.constants.SubType; import mage.filter.FilterPermanent; import mage.game.Game; -import mage.game.events.DamagedBatchAllEvent; +import mage.game.events.DamagedBatchBySourceEvent; import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -96,7 +97,7 @@ class LongRiverLurkerEffect extends OneShotEffect { } } -class LongRiverLurkerTriggeredAbility extends DelayedTriggeredAbility { +class LongRiverLurkerTriggeredAbility extends DelayedTriggeredAbility implements BatchTriggeredAbility { private final MageObjectReference mor; @@ -118,16 +119,13 @@ class LongRiverLurkerTriggeredAbility extends DelayedTriggeredAbility { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ALL; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_BY_SOURCE; } @Override public boolean checkTrigger(GameEvent event, Game game) { - return ((DamagedBatchAllEvent) event) - .getEvents() - .stream() - .filter(DamagedEvent::isCombatDamage) - .anyMatch(e -> mor.refersTo(e.getAttackerId(), game)); + return ((DamagedBatchBySourceEvent) event).isCombatDamage() + && mor.refersTo(event.getSourceId(), game); } @Override diff --git a/Mage.Sets/src/mage/cards/m/MagmaticGalleon.java b/Mage.Sets/src/mage/cards/m/MagmaticGalleon.java index 0b1019cd047..f60e4b901df 100644 --- a/Mage.Sets/src/mage/cards/m/MagmaticGalleon.java +++ b/Mage.Sets/src/mage/cards/m/MagmaticGalleon.java @@ -2,6 +2,7 @@ package mage.cards.m; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; @@ -13,9 +14,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedBatchForPermanentsEvent; -import mage.game.events.DamagedEvent; -import mage.game.events.GameEvent; +import mage.game.events.*; import mage.game.permanent.Permanent; import mage.game.permanent.token.TreasureToken; import mage.target.common.TargetOpponentsCreaturePermanent; @@ -57,7 +56,7 @@ public final class MagmaticGalleon extends CardImpl { } } -class MagmaticGalleonTriggeredAbility extends TriggeredAbilityImpl { +class MagmaticGalleonTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { MagmaticGalleonTriggeredAbility() { super(Zone.BATTLEFIELD, new CreateTokenEffect(new TreasureToken())); @@ -78,19 +77,19 @@ class MagmaticGalleonTriggeredAbility extends TriggeredAbilityImpl { return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_PERMANENTS; } + @Override + public boolean checkEvent(DamagedPermanentEvent event, Game game) { + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); + return permanent != null + && permanent.isCreature(game) + && game.getOpponents(getControllerId()).contains(permanent.getControllerId()) + && !event.isCombatDamage(); + } + @Override public boolean checkTrigger(GameEvent event, Game game) { - int damage = ((DamagedBatchForPermanentsEvent) event) - .getEvents() + int damage = getFilteredEvents((DamagedBatchForPermanentsEvent) event, game) .stream() - .filter(damagedEvent -> { - if (damagedEvent.isCombatDamage()) { - return false; - } - Permanent permanent = game.getPermanentOrLKIBattlefield(damagedEvent.getTargetId()); - return permanent != null && permanent.isCreature(game) - && game.getOpponents(this.getControllerId()).contains(permanent.getControllerId()); - }) .mapToInt(DamagedEvent::getExcess) .sum(); return damage >= 1; diff --git a/Mage.Sets/src/mage/cards/m/MalcolmKeenEyedNavigator.java b/Mage.Sets/src/mage/cards/m/MalcolmKeenEyedNavigator.java index 07944f8d4ca..f5d2a9bd394 100644 --- a/Mage.Sets/src/mage/cards/m/MalcolmKeenEyedNavigator.java +++ b/Mage.Sets/src/mage/cards/m/MalcolmKeenEyedNavigator.java @@ -1,6 +1,7 @@ package mage.cards.m; import mage.MageInt; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.keyword.FlyingAbility; @@ -12,8 +13,8 @@ import mage.constants.SubType; import mage.constants.SuperType; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedEvent; import mage.game.events.DamagedBatchForPlayersEvent; +import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.permanent.token.TreasureToken; @@ -56,7 +57,7 @@ public final class MalcolmKeenEyedNavigator extends CardImpl { } } -class MalcolmKeenEyedNavigatorTriggeredAbility extends TriggeredAbilityImpl { +class MalcolmKeenEyedNavigatorTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { MalcolmKeenEyedNavigatorTriggeredAbility() { super(Zone.BATTLEFIELD, null); @@ -72,20 +73,24 @@ class MalcolmKeenEyedNavigatorTriggeredAbility extends TriggeredAbilityImpl { } @Override - public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchForPlayersEvent dEvent = (DamagedBatchForPlayersEvent) event; - Set opponents = new HashSet<>(); - for (DamagedEvent damagedEvent : dEvent.getEvents()) { - Permanent permanent = game.getPermanent(damagedEvent.getSourceId()); - if (permanent == null - || !permanent.isControlledBy(getControllerId()) - || !permanent.hasSubtype(SubType.PIRATE, game) - || !game.getOpponents(getControllerId()).contains(damagedEvent.getTargetId())) { - continue; - } - opponents.add(damagedEvent.getTargetId()); + public boolean checkEvent(DamagedPlayerEvent event, Game game) { + if (!game.getOpponents(getControllerId()).contains(event.getTargetId())) { + return false; } - if (opponents.size() < 1) { + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); + return permanent != null + && permanent.isControlledBy(getControllerId()) + && permanent.hasSubtype(SubType.PIRATE, game); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Set opponents = new HashSet<>(); + getFilteredEvents((DamagedBatchForPlayersEvent) event, game) + .stream() + .map(GameEvent::getTargetId) + .forEach(opponents::add); + if (opponents.isEmpty()) { return false; } this.getEffects().clear(); diff --git a/Mage.Sets/src/mage/cards/m/MarinaVendrellsGrimoire.java b/Mage.Sets/src/mage/cards/m/MarinaVendrellsGrimoire.java index 02d66919f0d..82a54e1e140 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.cards.CardSetInfo; 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 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. - 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 final class MarinaVendrellsGrimoire extends CardImpl { 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/MidnightPathlighter.java b/Mage.Sets/src/mage/cards/m/MidnightPathlighter.java index 827584eb51b..f699a628fa0 100644 --- a/Mage.Sets/src/mage/cards/m/MidnightPathlighter.java +++ b/Mage.Sets/src/mage/cards/m/MidnightPathlighter.java @@ -1,7 +1,7 @@ package mage.cards.m; import mage.MageInt; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesAllEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; @@ -45,7 +45,7 @@ public final class MidnightPathlighter extends CardImpl { ))); // Whenever one or more creatures you control deal combat damage to a player, venture into the dungeon. - this.addAbility(new DealCombatDamageControlledTriggeredAbility(new VentureIntoTheDungeonEffect()) + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility(new VentureIntoTheDungeonEffect()) .addHint(CurrentDungeonHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/m/MindbladeRender.java b/Mage.Sets/src/mage/cards/m/MindbladeRender.java index cd23225d419..0f6faa345a7 100644 --- a/Mage.Sets/src/mage/cards/m/MindbladeRender.java +++ b/Mage.Sets/src/mage/cards/m/MindbladeRender.java @@ -1,6 +1,7 @@ package mage.cards.m; import mage.MageInt; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.LoseLifeSourceControllerEffect; @@ -10,10 +11,10 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedBatchForOnePlayerEvent; +import mage.game.events.DamagedBatchForPlayersEvent; +import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; -import mage.players.Player; import java.util.UUID; @@ -45,11 +46,12 @@ public final class MindbladeRender extends CardImpl { } } -class MindbladeRenderTriggeredAbility extends TriggeredAbilityImpl { +class MindbladeRenderTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { - public MindbladeRenderTriggeredAbility() { - super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1)); - this.addEffect(new LoseLifeSourceControllerEffect(1)); + MindbladeRenderTriggeredAbility() { + super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1, true)); + this.addEffect(new LoseLifeSourceControllerEffect(1).concatBy("and")); + setTriggerPhrase("Whenever your opponents are dealt combat damage, if any of that damage was dealt by a Warrior, "); } private MindbladeRenderTriggeredAbility(final MindbladeRenderTriggeredAbility effect) { @@ -63,38 +65,21 @@ class MindbladeRenderTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_PLAYERS; + } + + @Override + public boolean checkEvent(DamagedPlayerEvent event, Game game) { + if (!event.isCombatDamage() || !game.getOpponents(getControllerId()).contains(event.getTargetId())) { + return false; + } + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); + return permanent != null && permanent.isControlledBy(getControllerId()) && permanent.hasSubtype(SubType.WARRIOR, game); } @Override public boolean checkTrigger(GameEvent event, Game game) { - Player controller = game.getPlayer(getControllerId()); - if (controller == null) { - return false; - } - DamagedBatchForOnePlayerEvent dEvent = (DamagedBatchForOnePlayerEvent) event; - - if (!controller.hasOpponent(dEvent.getTargetId(), game)){ - return false; - } - if (!dEvent.isCombatDamage()) { - return false; - } - - int warriorDamage = dEvent.getEvents() - .stream() - .filter(ev -> { - Permanent attacker = game.getPermanentOrLKIBattlefield(ev.getSourceId()); - return attacker != null && attacker.hasSubtype(SubType.WARRIOR, game); - }) - .mapToInt(GameEvent::getAmount) - .sum(); - - return warriorDamage > 0; + return !getFilteredEvents((DamagedBatchForPlayersEvent) event, game).isEmpty(); } - @Override - public String getRule() { - return "Whenever your opponents are dealt combat damage, if any of that damage was dealt by a Warrior, you draw a card and you lose 1 life."; - } } diff --git a/Mage.Sets/src/mage/cards/m/Mindcrank.java b/Mage.Sets/src/mage/cards/m/Mindcrank.java index b2c5aebe109..24dbfefe16d 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 final class Mindcrank extends CardImpl { 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/m/MireBlight.java b/Mage.Sets/src/mage/cards/m/MireBlight.java index b582557bdcb..e3f0c9f3872 100644 --- a/Mage.Sets/src/mage/cards/m/MireBlight.java +++ b/Mage.Sets/src/mage/cards/m/MireBlight.java @@ -33,7 +33,7 @@ public final class MireBlight extends CardImpl { Ability ability = new EnchantAbility(auraTarget); this.addAbility(ability); // When enchanted creature is dealt damage, destroy it. - this.addAbility(new DealtDamageAttachedTriggeredAbility(new DestroyAttachedToEffect("it"), false).setTriggerPhrase("When enchanted creature is dealt damage, ")); + this.addAbility(new DealtDamageAttachedTriggeredAbility(new DestroyAttachedToEffect("it")).setTriggerPhrase("When enchanted creature is dealt damage, ")); } private MireBlight(final MireBlight card) { diff --git a/Mage.Sets/src/mage/cards/m/MortalWound.java b/Mage.Sets/src/mage/cards/m/MortalWound.java index 46c865183d6..1cab38bbfbf 100644 --- a/Mage.Sets/src/mage/cards/m/MortalWound.java +++ b/Mage.Sets/src/mage/cards/m/MortalWound.java @@ -31,7 +31,7 @@ public final class MortalWound extends CardImpl { Ability ability = new EnchantAbility(auraTarget); this.addAbility(ability); // When enchanted creature is dealt damage, destroy it. - this.addAbility(new DealtDamageAttachedTriggeredAbility(new DestroyAttachedToEffect("it"), false) + this.addAbility(new DealtDamageAttachedTriggeredAbility(new DestroyAttachedToEffect("it")) .setTriggerPhrase("When enchanted creature is dealt damage, ")); } diff --git a/Mage.Sets/src/mage/cards/m/MourningThrull.java b/Mage.Sets/src/mage/cards/m/MourningThrull.java index 7e9eaaf6e2c..3a29c3572fa 100644 --- a/Mage.Sets/src/mage/cards/m/MourningThrull.java +++ b/Mage.Sets/src/mage/cards/m/MourningThrull.java @@ -1,23 +1,24 @@ - package mage.cards.m; -import java.util.UUID; import mage.MageInt; -import mage.abilities.common.DealsDamageGainLifeSourceTriggeredAbility; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; +import java.util.UUID; + /** - * * @author North */ public final class MourningThrull extends CardImpl { public MourningThrull(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{W/B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W/B}"); this.subtype.add(SubType.THRULL); this.power = new MageInt(1); @@ -25,8 +26,9 @@ public final class MourningThrull extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); + // Whenever Mourning Thrull deals damage, you gain that much life. - this.addAbility(new DealsDamageGainLifeSourceTriggeredAbility()); + this.addAbility(new DealsDamageSourceTriggeredAbility(new GainLifeEffect(SavedDamageValue.MUCH))); } private MourningThrull(final MourningThrull card) { diff --git a/Mage.Sets/src/mage/cards/n/NaturesWill.java b/Mage.Sets/src/mage/cards/n/NaturesWill.java index 78e922d58b6..dff0b9fa0e3 100644 --- a/Mage.Sets/src/mage/cards/n/NaturesWill.java +++ b/Mage.Sets/src/mage/cards/n/NaturesWill.java @@ -3,7 +3,7 @@ package mage.cards.n; import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.effects.Effect; import mage.abilities.effects.common.TapAllTargetPlayerControlsEffect; import mage.abilities.effects.common.UntapAllEffect; @@ -25,7 +25,7 @@ public final class NaturesWill extends CardImpl { // Whenever one or more creatures you control deal combat damage to a player, tap all lands that player controls and untap all lands you control. Effect tapAllEffect = new TapAllTargetPlayerControlsEffect(StaticFilters.FILTER_LANDS); tapAllEffect.setText("tap all lands that player controls"); - Ability ability = new DealCombatDamageControlledTriggeredAbility(tapAllEffect, SetTargetPointer.PLAYER); + Ability ability = new OneOrMoreCombatDamagePlayerTriggeredAbility(tapAllEffect, SetTargetPointer.PLAYER); ability.addEffect(new UntapAllEffect(StaticFilters.FILTER_CONTROLLED_PERMANENT_LANDS).concatBy("and")); addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/n/Necravolver.java b/Mage.Sets/src/mage/cards/n/Necravolver.java index c781429e2b7..0ac557e6701 100644 --- a/Mage.Sets/src/mage/cards/n/Necravolver.java +++ b/Mage.Sets/src/mage/cards/n/Necravolver.java @@ -1,12 +1,12 @@ - package mage.cards.n; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.DealsDamageGainLifeSourceTriggeredAbility; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.condition.common.KickedCostCondition; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.KickerAbility; @@ -14,19 +14,19 @@ import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Duration; +import mage.constants.SubType; import mage.counters.CounterType; -/** - * - * @author LoneFox +import java.util.UUID; +/** + * @author LoneFox */ public final class Necravolver extends CardImpl { public Necravolver(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); this.subtype.add(SubType.VOLVER); this.power = new MageInt(2); this.toughness = new MageInt(2); @@ -36,14 +36,14 @@ public final class Necravolver extends CardImpl { kickerAbility.addKickerCost("{W}"); this.addAbility(kickerAbility); // If Necravolver was kicked with its {1}{G} kicker, it enters with two +1/+1 counters on it and with trample. - Ability ability = new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), - new KickedCostCondition("{1}{G}"), "If {this} was kicked with its {1}{G} kicker, it enters with two +1/+1 counters on it and with trample.", ""); + Ability ability = new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), + new KickedCostCondition("{1}{G}"), "If {this} was kicked with its {1}{G} kicker, it enters with two +1/+1 counters on it and with trample.", ""); ability.addEffect(new GainAbilitySourceEffect(TrampleAbility.getInstance(), Duration.WhileOnBattlefield)); this.addAbility(ability); // If Necravolver was kicked with its {W} kicker, it enters with a +1/+1 counter on it and with "Whenever Necravolver deals damage, you gain that much life." - ability = new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(1)), - new KickedCostCondition("{W}"), "If {this} was kicked with its {W} kicker, it enters with a +1/+1 counter on it and with \"Whenever {this} deals damage, you gain that much life.\"", ""); - ability.addEffect(new GainAbilitySourceEffect(new DealsDamageGainLifeSourceTriggeredAbility(), Duration.WhileOnBattlefield)); + ability = new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(1)), + new KickedCostCondition("{W}"), "If {this} was kicked with its {W} kicker, it enters with a +1/+1 counter on it and with \"Whenever {this} deals damage, you gain that much life.\"", ""); + ability.addEffect(new GainAbilitySourceEffect(new DealsDamageSourceTriggeredAbility(new GainLifeEffect(SavedDamageValue.MUCH)), Duration.WhileOnBattlefield)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/n/NettlingNuisance.java b/Mage.Sets/src/mage/cards/n/NettlingNuisance.java index 9199022b7ce..77555f00868 100644 --- a/Mage.Sets/src/mage/cards/n/NettlingNuisance.java +++ b/Mage.Sets/src/mage/cards/n/NettlingNuisance.java @@ -11,7 +11,7 @@ import mage.game.permanent.token.Token; import mage.players.Player; import mage.target.targetpointer.FixedTarget; import mage.abilities.Ability; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.combat.GoadTargetEffect; import mage.abilities.keyword.FlyingAbility; @@ -42,7 +42,7 @@ public final class NettlingNuisance extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Whenever one or more Faeries you control deal combat damage to a player, that player creates a 4/2 red Pirate creature token with "This creature can't block." The token is goaded for the rest of the game. - this.addAbility(new DealCombatDamageControlledTriggeredAbility(Zone.BATTLEFIELD, new NettlingNuisanceEffect(), filter, SetTargetPointer.PLAYER, false)); + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility(Zone.BATTLEFIELD, new NettlingNuisanceEffect(), filter, SetTargetPointer.PLAYER, false)); } private NettlingNuisance(final NettlingNuisance card) { diff --git a/Mage.Sets/src/mage/cards/n/NoblePurpose.java b/Mage.Sets/src/mage/cards/n/NoblePurpose.java index 2ba177cd605..c80147af5d0 100644 --- a/Mage.Sets/src/mage/cards/n/NoblePurpose.java +++ b/Mage.Sets/src/mage/cards/n/NoblePurpose.java @@ -1,30 +1,31 @@ - package mage.cards.n; -import java.util.UUID; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.DealsDamageToAnyTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SetTargetPointer; import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.DamagedEvent; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author Quercitron */ public final class NoblePurpose extends CardImpl { public NoblePurpose(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{3}{W}{W}"); - + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}{W}"); // Whenever a creature you control deals combat damage, you gain that much life. - this.addAbility(new NoblePurposeTriggeredAbility()); + this.addAbility(new DealsDamageToAnyTriggeredAbility(Zone.BATTLEFIELD, + new GainLifeEffect(SavedDamageValue.MUCH), + StaticFilters.FILTER_CONTROLLED_A_CREATURE, + SetTargetPointer.NONE, true, false + )); } private NoblePurpose(final NoblePurpose card) { @@ -36,46 +37,3 @@ public final class NoblePurpose extends CardImpl { return new NoblePurpose(this); } } - -class NoblePurposeTriggeredAbility extends TriggeredAbilityImpl { - - public NoblePurposeTriggeredAbility() { - super(Zone.BATTLEFIELD, null); - } - - private NoblePurposeTriggeredAbility(final NoblePurposeTriggeredAbility ability) { - super(ability); - } - - @Override - public NoblePurposeTriggeredAbility copy() { - return new NoblePurposeTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT - || event.getType() == GameEvent.EventType.DAMAGED_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - DamagedEvent damageEvent = (DamagedEvent) event; - if (damageEvent.isCombatDamage()) { - Permanent permanent = game.getPermanent(event.getSourceId()); - if (permanent != null && permanent.isCreature(game) - && permanent.isControlledBy(this.getControllerId())) { - this.getEffects().clear(); - this.getEffects().add(new GainLifeEffect(damageEvent.getAmount())); - return true; - } - } - return false; - } - - @Override - public String getRule() { - return "Whenever a creature you control deals combat damage, you gain that much life."; - } - -} diff --git a/Mage.Sets/src/mage/cards/n/NornsDecree.java b/Mage.Sets/src/mage/cards/n/NornsDecree.java index 55e93f08829..f3b013b4745 100644 --- a/Mage.Sets/src/mage/cards/n/NornsDecree.java +++ b/Mage.Sets/src/mage/cards/n/NornsDecree.java @@ -11,6 +11,7 @@ import mage.abilities.effects.common.counter.AddPoisonCounterTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.Zone; /** * @@ -22,7 +23,9 @@ public final class NornsDecree extends CardImpl { super(ownerId, setInfo, new CardType[] { CardType.ENCHANTMENT }, "{2}{W}"); // Whenever one or more creatures an opponent controls deal combat damage to you, that opponent gets a poison counter. - this.addAbility(new CombatDamageDealtToYouTriggeredAbility(new AddPoisonCounterTargetEffect(1).setText("that opponent gets a poison counter."), true)); + this.addAbility(new CombatDamageDealtToYouTriggeredAbility(Zone.BATTLEFIELD, + new AddPoisonCounterTargetEffect(1).setText("that opponent gets a poison counter"), + true, false)); // Whenever a player attacks, if one or more players being attacked are poisoned, the attacking player draws a card. this.addAbility(new ConditionalInterveningIfTriggeredAbility( diff --git a/Mage.Sets/src/mage/cards/o/OathOfLimDul.java b/Mage.Sets/src/mage/cards/o/OathOfLimDul.java index 46aab0dff8a..6367bcca736 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 final class OathOfLimDul extends CardImpl { 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 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 { - 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 @@ class OathOfLimDulEffect extends OneShotEffect { @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; } - return sacrificeDone; + 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; } @Override diff --git a/Mage.Sets/src/mage/cards/o/ObNixilisCaptiveKingpin.java b/Mage.Sets/src/mage/cards/o/ObNixilisCaptiveKingpin.java index 13c3eaff2e5..7981b9e99ef 100644 --- a/Mage.Sets/src/mage/cards/o/ObNixilisCaptiveKingpin.java +++ b/Mage.Sets/src/mage/cards/o/ObNixilisCaptiveKingpin.java @@ -1,8 +1,10 @@ package mage.cards.o; +import java.util.List; import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; @@ -58,7 +60,7 @@ public final class ObNixilisCaptiveKingpin extends CardImpl { } } -class ObNixilisCaptiveKingpinAbility extends TriggeredAbilityImpl { +class ObNixilisCaptiveKingpinAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { ObNixilisCaptiveKingpinAbility(Effect effect) { super(Zone.BATTLEFIELD, effect); @@ -74,28 +76,20 @@ class ObNixilisCaptiveKingpinAbility extends TriggeredAbilityImpl { return event.getType() == GameEvent.EventType.LOST_LIFE_BATCH; } + @Override + public boolean checkEvent(LifeLostEvent event, Game game) { + return game.getOpponents(getControllerId()).contains(event.getTargetId()); + } + @Override public boolean checkTrigger(GameEvent event, Game game) { - - LifeLostBatchEvent lifeLostBatchEvent = (LifeLostBatchEvent) event; - - boolean opponentLostLife = false; - boolean allis1 = true; - - for (UUID targetPlayer : CardUtil.getEventTargets(lifeLostBatchEvent)) { - // skip controller - if (targetPlayer.equals(getControllerId())) { - continue; - } - opponentLostLife = true; - - int lifeLost = lifeLostBatchEvent.getLifeLostByPlayer(targetPlayer); - if (lifeLost != 1) { - allis1 = false; - break; - } + List filteredEvents = getFilteredEvents((LifeLostBatchEvent) event, game); + if (filteredEvents.isEmpty()) { + return false; } - return opponentLostLife && allis1; + // if here, at least one opponent lost some amount of life + return CardUtil.getEventTargets(event).stream() + .allMatch(uuid -> LifeLostBatchEvent.getLifeLostByPlayer(filteredEvents, uuid) <= 1); } @Override diff --git a/Mage.Sets/src/mage/cards/o/OliviaOpulentOutlaw.java b/Mage.Sets/src/mage/cards/o/OliviaOpulentOutlaw.java index 7e6bfd633e2..4212990e8f5 100644 --- a/Mage.Sets/src/mage/cards/o/OliviaOpulentOutlaw.java +++ b/Mage.Sets/src/mage/cards/o/OliviaOpulentOutlaw.java @@ -3,7 +3,7 @@ package mage.cards.o; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.ActivateAsSorceryActivatedAbility; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.CreateTokenEffect; @@ -57,7 +57,7 @@ public final class OliviaOpulentOutlaw extends CardImpl { this.addAbility(LifelinkAbility.getInstance()); // Whenever one or more outlaws you control deal combat damage to a player, create a Treasure token. - this.addAbility(new DealCombatDamageControlledTriggeredAbility( + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility( new CreateTokenEffect(new TreasureToken()), filter )); diff --git a/Mage.Sets/src/mage/cards/o/OliviasAttendants.java b/Mage.Sets/src/mage/cards/o/OliviasAttendants.java index 294986114fb..0edc70de0c8 100644 --- a/Mage.Sets/src/mage/cards/o/OliviasAttendants.java +++ b/Mage.Sets/src/mage/cards/o/OliviasAttendants.java @@ -2,9 +2,10 @@ package mage.cards.o; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.keyword.MenaceAbility; @@ -12,10 +13,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.DamagedBatchAllEvent; -import mage.game.events.GameEvent; import mage.game.permanent.token.BloodToken; import mage.target.common.TargetAnyTarget; @@ -37,7 +34,7 @@ public final class OliviasAttendants extends CardImpl { this.addAbility(new MenaceAbility(false)); // Whenever Olivia's Attendants deals damage, create that many Blood tokens. - this.addAbility(new OliviasAttendantsTriggeredAbility()); + this.addAbility(new DealsDamageSourceTriggeredAbility(new CreateTokenEffect(new BloodToken(), SavedDamageValue.MANY))); // {2}{R}: Olivia's Attendants deals 1 damage to any target. Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(1), new ManaCostsImpl<>("{2}{R}")); @@ -54,45 +51,3 @@ public final class OliviasAttendants extends CardImpl { return new OliviasAttendants(this); } } - -class OliviasAttendantsTriggeredAbility extends TriggeredAbilityImpl { - - OliviasAttendantsTriggeredAbility() { - super(Zone.BATTLEFIELD, null); - } - - private OliviasAttendantsTriggeredAbility(final OliviasAttendantsTriggeredAbility ability) { - super(ability); - } - - @Override - public OliviasAttendantsTriggeredAbility copy() { - return new OliviasAttendantsTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ALL; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - int amount = ((DamagedBatchAllEvent) event) - .getEvents() - .stream() - .filter(e -> e.getAttackerId().equals(this.getSourceId())) - .mapToInt(GameEvent::getAmount) - .sum(); - if (amount < 1) { - return false; - } - this.getEffects().clear(); - this.addEffect(new CreateTokenEffect(new BloodToken(), amount)); - return true; - } - - @Override - public String getRule() { - return "Whenever {this} deals damage, create that many Blood tokens."; - } -} diff --git a/Mage.Sets/src/mage/cards/o/OngoingInvestigation.java b/Mage.Sets/src/mage/cards/o/OngoingInvestigation.java index a94a6c5a090..a1e89a5044b 100644 --- a/Mage.Sets/src/mage/cards/o/OngoingInvestigation.java +++ b/Mage.Sets/src/mage/cards/o/OngoingInvestigation.java @@ -3,7 +3,7 @@ package mage.cards.o; import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.ExileFromGraveCost; import mage.abilities.costs.mana.ManaCostsImpl; @@ -12,7 +12,6 @@ import mage.abilities.effects.keyword.InvestigateEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; import mage.filter.common.FilterCreatureCard; import mage.target.common.TargetCardInYourGraveyard; @@ -26,7 +25,7 @@ public final class OngoingInvestigation extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}"); // Whenever one or more creatures you control deal combat damage to a player, investigate. - this.addAbility(new DealCombatDamageControlledTriggeredAbility(new InvestigateEffect())); + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility(new InvestigateEffect())); // {1}{G}, Exile a creature card from your graveyard: Investigate. You gain 2 life. Ability ability = new SimpleActivatedAbility(new InvestigateEffect().setText("investigate"), new ManaCostsImpl<>("{1}{G}")); diff --git a/Mage.Sets/src/mage/cards/o/OrochiSoulReaver.java b/Mage.Sets/src/mage/cards/o/OrochiSoulReaver.java index 7601199ad9f..13b39a40b2d 100644 --- a/Mage.Sets/src/mage/cards/o/OrochiSoulReaver.java +++ b/Mage.Sets/src/mage/cards/o/OrochiSoulReaver.java @@ -2,7 +2,7 @@ package mage.cards.o; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.keyword.ManifestEffect; @@ -37,7 +37,7 @@ public final class OrochiSoulReaver extends CardImpl { this.addAbility(new NinjutsuAbility("{3}{B}")); // Whenever one or more creatures you control deal combat damage to a player, create a Treasure token and manifest the top card of that player's library. - Ability ability = new DealCombatDamageControlledTriggeredAbility( + Ability ability = new OneOrMoreCombatDamagePlayerTriggeredAbility( new CreateTokenEffect(new TreasureToken()), SetTargetPointer.PLAYER ); diff --git a/Mage.Sets/src/mage/cards/p/PaladinOfPrahv.java b/Mage.Sets/src/mage/cards/p/PaladinOfPrahv.java index 1b72dcd1856..7ec2d659f5d 100644 --- a/Mage.Sets/src/mage/cards/p/PaladinOfPrahv.java +++ b/Mage.Sets/src/mage/cards/p/PaladinOfPrahv.java @@ -1,16 +1,14 @@ - package mage.cards.p; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; -import mage.abilities.common.DealsDamageGainLifeSourceTriggeredAbility; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.Effect; -import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.keyword.ForecastAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -22,22 +20,23 @@ import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author FenrisulfrX */ public final class PaladinOfPrahv extends CardImpl { public PaladinOfPrahv(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{W}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}{W}"); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.KNIGHT); this.power = new MageInt(3); this.toughness = new MageInt(4); // Whenever Paladin of Prahv deals damage, you gain that much life. - this.addAbility(new DealsDamageGainLifeSourceTriggeredAbility()); - + this.addAbility(new DealsDamageSourceTriggeredAbility(new GainLifeEffect(SavedDamageValue.MUCH))); + // Forecast - {1}{W}, Reveal Paladin of Prahv from your hand: Whenever target creature deals damage this turn, you gain that much life. Ability ability = new ForecastAbility(new CreateDelayedTriggeredAbilityEffect( new PaladinOfPrahvTriggeredAbility()), new ManaCostsImpl<>("{1}{W}")); @@ -56,7 +55,7 @@ public final class PaladinOfPrahv extends CardImpl { } class PaladinOfPrahvTriggeredAbility extends DelayedTriggeredAbility { - + public PaladinOfPrahvTriggeredAbility() { super(new GainLifeEffect(SavedDamageValue.MUCH), Duration.EndOfTurn, false); setTriggerPhrase("Whenever target creature deals damage this turn, "); @@ -73,7 +72,7 @@ class PaladinOfPrahvTriggeredAbility extends DelayedTriggeredAbility { @Override public boolean checkEventType(GameEvent event, Game game) { - switch(event.getType()) { + switch (event.getType()) { case DAMAGED_PERMANENT: case DAMAGED_PLAYER: return true; diff --git a/Mage.Sets/src/mage/cards/p/PhantomNishoba.java b/Mage.Sets/src/mage/cards/p/PhantomNishoba.java index bf61b40e26c..557a3d4e664 100644 --- a/Mage.Sets/src/mage/cards/p/PhantomNishoba.java +++ b/Mage.Sets/src/mage/cards/p/PhantomNishoba.java @@ -1,11 +1,12 @@ - package mage.cards.p; import mage.MageInt; -import mage.abilities.common.DealsDamageGainLifeSourceTriggeredAbility; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.PreventDamageAndRemoveCountersEffect; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; @@ -36,7 +37,7 @@ public final class PhantomNishoba extends CardImpl { this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(7), true), "with seven +1/+1 counters on it")); // Whenever Phantom Nishoba deals damage, you gain that much life. - this.addAbility(new DealsDamageGainLifeSourceTriggeredAbility()); + this.addAbility(new DealsDamageSourceTriggeredAbility(new GainLifeEffect(SavedDamageValue.MUCH))); // If damage would be dealt to Phantom Nishoba, prevent that damage. Remove a +1/+1 counter from Phantom Nishoba. this.addAbility(new SimpleStaticAbility( @@ -52,4 +53,4 @@ public final class PhantomNishoba extends CardImpl { public PhantomNishoba copy() { return new PhantomNishoba(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/p/PhyrexianTotem.java b/Mage.Sets/src/mage/cards/p/PhyrexianTotem.java index 6c95c79c44c..f03f6d65846 100644 --- a/Mage.Sets/src/mage/cards/p/PhyrexianTotem.java +++ b/Mage.Sets/src/mage/cards/p/PhyrexianTotem.java @@ -1,45 +1,47 @@ - package mage.cards.p; -import java.util.UUID; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.Ability; +import mage.abilities.common.DealtDamageToSourceTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.Condition; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.dynamicvalue.common.StaticValue; -import mage.abilities.effects.common.SacrificeEffect; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.SacrificeControllerEffect; import mage.abilities.effects.common.continuous.BecomesCreatureSourceEffect; import mage.abilities.keyword.TrampleAbility; import mage.abilities.mana.BlackManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Duration; -import mage.constants.Zone; -import mage.filter.common.FilterControlledPermanent; +import mage.constants.SubType; +import mage.filter.StaticFilters; import mage.game.Game; -import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.permanent.token.TokenImpl; -import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; /** - * * @author FenrisulfrX */ public final class PhyrexianTotem extends CardImpl { public PhyrexianTotem(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{3}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); // {tap}: Add {B}. this.addAbility(new BlackManaAbility()); + // {2}{B}: {this} becomes a 5/5 black Horror artifact creature with trample until end of turn. this.addAbility(new SimpleActivatedAbility(new BecomesCreatureSourceEffect( new PhyrexianTotemToken(), CardType.ARTIFACT, Duration.EndOfTurn), new ManaCostsImpl<>("{2}{B}"))); + // Whenever {this} is dealt damage, if it's a creature, sacrifice that many permanents. - this.addAbility(new PhyrexianTotemTriggeredAbility()); + this.addAbility(new DealtDamageToSourceTriggeredAbility( + new SacrificeControllerEffect(StaticFilters.FILTER_PERMANENTS, SavedDamageValue.MANY, ""), false + ).withInterveningIf(PhyrexianTotemCondition.instance)); } private PhyrexianTotem(final PhyrexianTotem card) { @@ -50,7 +52,7 @@ public final class PhyrexianTotem extends CardImpl { public PhyrexianTotem copy() { return new PhyrexianTotem(this); } - + private static class PhyrexianTotemToken extends TokenImpl { PhyrexianTotemToken() { super("Phyrexian Horror", "5/5 black Phyrexian Horror artifact creature with trample"); @@ -63,6 +65,7 @@ public final class PhyrexianTotem extends CardImpl { toughness = new MageInt(5); this.addAbility(TrampleAbility.getInstance()); } + private PhyrexianTotemToken(final PhyrexianTotemToken token) { super(token); } @@ -73,47 +76,17 @@ public final class PhyrexianTotem extends CardImpl { } } -class PhyrexianTotemTriggeredAbility extends TriggeredAbilityImpl { - - public PhyrexianTotemTriggeredAbility() { - super(Zone.BATTLEFIELD, new SacrificeEffect(new FilterControlledPermanent(), 0,"")); - } - - private PhyrexianTotemTriggeredAbility(final PhyrexianTotemTriggeredAbility ability) { - super(ability); - } - +enum PhyrexianTotemCondition implements Condition { + instance; + @Override - public PhyrexianTotemTriggeredAbility copy() { - return new PhyrexianTotemTriggeredAbility(this); - } - - @Override - public boolean checkInterveningIfClause(Game game) { - Permanent permanent = game.getPermanentOrLKIBattlefield(getSourceId()); - if (permanent != null) { - return permanent.isCreature(game); - } - return false; + public boolean apply(Game game, Ability source) { + Permanent permanent = source.getSourcePermanentOrLKI(game); + return permanent != null && permanent.isCreature(game); } @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(getSourceId())) { - getEffects().get(0).setTargetPointer(new FixedTarget(getControllerId())); - ((SacrificeEffect) getEffects().get(0)).setAmount(StaticValue.get(event.getAmount())); - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever {this} is dealt damage, if it's a creature, sacrifice that many permanents."; + public String toString() { + return "it's a creature"; } } diff --git a/Mage.Sets/src/mage/cards/p/PiousWarrior.java b/Mage.Sets/src/mage/cards/p/PiousWarrior.java index 892927fc46e..9b0087a87eb 100644 --- a/Mage.Sets/src/mage/cards/p/PiousWarrior.java +++ b/Mage.Sets/src/mage/cards/p/PiousWarrior.java @@ -1,30 +1,23 @@ - package mage.cards.p; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.common.DealtCombatDamageToSourceTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Outcome; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.DamagedBatchForOnePermanentEvent; -import mage.game.events.GameEvent; -import mage.players.Player; + +import java.util.UUID; /** - * * @author Backfir3 */ public final class PiousWarrior extends CardImpl { public PiousWarrior(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.REBEL); this.subtype.add(SubType.WARRIOR); @@ -32,8 +25,8 @@ public final class PiousWarrior extends CardImpl { this.power = new MageInt(2); this.toughness = new MageInt(3); - // Whenever Pious Warrior is dealt combat damage, you gain that much life. - this.addAbility(new PiousWarriorTriggeredAbility()); + // Whenever Pious Warrior is dealt combat damage, you gain that much life. + this.addAbility(new DealtCombatDamageToSourceTriggeredAbility(new GainLifeEffect(SavedDamageValue.MUCH), false)); } private PiousWarrior(final PiousWarrior card) { @@ -45,66 +38,3 @@ public final class PiousWarrior extends CardImpl { return new PiousWarrior(this); } } - -class PiousWarriorTriggeredAbility extends TriggeredAbilityImpl { - - public PiousWarriorTriggeredAbility() { - super(Zone.BATTLEFIELD, new PiousWarriorGainLifeEffect()); - setTriggerPhrase("Whenever {this} is dealt combat damage, "); - } - - private PiousWarriorTriggeredAbility(final PiousWarriorTriggeredAbility effect) { - super(effect); - } - - @Override - public PiousWarriorTriggeredAbility copy() { - return new PiousWarriorTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - - DamagedBatchForOnePermanentEvent dEvent = (DamagedBatchForOnePermanentEvent) event; - int damage = dEvent.getAmount(); - - if (event.getTargetId().equals(this.sourceId) && dEvent.isCombatDamage() && damage > 0) { - this.getEffects().setValue("damageAmount", damage); - return true; - } - return false; - } -} - - -class PiousWarriorGainLifeEffect extends OneShotEffect { - - public PiousWarriorGainLifeEffect() { - super(Outcome.GainLife); - staticText = "you gain that much life"; - } - - private PiousWarriorGainLifeEffect(final PiousWarriorGainLifeEffect effect) { - super(effect); - } - - @Override - public PiousWarriorGainLifeEffect copy() { - return new PiousWarriorGainLifeEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - player.gainLife((Integer) this.getValue("damageAmount"), game, source); - } - return true; - } - -} diff --git a/Mage.Sets/src/mage/cards/p/PopularEntertainer.java b/Mage.Sets/src/mage/cards/p/PopularEntertainer.java index fa5afd36317..574df1bd765 100644 --- a/Mage.Sets/src/mage/cards/p/PopularEntertainer.java +++ b/Mage.Sets/src/mage/cards/p/PopularEntertainer.java @@ -1,5 +1,6 @@ package mage.cards.p; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.combat.GoadTargetEffect; @@ -13,8 +14,10 @@ import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.Game; import mage.game.events.DamagedBatchForOnePlayerEvent; +import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; +import mage.players.Player; import mage.target.TargetPermanent; import java.util.UUID; @@ -47,7 +50,7 @@ public final class PopularEntertainer extends CardImpl { } } -class PopularEntertainerAbility extends TriggeredAbilityImpl { +class PopularEntertainerAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { PopularEntertainerAbility() { super(Zone.BATTLEFIELD, new GoadTargetEffect(), false); @@ -68,24 +71,24 @@ class PopularEntertainerAbility extends TriggeredAbilityImpl { } @Override - public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchForOnePlayerEvent dEvent = (DamagedBatchForOnePlayerEvent) event; - int damage = dEvent.getEvents() - .stream() - .filter(e -> { - Permanent permanent = game.getPermanentOrLKIBattlefield(e.getSourceId()); - return permanent != null && permanent.isControlledBy(getControllerId()); - }) - .mapToInt(GameEvent::getAmount) - .sum(); + public boolean checkEvent(DamagedPlayerEvent event, Game game) { + if (!event.isCombatDamage() || event.getAmount() <= 0) { + return false; + } + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); + return permanent != null && isControlledBy(permanent.getControllerId()); + } - if (!dEvent.isCombatDamage() || damage < 1) { + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Player player = game.getPlayer(event.getTargetId()); + if (player == null || getFilteredEvents((DamagedBatchForOnePlayerEvent) event, game).isEmpty()) { return false; } FilterPermanent filter = new FilterCreaturePermanent( - "creature controlled by " + game.getPlayer(dEvent.getTargetId()).getName() + "creature controlled by " + player.getName() ); - filter.add(new ControllerIdPredicate(dEvent.getTargetId())); + filter.add(new ControllerIdPredicate(player.getId())); this.getTargets().clear(); this.addTarget(new TargetPermanent(filter)); return true; diff --git a/Mage.Sets/src/mage/cards/p/ProfessionalFaceBreaker.java b/Mage.Sets/src/mage/cards/p/ProfessionalFaceBreaker.java index e3d8ab66b4d..558db413c91 100644 --- a/Mage.Sets/src/mage/cards/p/ProfessionalFaceBreaker.java +++ b/Mage.Sets/src/mage/cards/p/ProfessionalFaceBreaker.java @@ -1,7 +1,7 @@ package mage.cards.p; import mage.MageInt; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.effects.common.CreateTokenEffect; @@ -37,7 +37,7 @@ public final class ProfessionalFaceBreaker extends CardImpl { this.addAbility(new MenaceAbility(false)); // Whenever one or more creatures you control deal combat damage to a player, create a Treasure token. - this.addAbility(new DealCombatDamageControlledTriggeredAbility(new CreateTokenEffect(new TreasureToken()))); + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility(new CreateTokenEffect(new TreasureToken()))); // Sacrifice a Treasure: Exile the top card of your library. You may play that card this turn. this.addAbility(new SimpleActivatedAbility( diff --git a/Mage.Sets/src/mage/cards/p/ProsperousThief.java b/Mage.Sets/src/mage/cards/p/ProsperousThief.java index c1ffc842f26..fcc128717d5 100644 --- a/Mage.Sets/src/mage/cards/p/ProsperousThief.java +++ b/Mage.Sets/src/mage/cards/p/ProsperousThief.java @@ -1,7 +1,7 @@ package mage.cards.p; import mage.MageInt; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.keyword.NinjutsuAbility; import mage.cards.CardImpl; @@ -37,7 +37,7 @@ public final class ProsperousThief extends CardImpl { this.addAbility(new NinjutsuAbility("{1}{U}")); // Whenever one or more Ninja or Rogue creatures you control deal combat damage to a player, create a Treasure token. - this.addAbility(new DealCombatDamageControlledTriggeredAbility(new CreateTokenEffect(new TreasureToken()), filter)); + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility(new CreateTokenEffect(new TreasureToken()), filter)); } private ProsperousThief(final ProsperousThief card) { @@ -48,4 +48,4 @@ public final class ProsperousThief extends CardImpl { public ProsperousThief copy() { return new ProsperousThief(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/p/PutridWarrior.java b/Mage.Sets/src/mage/cards/p/PutridWarrior.java index 5ed11edfaee..f46b567d50d 100644 --- a/Mage.Sets/src/mage/cards/p/PutridWarrior.java +++ b/Mage.Sets/src/mage/cards/p/PutridWarrior.java @@ -1,34 +1,28 @@ - package mage.cards.p; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.Mode; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.Effect; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.LoseLifeAllPlayersEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; -import mage.constants.Zone; +import mage.constants.SubType; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.players.Player; -/** - * - * @author LoneFox +import java.util.UUID; +/** + * @author LoneFox */ public final class PutridWarrior extends CardImpl { public PutridWarrior(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{W}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{B}"); this.subtype.add(SubType.ZOMBIE); this.subtype.add(SubType.SOLDIER); this.subtype.add(SubType.WARRIOR); @@ -36,9 +30,8 @@ public final class PutridWarrior extends CardImpl { this.toughness = new MageInt(2); // Whenever Putrid Warrior deals damage, choose one - Each player loses 1 life; or each player gains 1 life. - Ability ability = new PutridWarriorDealsDamageTriggeredAbility(new LoseLifeAllPlayersEffect(1)); - Mode mode = new Mode(new PutridWarriorGainLifeEffect()); - ability.addMode(mode); + Ability ability = new DealsDamageSourceTriggeredAbility(new LoseLifeAllPlayersEffect(1)); + ability.addMode(new Mode(new PutridWarriorGainLifeEffect())); this.addAbility(ability); } @@ -52,41 +45,11 @@ public final class PutridWarrior extends CardImpl { } } - -class PutridWarriorDealsDamageTriggeredAbility extends TriggeredAbilityImpl { - - public PutridWarriorDealsDamageTriggeredAbility(Effect effect) { - super(Zone.BATTLEFIELD, effect, false); - setTriggerPhrase("Whenever {this} deals damage, " ); - } - - private PutridWarriorDealsDamageTriggeredAbility(final PutridWarriorDealsDamageTriggeredAbility ability) { - super(ability); - } - - @Override - public PutridWarriorDealsDamageTriggeredAbility copy() { - return new PutridWarriorDealsDamageTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER - || event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return event.getSourceId().equals(this.sourceId); - } -} - - class PutridWarriorGainLifeEffect extends OneShotEffect { PutridWarriorGainLifeEffect() { super(Outcome.GainLife); - staticText = "Each player gains 1 life."; + staticText = "each player gains 1 life"; } private PutridWarriorGainLifeEffect(final PutridWarriorGainLifeEffect effect) { @@ -99,10 +62,10 @@ class PutridWarriorGainLifeEffect extends OneShotEffect { } @Override - public boolean apply(Game game, Ability source) { - for(UUID playerId: game.getState().getPlayersInRange(source.getControllerId(), game)) { + public boolean apply(Game game, Ability source) { + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { Player player = game.getPlayer(playerId); - if(player != null) { + if (player != null) { player.gainLife(1, game, source); } } diff --git a/Mage.Sets/src/mage/cards/p/PyrewildShaman.java b/Mage.Sets/src/mage/cards/p/PyrewildShaman.java index 14712f05535..93dc40f06c0 100644 --- a/Mage.Sets/src/mage/cards/p/PyrewildShaman.java +++ b/Mage.Sets/src/mage/cards/p/PyrewildShaman.java @@ -1,7 +1,7 @@ package mage.cards.p; import mage.MageInt; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.condition.common.SourceInGraveyardCondition; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DoIfCostPaid; @@ -29,7 +29,7 @@ public final class PyrewildShaman extends CardImpl { this.addAbility(new BloodrushAbility("{1}{R}", new BoostTargetEffect(3, 1, Duration.EndOfTurn))); // Whenever one or more creatures you control deal combat damage to a player, if Pyrewild Shaman is in your graveyard, you may pay {3}. If you do, return Pyrewild Shaman to your hand. - this.addAbility(new DealCombatDamageControlledTriggeredAbility(Zone.GRAVEYARD, new DoIfCostPaid( + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility(Zone.GRAVEYARD, new DoIfCostPaid( new ReturnToHandSourceEffect().setText("return {this} to your hand"), new ManaCostsImpl<>("{3}") ), StaticFilters.FILTER_PERMANENT_CREATURES, SetTargetPointer.NONE, false) diff --git a/Mage.Sets/src/mage/cards/q/QuartzwoodCrasher.java b/Mage.Sets/src/mage/cards/q/QuartzwoodCrasher.java index c8d96a93e9d..70ff383a1a6 100644 --- a/Mage.Sets/src/mage/cards/q/QuartzwoodCrasher.java +++ b/Mage.Sets/src/mage/cards/q/QuartzwoodCrasher.java @@ -2,7 +2,7 @@ package mage.cards.q; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.TrampleAbility; @@ -41,7 +41,7 @@ public final class QuartzwoodCrasher extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // Whenever one or more creatures you control with trample deal combat damage to a player, create an X/X green Dinosaur Beast creature token with trample, where X is the amount of damage those creatures dealt to that player. - this.addAbility(new DealCombatDamageControlledTriggeredAbility(new QuartzwoodCrasherEffect(), filter)); + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility(new QuartzwoodCrasherEffect(), filter)); } private QuartzwoodCrasher(final QuartzwoodCrasher card) { diff --git a/Mage.Sets/src/mage/cards/r/RaggedVeins.java b/Mage.Sets/src/mage/cards/r/RaggedVeins.java index a468b0cd46f..9e20d3d0a71 100644 --- a/Mage.Sets/src/mage/cards/r/RaggedVeins.java +++ b/Mage.Sets/src/mage/cards/r/RaggedVeins.java @@ -36,8 +36,7 @@ public final class RaggedVeins extends CardImpl { // Whenever enchanted creature is dealt damage, its controller loses that much life. this.addAbility(new DealtDamageAttachedTriggeredAbility( - new LoseLifeControllerAttachedEffect(SavedDamageValue.MUCH), - false + new LoseLifeControllerAttachedEffect(SavedDamageValue.MUCH) )); } diff --git a/Mage.Sets/src/mage/cards/r/Rakavolver.java b/Mage.Sets/src/mage/cards/r/Rakavolver.java index d27c30d0d7f..c1d2713ccb3 100644 --- a/Mage.Sets/src/mage/cards/r/Rakavolver.java +++ b/Mage.Sets/src/mage/cards/r/Rakavolver.java @@ -1,12 +1,12 @@ - package mage.cards.r; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.DealsDamageGainLifeSourceTriggeredAbility; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.condition.common.KickedCostCondition; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.FlyingAbility; @@ -14,19 +14,19 @@ import mage.abilities.keyword.KickerAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Duration; +import mage.constants.SubType; import mage.counters.CounterType; -/** - * - * @author LoneFox +import java.util.UUID; +/** + * @author LoneFox */ public final class Rakavolver extends CardImpl { public Rakavolver(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); this.subtype.add(SubType.VOLVER); this.power = new MageInt(2); this.toughness = new MageInt(2); @@ -35,14 +35,16 @@ public final class Rakavolver extends CardImpl { KickerAbility kickerAbility = new KickerAbility("{1}{W}"); kickerAbility.addKickerCost("{U}"); this.addAbility(kickerAbility); + // If Rakavolver was kicked with its {1}{W} kicker, it enters with two +1/+1 counters on it and with "Whenever Rakavolver deals damage, you gain that much life." - Ability ability = new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), - new KickedCostCondition("{1}{W}"), "If {this} was kicked with its {1}{W} kicker, it enters with two +1/+1 counters on it and with \"Whenever {this} deals damage, you gain that much life.\"", ""); - ability.addEffect(new GainAbilitySourceEffect(new DealsDamageGainLifeSourceTriggeredAbility(), Duration.WhileOnBattlefield)); + Ability ability = new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), + new KickedCostCondition("{1}{W}"), "If {this} was kicked with its {1}{W} kicker, it enters with two +1/+1 counters on it and with \"Whenever {this} deals damage, you gain that much life.\"", ""); + ability.addEffect(new GainAbilitySourceEffect(new DealsDamageSourceTriggeredAbility(new GainLifeEffect(SavedDamageValue.MUCH)), Duration.WhileOnBattlefield)); this.addAbility(ability); + // If Rakavolver was kicked with its {U} kicker, it enters with a +1/+1 counter on it and with flying. - ability = new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(1)), - new KickedCostCondition("{U}"), "If {this} was kicked with its {U} kicker, it enters with a +1/+1 counter on it and with flying.", ""); + ability = new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(1)), + new KickedCostCondition("{U}"), "If {this} was kicked with its {U} kicker, it enters with a +1/+1 counter on it and with flying.", ""); ability.addEffect(new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.WhileOnBattlefield)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/r/RamirezDePietroPillager.java b/Mage.Sets/src/mage/cards/r/RamirezDePietroPillager.java index ed86b53a164..71d5e88863e 100644 --- a/Mage.Sets/src/mage/cards/r/RamirezDePietroPillager.java +++ b/Mage.Sets/src/mage/cards/r/RamirezDePietroPillager.java @@ -2,7 +2,7 @@ package mage.cards.r; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; @@ -41,7 +41,7 @@ public final class RamirezDePietroPillager extends CardImpl { this.addAbility(ability); // Whenever one or more Pirates you control deal combat damage to a player, exile the top card of that player's library. You may cast that card for as long as it remains exiled. - this.addAbility(new DealCombatDamageControlledTriggeredAbility(Zone.BATTLEFIELD, new RamirezDePietroPillagerEffect(), + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility(Zone.BATTLEFIELD, new RamirezDePietroPillagerEffect(), filter, SetTargetPointer.PLAYER, false)); } diff --git a/Mage.Sets/src/mage/cards/r/RapaciousGuest.java b/Mage.Sets/src/mage/cards/r/RapaciousGuest.java index cc8b2f81845..77a0a5124ac 100644 --- a/Mage.Sets/src/mage/cards/r/RapaciousGuest.java +++ b/Mage.Sets/src/mage/cards/r/RapaciousGuest.java @@ -2,7 +2,7 @@ package mage.cards.r; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.LeavesBattlefieldTriggeredAbility; import mage.abilities.common.SacrificePermanentTriggeredAbility; import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; @@ -38,7 +38,7 @@ public final class RapaciousGuest extends CardImpl { this.addAbility(new MenaceAbility(false)); // Whenever one or more creatures you control deal combat damage to a player, create a Food token. - this.addAbility(new DealCombatDamageControlledTriggeredAbility( + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility( new CreateTokenEffect(new FoodToken()) )); diff --git a/Mage.Sets/src/mage/cards/r/Repercussion.java b/Mage.Sets/src/mage/cards/r/Repercussion.java index 2f7c4e51c30..d4922d0febd 100644 --- a/Mage.Sets/src/mage/cards/r/Repercussion.java +++ b/Mage.Sets/src/mage/cards/r/Repercussion.java @@ -1,19 +1,13 @@ package mage.cards.r; -import mage.MageObjectReference; -import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.Effect; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.common.DealtDamageAnyTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.DamageTargetEffect; 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.game.permanent.Permanent; -import mage.players.Player; +import mage.constants.SetTargetPointer; +import mage.filter.StaticFilters; import java.util.UUID; @@ -26,7 +20,9 @@ public final class Repercussion extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}{R}"); // Whenever a creature is dealt damage, Repercussion deals that much damage to that creature's controller. - this.addAbility(new RepercussionTriggeredAbility(new RepercussionEffect())); + this.addAbility(new DealtDamageAnyTriggeredAbility( + new DamageTargetEffect(SavedDamageValue.MUCH).withTargetDescription("that creature's controller"), + StaticFilters.FILTER_PERMANENT_A_CREATURE, SetTargetPointer.PLAYER, false)); } private Repercussion(final Repercussion card) { @@ -38,77 +34,3 @@ public final class Repercussion extends CardImpl { return new Repercussion(this); } } - -class RepercussionTriggeredAbility extends TriggeredAbilityImpl { - - static final String PLAYER_DAMAGE_AMOUNT_KEY = "playerDamage"; - static final String TRIGGERING_CREATURE_KEY = "triggeringCreature"; - - public RepercussionTriggeredAbility(Effect effect) { - super(Zone.BATTLEFIELD, effect); - } - - private RepercussionTriggeredAbility(final RepercussionTriggeredAbility ability) { - super(ability); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - Permanent permanent = game.getPermanent(event.getTargetId()); - if (permanent == null || !permanent.isCreature(game)) { - return false; - } - getEffects().setValue(PLAYER_DAMAGE_AMOUNT_KEY, event.getAmount()); - getEffects().setValue(TRIGGERING_CREATURE_KEY, new MageObjectReference(event.getTargetId(), game)); - return true; - } - - @Override - public String getRule() { - return "Whenever a creature is dealt damage, {this} deals that much damage to that creature's controller."; - } - - @Override - public RepercussionTriggeredAbility copy() { - return new RepercussionTriggeredAbility(this); - } -} - -class RepercussionEffect extends OneShotEffect { - - RepercussionEffect() { - super(Outcome.Damage); - } - - private RepercussionEffect(final RepercussionEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - Integer playerDamage = (Integer) this.getValue(RepercussionTriggeredAbility.PLAYER_DAMAGE_AMOUNT_KEY); - MageObjectReference mor = (MageObjectReference) this.getValue(RepercussionTriggeredAbility.TRIGGERING_CREATURE_KEY); - if (playerDamage != null && mor != null) { - Permanent creature = mor.getPermanentOrLKIBattlefield(game); - if (creature != null) { - Player player = game.getPlayer(creature.getControllerId()); - if (player != null) { - player.damage(playerDamage, source.getSourceId(), source, game); - } - } - return true; - } - return false; - } - - @Override - public RepercussionEffect copy() { - return new RepercussionEffect(this); - } - -} diff --git a/Mage.Sets/src/mage/cards/r/RisonaAsariCommander.java b/Mage.Sets/src/mage/cards/r/RisonaAsariCommander.java index 910cfe40ec0..bd0bbdeba0a 100644 --- a/Mage.Sets/src/mage/cards/r/RisonaAsariCommander.java +++ b/Mage.Sets/src/mage/cards/r/RisonaAsariCommander.java @@ -1,28 +1,29 @@ package mage.cards.r; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; import mage.abilities.condition.Condition; -import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.effects.common.counter.RemoveCounterSourceEffect; -import mage.constants.SubType; -import mage.constants.SuperType; import mage.abilities.keyword.HasteAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; -import mage.game.events.DamagedBatchForPlayersEvent; -import mage.game.events.DamagedEvent; +import mage.game.events.DamagedBatchForOnePlayerEvent; +import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; +import java.util.UUID; + /** * * @author weirddan455 @@ -42,11 +43,9 @@ public final class RisonaAsariCommander extends CardImpl { this.addAbility(HasteAbility.getInstance()); // Whenever Risona, Asari Commander deals combat damage to a player, if it doesn't have an indestructible counter on it, put an indestructible counter on it. - this.addAbility(new ConditionalInterveningIfTriggeredAbility( - new DealsCombatDamageToAPlayerTriggeredAbility(new AddCountersSourceEffect(CounterType.INDESTRUCTIBLE.createInstance()), false), - RisonaAsariCommanderCondition.instance, - "Whenever {this} deals combat damage to a player, if it doesn't have an indestructible counter on it, put an indestructible counter on it." - )); + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new AddCountersSourceEffect( + CounterType.INDESTRUCTIBLE.createInstance()), false + ).withInterveningIf(RisonaAsariCommanderCondition.instance)); // Whenever combat damage is dealt to you, remove an indestructible counter from Risona. this.addAbility(new RisonaAsariCommanderTriggeredAbility()); @@ -62,9 +61,9 @@ public final class RisonaAsariCommander extends CardImpl { } } -class RisonaAsariCommanderTriggeredAbility extends TriggeredAbilityImpl { +class RisonaAsariCommanderTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { - public RisonaAsariCommanderTriggeredAbility() { + RisonaAsariCommanderTriggeredAbility() { super(Zone.BATTLEFIELD, new RemoveCounterSourceEffect(CounterType.INDESTRUCTIBLE.createInstance())); setTriggerPhrase("Whenever combat damage is dealt to you, "); } @@ -80,16 +79,17 @@ class RisonaAsariCommanderTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_PLAYERS; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; } @Override public boolean checkTrigger(GameEvent event, Game game) { - return ((DamagedBatchForPlayersEvent) event) - .getEvents() - .stream() - .filter(DamagedEvent::isCombatDamage) - .anyMatch(e -> e.getTargetId().equals(getControllerId())); + // all events in the batch are always relevant + if (isControlledBy(event.getTargetId()) && ((DamagedBatchForOnePlayerEvent) event).isCombatDamage()) { + this.getAllEffects().setValue("damage", event.getAmount()); + return true; + } + return false; } } @@ -101,4 +101,9 @@ enum RisonaAsariCommanderCondition implements Condition { Permanent permanent = source.getSourcePermanentIfItStillExists(game); return permanent != null && permanent.getCounters(game).getCount(CounterType.INDESTRUCTIBLE) == 0; } + + @Override + public String toString() { + return "it doesn't have an indestructible counter on it"; + } } diff --git a/Mage.Sets/src/mage/cards/r/RiteOfPassage.java b/Mage.Sets/src/mage/cards/r/RiteOfPassage.java index 2242d1c3d19..e5bc6c57c53 100644 --- a/Mage.Sets/src/mage/cards/r/RiteOfPassage.java +++ b/Mage.Sets/src/mage/cards/r/RiteOfPassage.java @@ -1,35 +1,28 @@ - package mage.cards.r; -import java.util.UUID; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.Effect; +import mage.abilities.common.DealtDamageAnyTriggeredAbility; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; +import mage.constants.SetTargetPointer; import mage.counters.CounterType; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; /** - * * @author Plopman */ public final class RiteOfPassage extends CardImpl { public RiteOfPassage(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}"); // Whenever a creature you control is dealt damage, put a +1/+1 counter on it. - Effect effect = new AddCountersTargetEffect(CounterType.P1P1.createInstance()); - effect.setText("put a +1/+1 counter on it"); - this.addAbility(new RiteOfPassageTriggeredAbility(effect)); + this.addAbility(new DealtDamageAnyTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()).setText("put a +1/+1 counter on it"), + StaticFilters.FILTER_CONTROLLED_A_CREATURE, SetTargetPointer.PERMANENT, false)); } @@ -42,36 +35,3 @@ public final class RiteOfPassage extends CardImpl { return new RiteOfPassage(this); } } - -class RiteOfPassageTriggeredAbility extends TriggeredAbilityImpl { - - public RiteOfPassageTriggeredAbility(Effect effect) { - super(Zone.BATTLEFIELD, effect); - setTriggerPhrase("Whenever a creature you control is dealt damage, "); - } - - private RiteOfPassageTriggeredAbility(final RiteOfPassageTriggeredAbility ability) { - super(ability); - } - - @Override - public RiteOfPassageTriggeredAbility copy() { - return new RiteOfPassageTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - UUID targetId = event.getTargetId(); - Permanent permanent = game.getPermanent(targetId); - if (permanent != null && StaticFilters.FILTER_CONTROLLED_CREATURE.match(permanent, getControllerId(), this, game)) { - getEffects().setTargetPointer(new FixedTarget(targetId, game)); - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/r/RooftopBypass.java b/Mage.Sets/src/mage/cards/r/RooftopBypass.java index 6587c7dd39b..99d451e2bdc 100644 --- a/Mage.Sets/src/mage/cards/r/RooftopBypass.java +++ b/Mage.Sets/src/mage/cards/r/RooftopBypass.java @@ -2,7 +2,7 @@ package mage.cards.r; import java.util.UUID; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -21,7 +21,7 @@ public final class RooftopBypass extends CardImpl { // Whenever one or more nontoken creatures you control deal combat damage to a player, create a 1/1 black Assassin creature token with menace. - this.addAbility(new DealCombatDamageControlledTriggeredAbility(new CreateTokenEffect(new AssassinMenaceToken()), + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility(new CreateTokenEffect(new AssassinMenaceToken()), StaticFilters.FILTER_CONTROLLED_CREATURES_NON_TOKEN)); } diff --git a/Mage.Sets/src/mage/cards/s/SatoruTheInfiltrator.java b/Mage.Sets/src/mage/cards/s/SatoruTheInfiltrator.java index 44456cc5901..4130c719ba6 100644 --- a/Mage.Sets/src/mage/cards/s/SatoruTheInfiltrator.java +++ b/Mage.Sets/src/mage/cards/s/SatoruTheInfiltrator.java @@ -1,6 +1,7 @@ package mage.cards.s; import mage.MageInt; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.keyword.MenaceAbility; @@ -20,7 +21,6 @@ import mage.game.stack.Spell; import java.util.List; import java.util.UUID; -import java.util.stream.Collectors; /** * @author Susucr @@ -54,9 +54,9 @@ public final class SatoruTheInfiltrator extends CardImpl { } } -class SatoruTheInfiltratorTriggeredAbility extends TriggeredAbilityImpl { +class SatoruTheInfiltratorTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { - public SatoruTheInfiltratorTriggeredAbility() { + SatoruTheInfiltratorTriggeredAbility() { super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), false); this.setTriggerPhrase("Whenever {this} and/or one or more other nontoken creatures " + "enter the battlefield under your control, if none of them were cast or no mana was spent to cast them, "); @@ -76,24 +76,21 @@ class SatoruTheInfiltratorTriggeredAbility extends TriggeredAbilityImpl { return event.getType() == GameEvent.EventType.ZONE_CHANGE_BATCH; } - // event is GameEvent.EventType.ENTERS_THE_BATTLEFIELD + @Override + public boolean checkEvent(ZoneChangeEvent event, Game game) { + if (event.getToZone() != Zone.BATTLEFIELD) { + return false; + } + Permanent permanent = event.getTarget(); + if (permanent == null || !permanent.isControlledBy(getControllerId())) { + return false; + } + return permanent.getId().equals(getSourceId()) || (permanent.isCreature(game) && !(permanent instanceof PermanentToken)); + } + @Override public boolean checkTrigger(GameEvent event, Game game) { - ZoneChangeBatchEvent zEvent = (ZoneChangeBatchEvent) event; - List moved = zEvent.getEvents() - .stream() - .filter(e -> e.getToZone() == Zone.BATTLEFIELD) // Keep only to the battlefield - .filter(e -> { - Permanent permanent = e.getTarget(); - if (permanent == null) { - return false; - } - return permanent.isControlledBy(getControllerId()) // under your control - && (permanent.getId().equals(getSourceId()) // {this} - || (permanent.isCreature(game) && !(permanent instanceof PermanentToken)) // other nontoken Creature - ); - }) - .collect(Collectors.toList()); + List moved = getFilteredEvents((ZoneChangeBatchEvent) event, game); if (moved.isEmpty()) { return false; } diff --git a/Mage.Sets/src/mage/cards/s/ShacklesOfTreachery.java b/Mage.Sets/src/mage/cards/s/ShacklesOfTreachery.java index fd4e874b817..13c9ae218ad 100644 --- a/Mage.Sets/src/mage/cards/s/ShacklesOfTreachery.java +++ b/Mage.Sets/src/mage/cards/s/ShacklesOfTreachery.java @@ -1,7 +1,8 @@ package mage.cards.s; import mage.MageObject; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.TriggeredAbility; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.UntapTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; @@ -11,13 +12,11 @@ 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.filter.common.FilterEquipmentPermanent; import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; -import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; @@ -29,6 +28,13 @@ import java.util.UUID; */ public final class ShacklesOfTreachery extends CardImpl { + private static final FilterPermanent filter + = new FilterEquipmentPermanent("Equipment attached to it"); + + static { + filter.add(ShacklesOfTreacheryPredicate.instance); + } + public ShacklesOfTreachery(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}"); @@ -38,8 +44,13 @@ public final class ShacklesOfTreachery extends CardImpl { this.getSpellAbility().addEffect(new GainAbilityTargetEffect( HasteAbility.getInstance(), Duration.EndOfTurn ).setText("Until end of turn, it gains haste")); + + TriggeredAbility ability = new DealsDamageSourceTriggeredAbility(new DestroyTargetEffect()); + ability.addTarget(new TargetPermanent(filter)); + ability.setTriggerPhrase("Whenever this creature deals damage, "); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect( - new ShacklesOfTreacheryTriggeredAbility(), Duration.EndOfTurn + ability, Duration.EndOfTurn ).setText("and \"Whenever this creature deals damage, destroy target Equipment attached to it.\"")); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); } @@ -54,52 +65,12 @@ public final class ShacklesOfTreachery extends CardImpl { } } -class ShacklesOfTreacheryTriggeredAbility extends TriggeredAbilityImpl { - - private enum ShacklesOfTreacheryPredicate implements ObjectSourcePlayerPredicate { - instance; - - @Override - public boolean apply(ObjectSourcePlayer input, Game game) { - Permanent permanent = input.getSource().getSourcePermanentIfItStillExists(game); - return permanent != null && permanent.getAttachments().contains(input.getObject().getId()); - } - } - - private static final FilterPermanent filter - = new FilterEquipmentPermanent("Equipment attached to this creature"); - - static { - filter.add(ShacklesOfTreacheryPredicate.instance); - } - - ShacklesOfTreacheryTriggeredAbility() { - super(Zone.BATTLEFIELD, new DestroyTargetEffect(), false); - this.addTarget(new TargetPermanent(filter)); - } - - private ShacklesOfTreacheryTriggeredAbility(final ShacklesOfTreacheryTriggeredAbility ability) { - super(ability); - } +enum ShacklesOfTreacheryPredicate implements ObjectSourcePlayerPredicate { + instance; @Override - public ShacklesOfTreacheryTriggeredAbility copy() { - return new ShacklesOfTreacheryTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT - || event.getType() == GameEvent.EventType.DAMAGED_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return event.getSourceId().equals(this.getSourceId()); - } - - @Override - public String getRule() { - return "Whenever this creature deals damage, destroy target Equipment attached to it."; + public boolean apply(ObjectSourcePlayer input, Game game) { + Permanent permanent = input.getSource().getSourcePermanentIfItStillExists(game); + return permanent != null && permanent.getAttachments().contains(input.getObject().getId()); } } diff --git a/Mage.Sets/src/mage/cards/s/ShriekwoodDevourer.java b/Mage.Sets/src/mage/cards/s/ShriekwoodDevourer.java new file mode 100644 index 00000000000..984de495022 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShriekwoodDevourer.java @@ -0,0 +1,84 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.UntapLandsEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; + +import java.util.Objects; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ShriekwoodDevourer extends CardImpl { + + public ShriekwoodDevourer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{G}{G}"); + + this.subtype.add(SubType.TREEFOLK); + this.power = new MageInt(7); + this.toughness = new MageInt(5); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever you attack with one or more creatures, untap up to X lands, where X is the greatest power among those creatures. + this.addAbility(new AttacksWithCreaturesTriggeredAbility( + Zone.BATTLEFIELD, new ShriekwoodDevourerEffect(), + 1, StaticFilters.FILTER_PERMANENT_CREATURES, true + ).setTriggerPhrase("Whenever you attack with one or more creatures, ")); + } + + private ShriekwoodDevourer(final ShriekwoodDevourer card) { + super(card); + } + + @Override + public ShriekwoodDevourer copy() { + return new ShriekwoodDevourer(this); + } +} + +class ShriekwoodDevourerEffect extends OneShotEffect { + + ShriekwoodDevourerEffect() { + super(Outcome.Benefit); + staticText = "untap up to X lands, where X is the greatest power among those creatures"; + } + + private ShriekwoodDevourerEffect(final ShriekwoodDevourerEffect effect) { + super(effect); + } + + @Override + public ShriekwoodDevourerEffect copy() { + return new ShriekwoodDevourerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + int xValue = this + .getTargetPointer() + .getTargets(game, source) + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .map(MageObject::getPower) + .mapToInt(MageInt::getValue) + .max() + .orElse(0); + return xValue > 0 && new UntapLandsEffect(xValue).apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SierraNukasBiggestFan.java b/Mage.Sets/src/mage/cards/s/SierraNukasBiggestFan.java index 9f90ebb7947..ab99945a993 100644 --- a/Mage.Sets/src/mage/cards/s/SierraNukasBiggestFan.java +++ b/Mage.Sets/src/mage/cards/s/SierraNukasBiggestFan.java @@ -4,7 +4,7 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.SacrificePermanentTriggeredAbility; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.CountersSourceCount; @@ -39,7 +39,7 @@ public final class SierraNukasBiggestFan extends CardImpl { // The Nuka-Cola Challenge -- Whenever one or more creatures you control deal combat damage to a player, // put a quest counter on Sierra, Nuka's Biggest Fan and create a Food token. - Ability ability = new DealCombatDamageControlledTriggeredAbility(new AddCountersSourceEffect(CounterType.QUEST.createInstance())); + Ability ability = new OneOrMoreCombatDamagePlayerTriggeredAbility(new AddCountersSourceEffect(CounterType.QUEST.createInstance())); ability.addEffect(new CreateTokenEffect(new FoodToken()).concatBy("and")); this.addAbility(ability.withFlavorWord("The Nuka-Cola Challenge")); diff --git a/Mage.Sets/src/mage/cards/s/SoulLink.java b/Mage.Sets/src/mage/cards/s/SoulLink.java index 818a96d3b15..f24bdb5d878 100644 --- a/Mage.Sets/src/mage/cards/s/SoulLink.java +++ b/Mage.Sets/src/mage/cards/s/SoulLink.java @@ -36,7 +36,7 @@ public final class SoulLink extends CardImpl { this.addAbility(new DealsDamageAttachedTriggeredAbility(Zone.BATTLEFIELD, new GainLifeEffect(SavedDamageValue.MUCH), false)); // Whenever enchanted creature is dealt damage, you gain that much life. - this.addAbility(new DealtDamageAttachedTriggeredAbility(new GainLifeEffect(SavedDamageValue.MUCH), false)); + this.addAbility(new DealtDamageAttachedTriggeredAbility(new GainLifeEffect(SavedDamageValue.MUCH))); } private SoulLink(final SoulLink card) { diff --git a/Mage.Sets/src/mage/cards/s/SoulsOfTheFaultless.java b/Mage.Sets/src/mage/cards/s/SoulsOfTheFaultless.java index dd1528d9297..a07506dde73 100644 --- a/Mage.Sets/src/mage/cards/s/SoulsOfTheFaultless.java +++ b/Mage.Sets/src/mage/cards/s/SoulsOfTheFaultless.java @@ -1,32 +1,29 @@ - package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.Effect; +import mage.abilities.common.DealtCombatDamageToSourceTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.keyword.DefenderAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; -import mage.constants.Zone; +import mage.constants.SubType; import mage.game.Game; -import mage.game.events.DamagedBatchForOnePermanentEvent; -import mage.game.events.GameEvent; import mage.players.Player; +import java.util.UUID; + /** - * * @author North */ public final class SoulsOfTheFaultless extends CardImpl { public SoulsOfTheFaultless(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{W}{B}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{B}{B}"); this.subtype.add(SubType.SPIRIT); this.power = new MageInt(0); @@ -34,8 +31,13 @@ public final class SoulsOfTheFaultless extends CardImpl { // Defender this.addAbility(DefenderAbility.getInstance()); + // Whenever Souls of the Faultless is dealt combat damage, you gain that much life and attacking player loses that much life. - this.addAbility(new SoulsOfTheFaultlessTriggeredAbility()); + Ability ability = new DealtCombatDamageToSourceTriggeredAbility( + new GainLifeEffect(SavedDamageValue.MUCH), false + ); + ability.addEffect(new SoulsOfTheFaultlessEffect()); + this.addAbility(ability); } private SoulsOfTheFaultless(final SoulsOfTheFaultless card) { @@ -48,50 +50,11 @@ public final class SoulsOfTheFaultless extends CardImpl { } } -class SoulsOfTheFaultlessTriggeredAbility extends TriggeredAbilityImpl { - - public SoulsOfTheFaultlessTriggeredAbility() { - super(Zone.BATTLEFIELD, new SoulsOfTheFaultlessEffect()); - setTriggerPhrase("Whenever {this} is dealt combat damage, "); - } - - private SoulsOfTheFaultlessTriggeredAbility(final SoulsOfTheFaultlessTriggeredAbility effect) { - super(effect); - } - - @Override - public SoulsOfTheFaultlessTriggeredAbility copy() { - return new SoulsOfTheFaultlessTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchForOnePermanentEvent dEvent = (DamagedBatchForOnePermanentEvent) event; - - int damage = dEvent.getAmount(); - - if (dEvent.getTargetId().equals(this.sourceId) && dEvent.isCombatDamage() && damage > 0) { - UUID attackerId = game.getActivePlayerId(); - for (Effect effect : this.getEffects()) { - effect.setValue("damageAmount", damage); - effect.setValue("attackerId", attackerId); - } - return true; - } - return false; - } -} - class SoulsOfTheFaultlessEffect extends OneShotEffect { SoulsOfTheFaultlessEffect() { - super(Outcome.GainLife); - staticText = "you gain that much life and attacking player loses that much life"; + super(Outcome.LoseLife); + staticText = "and attacking player loses that much life"; } private SoulsOfTheFaultlessEffect(final SoulsOfTheFaultlessEffect effect) { @@ -105,15 +68,8 @@ class SoulsOfTheFaultlessEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Integer amount = (Integer) this.getValue("damageAmount"); - - Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - player.gainLife(amount, game, source); - } - - UUID attackerId = (UUID) this.getValue("attackerId"); - Player attacker = game.getPlayer(attackerId); + int amount = SavedDamageValue.MUCH.calculate(game, source, this); + Player attacker = game.getPlayer(game.getActivePlayerId()); if (attacker != null) { attacker.loseLife(amount, game, source, false); } diff --git a/Mage.Sets/src/mage/cards/s/SowerOfDiscord.java b/Mage.Sets/src/mage/cards/s/SowerOfDiscord.java index 308c0337f12..190a4a178c3 100644 --- a/Mage.Sets/src/mage/cards/s/SowerOfDiscord.java +++ b/Mage.Sets/src/mage/cards/s/SowerOfDiscord.java @@ -1,8 +1,8 @@ package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.AsEntersBattlefieldAbility; import mage.abilities.effects.Effect; @@ -16,12 +16,15 @@ import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; +import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPlayer; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** * * @author TheElk801 @@ -107,9 +110,9 @@ class SowerOfDiscordEntersBattlefieldEffect extends OneShotEffect { } -class SowerOfDiscordTriggeredAbility extends TriggeredAbilityImpl { +class SowerOfDiscordTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { - public SowerOfDiscordTriggeredAbility() { + SowerOfDiscordTriggeredAbility() { super(Zone.BATTLEFIELD, null); } diff --git a/Mage.Sets/src/mage/cards/s/StirThePride.java b/Mage.Sets/src/mage/cards/s/StirThePride.java index a3f2a99f594..abd34291eeb 100644 --- a/Mage.Sets/src/mage/cards/s/StirThePride.java +++ b/Mage.Sets/src/mage/cards/s/StirThePride.java @@ -1,10 +1,10 @@ - package mage.cards.s; -import java.util.UUID; import mage.abilities.Mode; -import mage.abilities.common.DealsDamageGainLifeSourceTriggeredAbility; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.Effect; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.continuous.BoostControlledEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.keyword.EntwineAbility; @@ -14,23 +14,23 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.filter.StaticFilters; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class StirThePride extends CardImpl { public StirThePride(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{4}{W}"); - + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{W}"); // Choose one - this.getSpellAbility().getModes().setMinModes(1); this.getSpellAbility().getModes().setMaxModes(1); // Creatures you control get +2/+2 until end of turn; - this.getSpellAbility().addEffect(new BoostControlledEffect(2,2, Duration.EndOfTurn)); + this.getSpellAbility().addEffect(new BoostControlledEffect(2, 2, Duration.EndOfTurn)); // or until end of turn, creatures you control gain "Whenever this creature deals damage, you gain that much life." - Effect effect = new GainAbilityControlledEffect(new DealsDamageGainLifeSourceTriggeredAbility(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURES); + Effect effect = new GainAbilityControlledEffect(new DealsDamageSourceTriggeredAbility(new GainLifeEffect(SavedDamageValue.MUCH)), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURES); effect.setText("until end of turn, creatures you control gain \"Whenever this creature deals damage, you gain that much life.\""); Mode mode = new Mode(effect); this.getSpellAbility().getModes().addMode(mode); diff --git a/Mage.Sets/src/mage/cards/s/StormTheVault.java b/Mage.Sets/src/mage/cards/s/StormTheVault.java index c4ebbe9715b..11d924c3997 100644 --- a/Mage.Sets/src/mage/cards/s/StormTheVault.java +++ b/Mage.Sets/src/mage/cards/s/StormTheVault.java @@ -4,7 +4,7 @@ package mage.cards.s; import java.util.UUID; import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; @@ -33,7 +33,7 @@ public final class StormTheVault extends CardImpl { this.secondSideCardClazz = mage.cards.v.VaultOfCatlacan.class; // Whenever one or more creatures you control deal combat damage to a player, create a colorless Treasure artifact token with "{T}, Sacrifice this artifact: Add one mana of any color." - this.addAbility(new DealCombatDamageControlledTriggeredAbility(new CreateTokenEffect(new TreasureToken()))); + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility(new CreateTokenEffect(new TreasureToken()))); // At the beginning of your end step, if you control five or more artifacts, transform Storm the Vault. this.addAbility(new TransformAbility()); diff --git a/Mage.Sets/src/mage/cards/s/StrongholdArena.java b/Mage.Sets/src/mage/cards/s/StrongholdArena.java index b94499cf00b..eea2d3f0027 100644 --- a/Mage.Sets/src/mage/cards/s/StrongholdArena.java +++ b/Mage.Sets/src/mage/cards/s/StrongholdArena.java @@ -1,6 +1,6 @@ package mage.cards.s; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.MultipliedValue; @@ -39,7 +39,7 @@ public final class StrongholdArena extends CardImpl { // Whenever one or more creatures you control deal combat damage to a player, you may reveal the top card of your library and put it into your hand. // If you do, you lose life equal to its mana value. - this.addAbility(new DealCombatDamageControlledTriggeredAbility( + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility( Zone.BATTLEFIELD, new RevealPutInHandLoseLifeEffect(true), StaticFilters.FILTER_PERMANENT_CREATURES, SetTargetPointer.NONE, true diff --git a/Mage.Sets/src/mage/cards/s/SunDroplet.java b/Mage.Sets/src/mage/cards/s/SunDroplet.java index 6cbe9b48cba..5d608eb1734 100644 --- a/Mage.Sets/src/mage/cards/s/SunDroplet.java +++ b/Mage.Sets/src/mage/cards/s/SunDroplet.java @@ -1,20 +1,17 @@ - package mage.cards.s; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.YoureDealtDamageTriggeredAbility; import mage.abilities.costs.common.RemoveCountersSourceCost; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.TargetController; -import mage.constants.Zone; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.events.GameEvent; import java.util.UUID; @@ -27,7 +24,8 @@ public final class SunDroplet extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); // Whenever you're dealt damage, put that many charge counters on Sun Droplet. - this.addAbility(new SunDropletTriggeredAbility()); + this.addAbility(new YoureDealtDamageTriggeredAbility(new AddCountersSourceEffect( + CounterType.CHARGE.createInstance(), SavedDamageValue.MANY), false)); // At the beginning of each upkeep, you may remove a charge counter from Sun Droplet. If you do, you gain 1 life. this.addAbility(new BeginningOfUpkeepTriggeredAbility( @@ -46,41 +44,3 @@ public final class SunDroplet extends CardImpl { return new SunDroplet(this); } } - -class SunDropletTriggeredAbility extends TriggeredAbilityImpl { - - SunDropletTriggeredAbility() { - super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.CHARGE.createInstance()), false); - } - - private SunDropletTriggeredAbility(final SunDropletTriggeredAbility ability) { - super(ability); - } - - @Override - public SunDropletTriggeredAbility copy() { - return new SunDropletTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(this.getControllerId())) { - this.getEffects().clear(); - if (event.getAmount() > 0) { - this.addEffect(new AddCountersSourceEffect(CounterType.CHARGE.createInstance(event.getAmount()))); - } - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever you're dealt damage, put that many charge counters on {this}."; - } -} diff --git a/Mage.Sets/src/mage/cards/s/SwarmbornGiant.java b/Mage.Sets/src/mage/cards/s/SwarmbornGiant.java index d8de4d067cb..1b52f485be0 100644 --- a/Mage.Sets/src/mage/cards/s/SwarmbornGiant.java +++ b/Mage.Sets/src/mage/cards/s/SwarmbornGiant.java @@ -1,8 +1,8 @@ - package mage.cards.s; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.MonstrousCondition; @@ -19,6 +19,7 @@ import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; import mage.game.events.DamagedBatchForOnePlayerEvent; +import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import java.util.UUID; @@ -60,9 +61,9 @@ public final class SwarmbornGiant extends CardImpl { } } -class SwarmbornGiantTriggeredAbility extends TriggeredAbilityImpl { +class SwarmbornGiantTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { - public SwarmbornGiantTriggeredAbility() { + SwarmbornGiantTriggeredAbility() { super(Zone.BATTLEFIELD, new SacrificeSourceEffect(), false); setTriggerPhrase("When you're dealt combat damage, "); } @@ -83,9 +84,10 @@ class SwarmbornGiantTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchForOnePlayerEvent dEvent = (DamagedBatchForOnePlayerEvent) event; - if (dEvent.getTargetId().equals(this.getControllerId())) { - return dEvent.isCombatDamage() && dEvent.getAmount() > 0; + // all events in the batch are always relevant + if (isControlledBy(event.getTargetId()) && ((DamagedBatchForOnePlayerEvent) event).isCombatDamage()) { + this.getAllEffects().setValue("damage", event.getAmount()); + return true; } return false; } diff --git a/Mage.Sets/src/mage/cards/t/TadeasJuniperAscendant.java b/Mage.Sets/src/mage/cards/t/TadeasJuniperAscendant.java index c0176115730..818d9cd424b 100644 --- a/Mage.Sets/src/mage/cards/t/TadeasJuniperAscendant.java +++ b/Mage.Sets/src/mage/cards/t/TadeasJuniperAscendant.java @@ -3,7 +3,7 @@ package mage.cards.t; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.AttacksCreatureYouControlTriggeredAbility; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.Condition; import mage.abilities.condition.InvertCondition; @@ -63,7 +63,7 @@ public final class TadeasJuniperAscendant extends CardImpl { )); // Whenever one or more creatures you control deal combat damage to a player, draw a card. - this.addAbility(new DealCombatDamageControlledTriggeredAbility( + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility( new DrawCardSourceControllerEffect(1) )); } diff --git a/Mage.Sets/src/mage/cards/t/Tamanoa.java b/Mage.Sets/src/mage/cards/t/Tamanoa.java index 6ec8211ffbd..1ff25182e77 100644 --- a/Mage.Sets/src/mage/cards/t/Tamanoa.java +++ b/Mage.Sets/src/mage/cards/t/Tamanoa.java @@ -1,12 +1,11 @@ package mage.cards.t; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.dynamicvalue.common.SavedDamageValue; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -14,10 +13,12 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; +import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class Tamanoa extends CardImpl { @@ -30,7 +31,7 @@ public final class Tamanoa extends CardImpl { this.toughness = new MageInt(4); // Whenever a noncreature source you control deals damage, you gain that much life. - Ability ability = new TamanoaDealsDamageTriggeredAbility(Zone.BATTLEFIELD, new GainLifeEffect(SavedDamageValue.MUCH), false); + Ability ability = new TamanoaDealsDamageTriggeredAbility(); this.addAbility(ability); } @@ -45,10 +46,10 @@ public final class Tamanoa extends CardImpl { } } -class TamanoaDealsDamageTriggeredAbility extends TriggeredAbilityImpl { +class TamanoaDealsDamageTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { - public TamanoaDealsDamageTriggeredAbility(Zone zone, Effect effect, boolean optional) { - super(zone, effect, optional); + TamanoaDealsDamageTriggeredAbility() { + super(Zone.BATTLEFIELD, new GainLifeEffect(SavedDamageValue.MUCH), false); setTriggerPhrase("Whenever a noncreature source you control deals damage, "); } @@ -63,21 +64,17 @@ class TamanoaDealsDamageTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT - || event.getType() == GameEvent.EventType.DAMAGED_PLAYER; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_BY_SOURCE; } @Override public boolean checkTrigger(GameEvent event, Game game) { MageObject eventSourceObject = game.getObject(event.getSourceId()); - if (eventSourceObject != null && !eventSourceObject.isCreature(game)) { - if (isControlledBy(game.getControllerId(event.getSourceId()))) { - this.getEffects().forEach((effect) -> { - effect.setValue("damage", event.getAmount()); - }); - return true; - } + if (eventSourceObject == null || eventSourceObject.isCreature(game) + || !isControlledBy(game.getControllerId(event.getSourceId()))) { + return false; } - return false; + getEffects().setValue("damage", event.getAmount()); + return true; } } diff --git a/Mage.Sets/src/mage/cards/t/TamiyoFieldResearcher.java b/Mage.Sets/src/mage/cards/t/TamiyoFieldResearcher.java index 44019344521..d938346820c 100644 --- a/Mage.Sets/src/mage/cards/t/TamiyoFieldResearcher.java +++ b/Mage.Sets/src/mage/cards/t/TamiyoFieldResearcher.java @@ -1,8 +1,8 @@ - package mage.cards.t; import mage.MageObjectReference; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.LoyaltyAbility; import mage.abilities.effects.OneShotEffect; @@ -18,6 +18,7 @@ import mage.filter.StaticFilters; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.command.emblems.TamiyoFieldResearcherEmblem; +import mage.game.events.DamagedBatchBySourceEvent; import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -99,7 +100,7 @@ class TamiyoFieldResearcherEffect1 extends OneShotEffect { creatures.add(new MageObjectReference(uuid, game)); } if (!creatures.isEmpty()) { - DelayedTriggeredAbility delayedAbility = new TamiyoFieldResearcherDelayedTriggeredAbility(creatures); + DelayedTriggeredAbility delayedAbility = new TamiyoFieldResearcherDelayedTriggeredAbility(creatures, game.getTurnNum()); game.addDelayedTriggeredAbility(delayedAbility, source); } return true; @@ -108,34 +109,52 @@ class TamiyoFieldResearcherEffect1 extends OneShotEffect { } } -class TamiyoFieldResearcherDelayedTriggeredAbility extends DelayedTriggeredAbility { +// batch per source: +// > If Tamiyo’s first ability targets two creatures, and both deal combat damage at the same time, the delayed triggered ability triggers twice. +// > (2016-08-23) +class TamiyoFieldResearcherDelayedTriggeredAbility extends DelayedTriggeredAbility implements BatchTriggeredAbility { - private List creatures; + private final int startingTurn; + private final List creatures; - public TamiyoFieldResearcherDelayedTriggeredAbility(List creatures) { - super(new DrawCardSourceControllerEffect(1), Duration.UntilYourNextTurn, false); + TamiyoFieldResearcherDelayedTriggeredAbility(List creatures, int startingTurn) { + super(new DrawCardSourceControllerEffect(1, true), Duration.Custom, false); this.creatures = creatures; + this.startingTurn = startingTurn; + setTriggerPhrase("Until your next turn, whenever either of those creatures deals combat damage, "); } private TamiyoFieldResearcherDelayedTriggeredAbility(final TamiyoFieldResearcherDelayedTriggeredAbility ability) { super(ability); this.creatures = ability.creatures; + this.startingTurn = ability.startingTurn; } @Override public boolean checkEventType(GameEvent event, Game game) { - return event instanceof DamagedEvent; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_BY_SOURCE; + } + + @Override + public boolean checkEvent(DamagedEvent event, Game game) { + if (!event.isCombatDamage() || event.getAmount() <= 0) { + return false; + } + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); + if (permanent == null) { + return false; + } + return creatures.contains(new MageObjectReference(permanent.getId(), game)); } @Override public boolean checkTrigger(GameEvent event, Game game) { - if (((DamagedEvent) event).isCombatDamage()) { - Permanent damageSource = game.getPermanent(event.getSourceId()); - if (damageSource != null) { - return creatures.contains(new MageObjectReference(damageSource, game)); - } - } - return false; + return !getFilteredEvents((DamagedBatchBySourceEvent) event, game).isEmpty(); + } + + @Override + public boolean isInactive(Game game) { + return game.isActivePlayer(getControllerId()) && game.getTurnNum() != startingTurn; } @Override @@ -143,8 +162,4 @@ class TamiyoFieldResearcherDelayedTriggeredAbility extends DelayedTriggeredAbili return new TamiyoFieldResearcherDelayedTriggeredAbility(this); } - @Override - public String getRule() { - return "Until your next turn, whenever either of those creatures deals combat damage, you draw a card."; - } } diff --git a/Mage.Sets/src/mage/cards/t/TerminationFacilitator.java b/Mage.Sets/src/mage/cards/t/TerminationFacilitator.java index c994b78cc63..cd68e687fa9 100644 --- a/Mage.Sets/src/mage/cards/t/TerminationFacilitator.java +++ b/Mage.Sets/src/mage/cards/t/TerminationFacilitator.java @@ -2,22 +2,21 @@ package mage.cards.t; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.DealtDamageAnyTriggeredAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SetTargetPointer; import mage.constants.SubType; -import mage.constants.Zone; +import mage.constants.TargetController; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent; import mage.target.common.TargetCreatureOrPlaneswalker; -import mage.target.targetpointer.FixedTarget; import java.util.UUID; @@ -26,6 +25,13 @@ import java.util.UUID; */ public final class TerminationFacilitator extends CardImpl { + private static final FilterPermanent filter = new FilterCreatureOrPlaneswalkerPermanent("a creature or planeswalker an opponent controls with a bounty counter on it"); + + static { + filter.add(TargetController.OPPONENT.getControllerPredicate()); + filter.add(CounterType.BOUNTY.getPredicate()); + } + public TerminationFacilitator(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); @@ -42,7 +48,8 @@ public final class TerminationFacilitator extends CardImpl { this.addAbility(ability); // Whenever a creature or planeswalker an opponent controls with a bounty counter on it is dealt damage, destroy it. - this.addAbility(new TerminationFacilitatorTriggeredAbility()); + this.addAbility(new DealtDamageAnyTriggeredAbility(new DestroyTargetEffect().setText("destroy it"), + filter, SetTargetPointer.PERMANENT, false)); } private TerminationFacilitator(final TerminationFacilitator card) { @@ -54,42 +61,3 @@ public final class TerminationFacilitator extends CardImpl { return new TerminationFacilitator(this); } } - -class TerminationFacilitatorTriggeredAbility extends TriggeredAbilityImpl { - - TerminationFacilitatorTriggeredAbility() { - super(Zone.BATTLEFIELD, new DestroyTargetEffect()); - } - - private TerminationFacilitatorTriggeredAbility(final TerminationFacilitatorTriggeredAbility ability) { - super(ability); - } - - @Override - public TerminationFacilitatorTriggeredAbility copy() { - return new TerminationFacilitatorTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - Permanent permanent = game.getPermanent(event.getTargetId()); - if (permanent != null - && permanent.getCounters(game).containsKey(CounterType.BOUNTY) - && game.getOpponents(getControllerId()).contains(permanent.getControllerId()) - && (permanent.isCreature(game) || permanent.isPlaneswalker(game))) { - this.getEffects().setTargetPointer(new FixedTarget(permanent, game)); - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever a creature or planeswalker an opponent controls with a bounty counter on it is dealt damage, destroy it."; - } -} diff --git a/Mage.Sets/src/mage/cards/t/TheMillenniumCalendar.java b/Mage.Sets/src/mage/cards/t/TheMillenniumCalendar.java index b5b66b14eab..68d618521dd 100644 --- a/Mage.Sets/src/mage/cards/t/TheMillenniumCalendar.java +++ b/Mage.Sets/src/mage/cards/t/TheMillenniumCalendar.java @@ -1,6 +1,7 @@ package mage.cards.t; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.StateTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleActivatedAbility; @@ -23,7 +24,6 @@ import mage.game.events.UntappedBatchEvent; import mage.game.events.UntappedEvent; import mage.game.permanent.Permanent; -import java.util.Objects; import java.util.UUID; /** @@ -61,13 +61,13 @@ public final class TheMillenniumCalendar extends CardImpl { } } -class TheMillenniumCalendarTriggeredAbility extends TriggeredAbilityImpl { +class TheMillenniumCalendarTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { - public TheMillenniumCalendarTriggeredAbility() { + TheMillenniumCalendarTriggeredAbility() { super(Zone.BATTLEFIELD, null, false); } - protected TheMillenniumCalendarTriggeredAbility(final TheMillenniumCalendarTriggeredAbility ability) { + private TheMillenniumCalendarTriggeredAbility(final TheMillenniumCalendarTriggeredAbility ability) { super(ability); } @@ -81,27 +81,24 @@ class TheMillenniumCalendarTriggeredAbility extends TriggeredAbilityImpl { return event.getType() == GameEvent.EventType.UNTAPPED_BATCH; } + @Override + public boolean checkEvent(UntappedEvent event, Game game) { + if (!event.isAnUntapStepEvent()) { + return false; + } + Permanent permanent = game.getPermanent(event.getTargetId()); + return permanent != null && isControlledBy(permanent.getControllerId()); + } + @Override public boolean checkTrigger(GameEvent event, Game game) { if (!game.isActivePlayer(getControllerId())) { return false; } - UntappedBatchEvent batchEvent = (UntappedBatchEvent) event; - int count = batchEvent - .getEvents() - .stream() - .filter(UntappedEvent::isAnUntapStepEvent) - .map(UntappedEvent::getTargetId) - .map(game::getPermanent) - .filter(Objects::nonNull) - .filter(p -> p.getControllerId().equals(getControllerId())) - .mapToInt(p -> 1) - .sum(); - - if (count <= 0) { + int count = getFilteredEvents((UntappedBatchEvent) event, game).size(); + if (count == 0) { return false; } - this.getEffects().clear(); this.addEffect(new AddCountersSourceEffect(CounterType.TIME.createInstance(count))); this.getHints().clear(); @@ -121,7 +118,7 @@ class TheMillenniumCalendarTriggeredAbility extends TriggeredAbilityImpl { */ class TheMillenniumCalendarStateTriggeredAbility extends StateTriggeredAbility { - public TheMillenniumCalendarStateTriggeredAbility() { + TheMillenniumCalendarStateTriggeredAbility() { super(Zone.BATTLEFIELD, new SacrificeSourceEffect()); withRuleTextReplacement(true); addEffect(new LoseLifeOpponentsEffect(1000).setText("and each opponent loses 1,000 life")); diff --git a/Mage.Sets/src/mage/cards/t/TheOddAcornGang.java b/Mage.Sets/src/mage/cards/t/TheOddAcornGang.java index df9e0f8b24d..38a12939701 100644 --- a/Mage.Sets/src/mage/cards/t/TheOddAcornGang.java +++ b/Mage.Sets/src/mage/cards/t/TheOddAcornGang.java @@ -4,7 +4,7 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.ActivateAsSorceryActivatedAbility; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.DrawCardSourceControllerEffect; @@ -64,7 +64,7 @@ public final class TheOddAcornGang extends CardImpl { ))); // Whenever one or more Squirrels you control deal combat damage to a player, draw a card. - this.addAbility(new DealCombatDamageControlledTriggeredAbility(Zone.BATTLEFIELD, + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), filter, SetTargetPointer.NONE, false)); } diff --git a/Mage.Sets/src/mage/cards/t/TheRavensWarning.java b/Mage.Sets/src/mage/cards/t/TheRavensWarning.java index 9724fac8108..c25f3cbd10e 100644 --- a/Mage.Sets/src/mage/cards/t/TheRavensWarning.java +++ b/Mage.Sets/src/mage/cards/t/TheRavensWarning.java @@ -2,6 +2,7 @@ package mage.cards.t; import java.util.UUID; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.common.SagaAbility; import mage.abilities.effects.common.*; @@ -15,6 +16,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.game.Game; import mage.game.events.DamagedBatchForOnePlayerEvent; +import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.permanent.token.BlueBirdToken; @@ -63,7 +65,7 @@ public final class TheRavensWarning extends CardImpl { } } -class TheRavensWarningTriggeredAbility extends DelayedTriggeredAbility { +class TheRavensWarningTriggeredAbility extends DelayedTriggeredAbility implements BatchTriggeredAbility { TheRavensWarningTriggeredAbility() { super(new LookAtTargetPlayerHandEffect(), Duration.EndOfTurn, false); @@ -85,23 +87,24 @@ class TheRavensWarningTriggeredAbility extends DelayedTriggeredAbility { } @Override - public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchForOnePlayerEvent dEvent = (DamagedBatchForOnePlayerEvent) event; - if (!dEvent.isCombatDamage()) { + public boolean checkEvent(DamagedPlayerEvent event, Game game) { + if (!event.isCombatDamage()) { return false; } - if (dEvent.getEvents().stream() - .anyMatch(e -> { - Permanent permanent = game.getPermanentOrLKIBattlefield(e.getSourceId()); - return permanent != null - && permanent.isCreature(game) - && permanent.isControlledBy(this.getControllerId()) - && permanent.hasAbility(FlyingAbility.getInstance(), game); - })) { - getEffects().setTargetPointer(new FixedTarget(dEvent.getTargetId())); - return true; + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); + return permanent != null + && permanent.isCreature(game) + && permanent.isControlledBy(getControllerId()) + && permanent.hasAbility(FlyingAbility.getInstance(), game); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (getFilteredEvents((DamagedBatchForOnePlayerEvent) event, game).isEmpty()) { + return false; } - return false; + getEffects().setTargetPointer(new FixedTarget(event.getTargetId())); + return true; } @Override diff --git a/Mage.Sets/src/mage/cards/t/ThopterSpyNetwork.java b/Mage.Sets/src/mage/cards/t/ThopterSpyNetwork.java index 7f75c371c35..58931c00f9e 100644 --- a/Mage.Sets/src/mage/cards/t/ThopterSpyNetwork.java +++ b/Mage.Sets/src/mage/cards/t/ThopterSpyNetwork.java @@ -2,7 +2,7 @@ package mage.cards.t; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.cards.CardImpl; @@ -32,7 +32,7 @@ public final class ThopterSpyNetwork extends CardImpl { this.addAbility(new ThopterSpyNetworkUpkeepTriggeredAbility()); // Whenever one or more artifact creatures you control deals combat damage to a player, draw a card. - this.addAbility(new DealCombatDamageControlledTriggeredAbility(new DrawCardSourceControllerEffect(1), filter)); + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility(new DrawCardSourceControllerEffect(1), filter)); } private ThopterSpyNetwork(final ThopterSpyNetwork card) { @@ -79,4 +79,4 @@ class ThopterSpyNetworkUpkeepTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "At the beginning of your upkeep, if you control an artifact, create a 1/1 colorless Thopter artifact creature token with flying."; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/t/ThunderbladeCharge.java b/Mage.Sets/src/mage/cards/t/ThunderbladeCharge.java index 3a803f8ca51..3c3dd26426d 100644 --- a/Mage.Sets/src/mage/cards/t/ThunderbladeCharge.java +++ b/Mage.Sets/src/mage/cards/t/ThunderbladeCharge.java @@ -2,7 +2,7 @@ package mage.cards.t; import mage.ApprovingObject; import mage.abilities.Ability; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.condition.common.SourceInGraveyardCondition; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; @@ -37,7 +37,7 @@ public final class ThunderbladeCharge extends CardImpl { // Whenever one or more creatures you control deal combat damage to a player, // if Thunderblade Charge is in your graveyard, you may pay {2}{R}{R}{R}. // If you do, you may cast it without paying its mana cost. - this.addAbility(new DealCombatDamageControlledTriggeredAbility(Zone.GRAVEYARD, + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility(Zone.GRAVEYARD, new DoIfCostPaid(new ThunderbladeChargeCastEffect(), new ManaCostsImpl<>("{2}{R}{R}{R}")), StaticFilters.FILTER_PERMANENT_CREATURES, SetTargetPointer.NONE, false ).withInterveningIf(SourceInGraveyardCondition.instance)); diff --git a/Mage.Sets/src/mage/cards/t/ToralfGodOfFury.java b/Mage.Sets/src/mage/cards/t/ToralfGodOfFury.java index 2af8737fb86..74aee9a3904 100644 --- a/Mage.Sets/src/mage/cards/t/ToralfGodOfFury.java +++ b/Mage.Sets/src/mage/cards/t/ToralfGodOfFury.java @@ -2,6 +2,7 @@ package mage.cards.t; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.Mode; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; @@ -28,6 +29,7 @@ import mage.filter.predicate.mageobject.MageObjectReferencePredicate; import mage.game.Game; import mage.game.events.DamagedBatchForOnePermanentEvent; import mage.game.events.DamagedEvent; +import mage.game.events.DamagedPermanentEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; @@ -92,7 +94,7 @@ public final class ToralfGodOfFury extends ModalDoubleFacedCard { } } -class ToralfGodOfFuryTriggeredAbility extends TriggeredAbilityImpl { +class ToralfGodOfFuryTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { ToralfGodOfFuryTriggeredAbility() { super(Zone.BATTLEFIELD, null); @@ -109,6 +111,7 @@ class ToralfGodOfFuryTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { + // all events in the batch are always relevant if triggers at all DamagedBatchForOnePermanentEvent dEvent = (DamagedBatchForOnePermanentEvent) event; int excessDamage = dEvent.getEvents() .stream() diff --git a/Mage.Sets/src/mage/cards/t/ToweringWaveMystic.java b/Mage.Sets/src/mage/cards/t/ToweringWaveMystic.java index 50dc1ee42b4..314249fcce0 100644 --- a/Mage.Sets/src/mage/cards/t/ToweringWaveMystic.java +++ b/Mage.Sets/src/mage/cards/t/ToweringWaveMystic.java @@ -1,15 +1,14 @@ package mage.cards.t; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.Ability; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.common.MillCardsTargetEffect; import mage.cards.CardImpl; 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.target.TargetPlayer; import java.util.UUID; @@ -27,8 +26,10 @@ public final class ToweringWaveMystic extends CardImpl { this.power = new MageInt(2); this.toughness = new MageInt(1); - // Whenever Towering-Wave Mystic deals damage, target player puts that many cards from the top of their library into their graveyard. - this.addAbility(new ToweringWaveMysticTriggeredAbility()); + // Whenever Towering-Wave Mystic deals damage, target player mills that many cards. + Ability ability = new DealsDamageSourceTriggeredAbility(new MillCardsTargetEffect(SavedDamageValue.MANY)); + ability.addTarget(new TargetPlayer()); + this.addAbility(ability); } private ToweringWaveMystic(final ToweringWaveMystic card) { @@ -40,41 +41,3 @@ public final class ToweringWaveMystic extends CardImpl { return new ToweringWaveMystic(this); } } - -class ToweringWaveMysticTriggeredAbility extends TriggeredAbilityImpl { - - ToweringWaveMysticTriggeredAbility() { - super(Zone.BATTLEFIELD, null, false); - this.addTarget(new TargetPlayer()); - } - - private ToweringWaveMysticTriggeredAbility(final ToweringWaveMysticTriggeredAbility ability) { - super(ability); - } - - @Override - public ToweringWaveMysticTriggeredAbility copy() { - return new ToweringWaveMysticTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT - || event.getType() == GameEvent.EventType.DAMAGED_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (!event.getSourceId().equals(this.getSourceId())) { - return false; - } - this.getEffects().clear(); - this.addEffect(new MillCardsTargetEffect(event.getAmount())); - return true; - } - - @Override - public String getRule() { - return "Whenever {this} deals damage, target player mills that many cards."; - } -} diff --git a/Mage.Sets/src/mage/cards/t/Transcendence.java b/Mage.Sets/src/mage/cards/t/Transcendence.java index 5bd4fba4146..c45f3e7d82c 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 final class Transcendence extends CardImpl { 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 @@ class TranscendenceStateTriggeredAbility extends StateTriggeredAbility { 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 00000000000..14c24744ed7 --- /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 8da71789ddb..582fd6b96a2 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.CardImpl; 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 final class VampireScrivener extends CardImpl { } } -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 @@ class VampireScrivenerTriggeredAbility extends TriggeredAbilityImpl { 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/VengefulPharaoh.java b/Mage.Sets/src/mage/cards/v/VengefulPharaoh.java index 1cd0062751a..d241d75645a 100644 --- a/Mage.Sets/src/mage/cards/v/VengefulPharaoh.java +++ b/Mage.Sets/src/mage/cards/v/VengefulPharaoh.java @@ -1,6 +1,7 @@ package mage.cards.v; import mage.MageInt; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.condition.common.SourceInGraveyardCondition; import mage.abilities.effects.common.DestroyTargetEffect; @@ -14,6 +15,7 @@ import mage.constants.Zone; import mage.game.Game; import mage.game.events.DamagedBatchForOnePermanentEvent; import mage.game.events.DamagedBatchForOnePlayerEvent; +import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.common.TargetAttackingCreature; @@ -49,7 +51,7 @@ public final class VengefulPharaoh extends CardImpl { } } -class VengefulPharaohTriggeredAbility extends TriggeredAbilityImpl { +class VengefulPharaohTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { VengefulPharaohTriggeredAbility() { super(Zone.GRAVEYARD, new DestroyTargetEffect(), false); diff --git a/Mage.Sets/src/mage/cards/v/VengefulTownsfolk.java b/Mage.Sets/src/mage/cards/v/VengefulTownsfolk.java index e53e827942e..a16768016e2 100644 --- a/Mage.Sets/src/mage/cards/v/VengefulTownsfolk.java +++ b/Mage.Sets/src/mage/cards/v/VengefulTownsfolk.java @@ -1,7 +1,7 @@ package mage.cards.v; import mage.MageInt; -import mage.abilities.common.DiesOneOrMoreCreatureTriggeredAbility; +import mage.abilities.common.DiesOneOrMoreTriggeredAbility; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -35,7 +35,7 @@ public final class VengefulTownsfolk extends CardImpl { this.toughness = new MageInt(3); // Whenever one or more other creatures you control die, put a +1/+1 counter on Vengeful Townsfolk. - this.addAbility(new DiesOneOrMoreCreatureTriggeredAbility( + this.addAbility(new DiesOneOrMoreTriggeredAbility( new AddCountersSourceEffect(CounterType.P1P1.createInstance(1)), filter, false)); diff --git a/Mage.Sets/src/mage/cards/v/VengefulWarchief.java b/Mage.Sets/src/mage/cards/v/VengefulWarchief.java index 8555a795f0f..1e138a5d861 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 final class VengefulWarchief extends CardImpl { 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 final class VengefulWarchief extends CardImpl { 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/VesselOfTheAllConsuming.java b/Mage.Sets/src/mage/cards/v/VesselOfTheAllConsuming.java index 31987548af9..d0162684638 100644 --- a/Mage.Sets/src/mage/cards/v/VesselOfTheAllConsuming.java +++ b/Mage.Sets/src/mage/cards/v/VesselOfTheAllConsuming.java @@ -3,7 +3,7 @@ package mage.cards.v; import mage.MageInt; import mage.MageObjectReference; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; import mage.abilities.common.DealsDamageToAPlayerTriggeredAbility; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.LoseGameTargetPlayerEffect; @@ -14,10 +14,8 @@ 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.DamagedEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.watchers.Watcher; @@ -25,8 +23,8 @@ import mage.watchers.Watcher; import java.util.AbstractMap; import java.util.HashMap; import java.util.Map; -import java.util.UUID; import java.util.Map.Entry; +import java.util.UUID; /** * @author TheElk801 @@ -48,7 +46,7 @@ public final class VesselOfTheAllConsuming extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // Whenever Vessel of the All-Consuming deals damage, put a +1/+1 counter on it. - this.addAbility(new VesselOfTheAllConsumingTriggeredAbility()); + this.addAbility(new DealsDamageSourceTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()))); // Whenever Vessel of the All-Consuming deals damage to a player, if it has dealt 10 or more damage to that player this turn, they lose the game. this.addAbility(new ConditionalInterveningIfTriggeredAbility( @@ -73,37 +71,6 @@ public final class VesselOfTheAllConsuming extends CardImpl { } } -class VesselOfTheAllConsumingTriggeredAbility extends TriggeredAbilityImpl { - - VesselOfTheAllConsumingTriggeredAbility() { - super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance())); - } - - private VesselOfTheAllConsumingTriggeredAbility(final VesselOfTheAllConsumingTriggeredAbility ability) { - super(ability); - } - - @Override - public VesselOfTheAllConsumingTriggeredAbility copy() { - return new VesselOfTheAllConsumingTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event instanceof DamagedEvent; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return event.getSourceId().equals(getSourceId()); - } - - @Override - public String getRule() { - return "Whenever {this} deals damage, put a +1/+1 counter on it."; - } -} - class VesselOfTheAllConsumingWatcher extends Watcher { private final Map, Integer> morMap = new HashMap<>(); diff --git a/Mage.Sets/src/mage/cards/v/VigorousCharge.java b/Mage.Sets/src/mage/cards/v/VigorousCharge.java index 39382117cac..9c6244e2182 100644 --- a/Mage.Sets/src/mage/cards/v/VigorousCharge.java +++ b/Mage.Sets/src/mage/cards/v/VigorousCharge.java @@ -2,9 +2,11 @@ package mage.cards.v; import mage.MageObjectReference; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.condition.common.KickedCondition; import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; @@ -16,7 +18,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.game.Game; -import mage.game.events.DamagedBatchAllEvent; +import mage.game.events.DamagedBatchBySourceEvent; import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -25,15 +27,14 @@ import mage.target.common.TargetCreaturePermanent; import java.util.UUID; /** - * * @author LevelX2 & L_J */ public final class VigorousCharge extends CardImpl { - + private static final String staticText = "Whenever that creature deals combat damage this turn, if this spell was kicked, you gain life equal to that damage"; public VigorousCharge(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{G}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{G}"); // Kicker {W} (You may pay an additional {W} as you cast this spell.) this.addAbility(new KickerAbility("{W}")); @@ -83,12 +84,12 @@ class VigorousChargeEffect extends OneShotEffect { } } -class VigorousChargeTriggeredAbility extends DelayedTriggeredAbility { +class VigorousChargeTriggeredAbility extends DelayedTriggeredAbility implements BatchTriggeredAbility { private final MageObjectReference mor; VigorousChargeTriggeredAbility(MageObjectReference mor) { - super(null, Duration.EndOfTurn, false, false); + super(new GainLifeEffect(SavedDamageValue.MUCH), Duration.EndOfTurn, false, false); this.mor = mor; } @@ -104,21 +105,16 @@ class VigorousChargeTriggeredAbility extends DelayedTriggeredAbility { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ALL; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_BY_SOURCE; } @Override public boolean checkTrigger(GameEvent event, Game game) { - int amount = ((DamagedBatchAllEvent) event) - .getEvents() - .stream() - .filter(DamagedEvent::isCombatDamage) - .filter(e -> mor.refersTo(e.getAttackerId(), game)) - .mapToInt(GameEvent::getAmount) - .sum(); - this.getEffects().clear(); - this.addEffect(new GainLifeEffect(amount)); - return amount >= 1; + if (!mor.refersTo(event.getSourceId(), game) || !((DamagedBatchBySourceEvent) event).isCombatDamage()) { + return false; + } + this.getEffects().setValue("damage", event.getAmount()); + return true; } @Override diff --git a/Mage.Sets/src/mage/cards/v/VilisBrokerOfBlood.java b/Mage.Sets/src/mage/cards/v/VilisBrokerOfBlood.java index abf53785dbd..9ee117518f5 100644 --- a/Mage.Sets/src/mage/cards/v/VilisBrokerOfBlood.java +++ b/Mage.Sets/src/mage/cards/v/VilisBrokerOfBlood.java @@ -2,10 +2,11 @@ package mage.cards.v; 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.cards.CardSetInfo; 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 final class VilisBrokerOfBlood extends CardImpl { 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 final class VilisBrokerOfBlood extends CardImpl { 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/cards/w/WallOfEssence.java b/Mage.Sets/src/mage/cards/w/WallOfEssence.java index e1d99e68312..cd6479c549f 100644 --- a/Mage.Sets/src/mage/cards/w/WallOfEssence.java +++ b/Mage.Sets/src/mage/cards/w/WallOfEssence.java @@ -1,21 +1,14 @@ - package mage.cards.w; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.common.DealtCombatDamageToSourceTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.keyword.DefenderAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SubType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.DamagedBatchForOnePermanentEvent; -import mage.game.events.GameEvent; -import mage.players.Player; import java.util.UUID; @@ -33,8 +26,9 @@ public final class WallOfEssence extends CardImpl { // Defender this.addAbility(DefenderAbility.getInstance()); + // Whenever Wall of Essence is dealt combat damage, you gain that much life. - this.addAbility(new WallOfEssenceTriggeredAbility()); + this.addAbility(new DealtCombatDamageToSourceTriggeredAbility(new GainLifeEffect(SavedDamageValue.MUCH), false)); } private WallOfEssence(final WallOfEssence card) { @@ -46,66 +40,3 @@ public final class WallOfEssence extends CardImpl { return new WallOfEssence(this); } } - -class WallOfEssenceTriggeredAbility extends TriggeredAbilityImpl { - - public WallOfEssenceTriggeredAbility() { - super(Zone.BATTLEFIELD, new PiousWarriorGainLifeEffect()); - setTriggerPhrase("Whenever {this} is dealt combat damage, "); - } - - private WallOfEssenceTriggeredAbility(final WallOfEssenceTriggeredAbility effect) { - super(effect); - } - - @Override - public WallOfEssenceTriggeredAbility copy() { - return new WallOfEssenceTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - - DamagedBatchForOnePermanentEvent dEvent = (DamagedBatchForOnePermanentEvent) event; - int damage = dEvent.getAmount(); - - if (event.getTargetId().equals(this.sourceId) && dEvent.isCombatDamage() && damage > 0) { - this.getEffects().setValue("damageAmount", damage); - return true; - } - return false; - } -} - - -class PiousWarriorGainLifeEffect extends OneShotEffect { - - PiousWarriorGainLifeEffect() { - super(Outcome.GainLife); - staticText = "you gain that much life"; - } - - private PiousWarriorGainLifeEffect(final PiousWarriorGainLifeEffect effect) { - super(effect); - } - - @Override - public PiousWarriorGainLifeEffect copy() { - return new PiousWarriorGainLifeEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - player.gainLife((Integer) this.getValue("damageAmount"), game, source); - } - return true; - } - -} diff --git a/Mage.Sets/src/mage/cards/w/WallOfSouls.java b/Mage.Sets/src/mage/cards/w/WallOfSouls.java index 1168be41b3f..e80e3be6bd0 100644 --- a/Mage.Sets/src/mage/cards/w/WallOfSouls.java +++ b/Mage.Sets/src/mage/cards/w/WallOfSouls.java @@ -2,7 +2,7 @@ package mage.cards.w; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.DealtCombatDamageToSourceTriggeredAbility; import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.keyword.DefenderAbility; @@ -10,16 +10,11 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.DamagedBatchForOnePermanentEvent; -import mage.game.events.GameEvent; import mage.target.common.TargetOpponentOrPlaneswalker; import java.util.UUID; /** - * * @author fireshoes */ public final class WallOfSouls extends CardImpl { @@ -34,7 +29,7 @@ public final class WallOfSouls extends CardImpl { this.addAbility(DefenderAbility.getInstance()); // Whenever Wall of Souls is dealt combat damage, it deals that much damage to target opponent or planeswalker. - Ability ability = new WallOfSoulsTriggeredAbility(); + Ability ability = new DealtCombatDamageToSourceTriggeredAbility(new DamageTargetEffect(SavedDamageValue.MUCH), false); ability.addTarget(new TargetOpponentOrPlaneswalker()); this.addAbility(ability); } @@ -48,38 +43,3 @@ public final class WallOfSouls extends CardImpl { return new WallOfSouls(this); } } - -class WallOfSoulsTriggeredAbility extends TriggeredAbilityImpl { - - public WallOfSoulsTriggeredAbility() { - super(Zone.BATTLEFIELD, new DamageTargetEffect(SavedDamageValue.MUCH, "it")); - setTriggerPhrase("Whenever {this} is dealt combat damage, "); - } - - private WallOfSoulsTriggeredAbility(final WallOfSoulsTriggeredAbility effect) { - super(effect); - } - - @Override - public WallOfSoulsTriggeredAbility copy() { - return new WallOfSoulsTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - - DamagedBatchForOnePermanentEvent dEvent = (DamagedBatchForOnePermanentEvent) event; - int damage = dEvent.getAmount(); - - if (event.getTargetId().equals(this.sourceId) && dEvent.isCombatDamage() && damage > 0) { - this.getEffects().setValue("damage", damage); - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/w/WarElemental.java b/Mage.Sets/src/mage/cards/w/WarElemental.java index 29b0f742e79..51147cd3b3d 100644 --- a/Mage.Sets/src/mage/cards/w/WarElemental.java +++ b/Mage.Sets/src/mage/cards/w/WarElemental.java @@ -1,27 +1,27 @@ - package mage.cards.w; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.Condition; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.common.SacrificeSourceUnlessConditionEffect; 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.Outcome; import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; +import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.watchers.common.BloodthirstWatcher; +import java.util.UUID; + /** * * @author spjspj @@ -36,7 +36,7 @@ public final class WarElemental extends CardImpl { this.toughness = new MageInt(1); // When War Elemental enters the battlefield, sacrifice it unless an opponent was dealt damage this turn. - this.addAbility(new EntersBattlefieldTriggeredAbility(new SacrificeSourceUnlessConditionEffect(new OpponentWasDealtDamageCondition()))); + this.addAbility(new EntersBattlefieldTriggeredAbility(new SacrificeSourceUnlessConditionEffect(WarElementalCondition.instance))); // Whenever an opponent is dealt damage, put that many +1/+1 counters on War Elemental. this.addAbility(new WarElementalTriggeredAbility()); @@ -53,10 +53,11 @@ public final class WarElemental extends CardImpl { } } -class WarElementalTriggeredAbility extends TriggeredAbilityImpl { +class WarElementalTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { - public WarElementalTriggeredAbility() { - super(Zone.BATTLEFIELD, new WarElementalEffect(), false); + WarElementalTriggeredAbility() { + super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance(), SavedDamageValue.MANY), false); + setTriggerPhrase("Whenever an opponent is dealt damage, "); } private WarElementalTriggeredAbility(final WarElementalTriggeredAbility ability) { @@ -75,44 +76,18 @@ class WarElementalTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - if (game.getOpponents(this.controllerId).contains(event.getTargetId())) { - this.getEffects().get(0).setValue("damageAmount", event.getAmount()); + // all events in the batch are always relevant + if (game.getOpponents(getControllerId()).contains(event.getTargetId())) { + this.getAllEffects().setValue("damage", event.getAmount()); return true; } return false; } - @Override - public String getRule() { - return "Whenever an opponent is dealt damage, put that many +1/+1 counters on {this}."; - } } -class WarElementalEffect extends OneShotEffect { - - WarElementalEffect() { - super(Outcome.Benefit); - } - - private WarElementalEffect(final WarElementalEffect effect) { - super(effect); - } - - @Override - public WarElementalEffect copy() { - return new WarElementalEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - return new AddCountersSourceEffect(CounterType.P1P1.createInstance((Integer) this.getValue("damageAmount"))).apply(game, source); - } -} - -class OpponentWasDealtDamageCondition implements Condition { - - public OpponentWasDealtDamageCondition() { - } +enum WarElementalCondition implements Condition { + instance; @Override public boolean apply(Game game, Ability source) { diff --git a/Mage.Sets/src/mage/cards/w/WarriorAngel.java b/Mage.Sets/src/mage/cards/w/WarriorAngel.java index 0eb4a067c8e..b1352d32db7 100644 --- a/Mage.Sets/src/mage/cards/w/WarriorAngel.java +++ b/Mage.Sets/src/mage/cards/w/WarriorAngel.java @@ -1,23 +1,24 @@ - package mage.cards.w; -import java.util.UUID; import mage.MageInt; -import mage.abilities.common.DealsDamageGainLifeSourceTriggeredAbility; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; +import java.util.UUID; + /** - * * @author fireshoes */ public final class WarriorAngel extends CardImpl { public WarriorAngel(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{W}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}{W}"); this.subtype.add(SubType.ANGEL); this.subtype.add(SubType.WARRIOR); this.power = new MageInt(3); @@ -25,9 +26,9 @@ public final class WarriorAngel extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); - + // Whenever Warrior Angel deals damage, you gain that much life. - this.addAbility(new DealsDamageGainLifeSourceTriggeredAbility()); + this.addAbility(new DealsDamageSourceTriggeredAbility(new GainLifeEffect(SavedDamageValue.MUCH))); } private WarriorAngel(final WarriorAngel card) { @@ -38,4 +39,4 @@ public final class WarriorAngel extends CardImpl { public WarriorAngel copy() { return new WarriorAngel(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/w/WaytaTrainerProdigy.java b/Mage.Sets/src/mage/cards/w/WaytaTrainerProdigy.java index 7101a6ad3f2..780eaf2d97e 100644 --- a/Mage.Sets/src/mage/cards/w/WaytaTrainerProdigy.java +++ b/Mage.Sets/src/mage/cards/w/WaytaTrainerProdigy.java @@ -2,6 +2,8 @@ package mage.cards.w; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; +import mage.abilities.TriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.CostAdjuster; @@ -16,6 +18,8 @@ import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.StaticFilters; import mage.game.Game; +import mage.game.events.BatchEvent; +import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; import mage.game.events.NumberOfTriggersEvent; import mage.game.permanent.Permanent; @@ -24,7 +28,6 @@ import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetCreaturePermanent; import mage.util.CardUtil; -import java.util.Objects; import java.util.UUID; @@ -157,30 +160,34 @@ class WaytaTrainerProdigyEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { // Permanent whose ability is being triggered (and will be retriggered) - Permanent triggeredPermanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); - if (triggeredPermanent == null || !triggeredPermanent.isControlledBy(source.getControllerId())) { + Permanent triggeringPermanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); + if (triggeringPermanent == null || !triggeringPermanent.isControlledBy(source.getControllerId())) { return false; } GameEvent sourceEvent = ((NumberOfTriggersEvent) event).getSourceEvent(); - if (sourceEvent == null) { - return false; + // check all damage events and damage batch events + if (sourceEvent instanceof DamagedEvent) { + return checkDamagedEvent((DamagedEvent) sourceEvent, source.getControllerId(), game); + } else if (sourceEvent instanceof BatchEvent) { + TriggeredAbility sourceTrigger = ((NumberOfTriggersEvent) event).getSourceTrigger(); + for (Object o : sourceTrigger instanceof BatchTriggeredAbility + ? ((BatchTriggeredAbility) sourceTrigger).getFilteredEvents((BatchEvent) sourceEvent, game) + : ((BatchEvent) sourceEvent).getEvents()) { + if (o instanceof DamagedEvent && checkDamagedEvent((DamagedEvent) o, source.getControllerId(), game)) { + return true; + } + } } - switch (sourceEvent.getType()) { - case DAMAGED_PERMANENT: - case DAMAGED_BATCH_FOR_ONE_PERMANENT: - case DAMAGED_BATCH_FOR_PERMANENTS: - case DAMAGED_BATCH_FOR_ALL: - break; - default: - return false; - } - // Get the one or more permanents damaged in this event - // If none of them are controlled by you, this ability doesn't apply. - return CardUtil.getEventTargets(sourceEvent) - .stream() - .map(game::getPermanentOrLKIBattlefield) - .filter(Objects::nonNull) - .anyMatch(p -> p.isControlledBy(source.getControllerId())); + return false; + } + + // Checks that a given DamagedEvent matches with + // "If a creature you control being dealt damage" + private static boolean checkDamagedEvent(DamagedEvent event, UUID controllerId, Game game) { + Permanent damagedPermanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); + return damagedPermanent != null + && damagedPermanent.isCreature(game) + && damagedPermanent.isControlledBy(controllerId); } @Override diff --git a/Mage.Sets/src/mage/cards/w/WildfireElemental.java b/Mage.Sets/src/mage/cards/w/WildfireElemental.java index 26d8c315f6d..d455f472dc3 100644 --- a/Mage.Sets/src/mage/cards/w/WildfireElemental.java +++ b/Mage.Sets/src/mage/cards/w/WildfireElemental.java @@ -1,17 +1,13 @@ package mage.cards.w; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.OpponentDealtNoncombatDamageTriggeredAbility; import mage.abilities.effects.common.continuous.BoostControlledEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.DamagedBatchForOnePlayerEvent; -import mage.game.events.GameEvent; import java.util.UUID; @@ -28,7 +24,7 @@ public final class WildfireElemental extends CardImpl { this.toughness = new MageInt(3); // Whenever an opponent is dealt noncombat damage, creatures you control get +1/+0 until end of turn. - this.addAbility(new WildfireElementalTriggeredAbility()); + this.addAbility(new OpponentDealtNoncombatDamageTriggeredAbility(new BoostControlledEffect(1, 0, Duration.EndOfTurn))); } private WildfireElemental(final WildfireElemental card) { @@ -40,37 +36,3 @@ public final class WildfireElemental extends CardImpl { return new WildfireElemental(this); } } - -class WildfireElementalTriggeredAbility extends TriggeredAbilityImpl { - - WildfireElementalTriggeredAbility() { - super(Zone.BATTLEFIELD, new BoostControlledEffect(1, 0, Duration.EndOfTurn), false); - } - - private WildfireElementalTriggeredAbility(final WildfireElementalTriggeredAbility ability) { - super(ability); - } - - @Override - public WildfireElementalTriggeredAbility copy() { - return new WildfireElementalTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchForOnePlayerEvent dEvent = (DamagedBatchForOnePlayerEvent) event; - return !dEvent.isCombatDamage() && dEvent.getAmount() > 0 && game.getOpponents(controllerId).contains(dEvent.getTargetId()); - } - - @Override - public String getRule() { - return "Whenever an opponent is dealt noncombat damage, " + - "creatures you control get +1/+0 until end of turn."; - } - -} diff --git a/Mage.Sets/src/mage/cards/w/WrathfulRaptors.java b/Mage.Sets/src/mage/cards/w/WrathfulRaptors.java index f994d91f537..d7601820e87 100644 --- a/Mage.Sets/src/mage/cards/w/WrathfulRaptors.java +++ b/Mage.Sets/src/mage/cards/w/WrathfulRaptors.java @@ -3,6 +3,7 @@ package mage.cards.w; import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.TrampleAbility; @@ -16,6 +17,7 @@ import mage.filter.common.FilterAnyTarget; import mage.filter.common.FilterPermanentOrPlayer; import mage.filter.predicate.Predicates; import mage.game.Game; +import mage.game.events.DamagedPermanentEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; @@ -50,7 +52,7 @@ public final class WrathfulRaptors extends CardImpl { } } -class WrathfulRaptorsTriggeredAbility extends TriggeredAbilityImpl { +class WrathfulRaptorsTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { private static final FilterPermanentOrPlayer filter = new FilterAnyTarget("any target that isn't a Dinosaur"); diff --git a/Mage.Sets/src/mage/cards/w/WrathfulRedDragon.java b/Mage.Sets/src/mage/cards/w/WrathfulRedDragon.java index ac1d42c5868..92310b47992 100644 --- a/Mage.Sets/src/mage/cards/w/WrathfulRedDragon.java +++ b/Mage.Sets/src/mage/cards/w/WrathfulRedDragon.java @@ -2,6 +2,7 @@ package mage.cards.w; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.FlyingAbility; @@ -15,6 +16,7 @@ import mage.filter.common.FilterAnyTarget; import mage.filter.common.FilterPermanentOrPlayer; import mage.filter.predicate.Predicates; import mage.game.Game; +import mage.game.events.DamagedPermanentEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; @@ -51,7 +53,7 @@ public final class WrathfulRedDragon extends CardImpl { } } -class WrathfulRedDragonTriggeredAbility extends TriggeredAbilityImpl { +class WrathfulRedDragonTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { private static final FilterPermanentOrPlayer filter = new FilterAnyTarget("any target that isn't a Dragon"); diff --git a/Mage.Sets/src/mage/cards/y/YarusRoarOfTheOldGods.java b/Mage.Sets/src/mage/cards/y/YarusRoarOfTheOldGods.java index d5d4f46f30d..9cc4eff08fe 100644 --- a/Mage.Sets/src/mage/cards/y/YarusRoarOfTheOldGods.java +++ b/Mage.Sets/src/mage/cards/y/YarusRoarOfTheOldGods.java @@ -4,7 +4,7 @@ import java.util.UUID; import mage.MageInt; import mage.MageObjectReference; import mage.abilities.Ability; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.common.DiesCreatureTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.OneShotEffect; @@ -57,7 +57,7 @@ public final class YarusRoarOfTheOldGods extends CardImpl { HasteAbility.getInstance(), Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT_CREATURES, true))); // Whenever one or more face-down creatures you control deal combat damage to a player, draw a card. - this.addAbility(new DealCombatDamageControlledTriggeredAbility( + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility( new DrawCardSourceControllerEffect(1), filter) .setTriggerPhrase("Whenever one or more face-down creatures you control deal combat damage to a player, ")); // Whenever a face-down creature you control dies, return it to the battlefield face down under its owner's control if it's a permanent card, then turn it face up. diff --git a/Mage.Sets/src/mage/cards/z/ZebraUnicorn.java b/Mage.Sets/src/mage/cards/z/ZebraUnicorn.java index f03f92fcf52..d9509e1622c 100644 --- a/Mage.Sets/src/mage/cards/z/ZebraUnicorn.java +++ b/Mage.Sets/src/mage/cards/z/ZebraUnicorn.java @@ -1,28 +1,29 @@ - package mage.cards.z; -import java.util.UUID; import mage.MageInt; -import mage.abilities.common.DealsDamageGainLifeSourceTriggeredAbility; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; +import java.util.UUID; + /** - * * @author fireshoes */ public final class ZebraUnicorn extends CardImpl { public ZebraUnicorn(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{G}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{W}"); this.subtype.add(SubType.UNICORN); this.power = new MageInt(2); this.toughness = new MageInt(2); // Whenever Zebra Unicorn deals damage, you gain that much life. - this.addAbility(new DealsDamageGainLifeSourceTriggeredAbility()); + this.addAbility(new DealsDamageSourceTriggeredAbility(new GainLifeEffect(SavedDamageValue.MUCH))); } private ZebraUnicorn(final ZebraUnicorn card) { @@ -33,4 +34,4 @@ public final class ZebraUnicorn extends CardImpl { public ZebraUnicorn copy() { return new ZebraUnicorn(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/z/ZellixSanityFlayer.java b/Mage.Sets/src/mage/cards/z/ZellixSanityFlayer.java index 7c7e9670fc1..f154a6ee95b 100644 --- a/Mage.Sets/src/mage/cards/z/ZellixSanityFlayer.java +++ b/Mage.Sets/src/mage/cards/z/ZellixSanityFlayer.java @@ -2,6 +2,7 @@ package mage.cards.z; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.ChooseABackgroundAbility; import mage.abilities.common.SimpleActivatedAbility; @@ -9,16 +10,17 @@ import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.MillCardsTargetEffect; +import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; import mage.constants.Zone; -import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.MilledBatchForOnePlayerEvent; +import mage.game.events.MilledCardEvent; import mage.game.permanent.token.Horror2Token; import mage.target.TargetPlayer; @@ -60,7 +62,7 @@ public final class ZellixSanityFlayer extends CardImpl { } } -class ZellixSanityFlayerTriggeredAbility extends TriggeredAbilityImpl { +class ZellixSanityFlayerTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { ZellixSanityFlayerTriggeredAbility() { super(Zone.BATTLEFIELD, new CreateTokenEffect(new Horror2Token())); @@ -82,8 +84,14 @@ class ZellixSanityFlayerTriggeredAbility extends TriggeredAbilityImpl { return event.getType() == GameEvent.EventType.MILLED_CARDS_BATCH_FOR_ONE_PLAYER; } + @Override + public boolean checkEvent(MilledCardEvent event, Game game) { + Card card = event.getCard(game); + return card != null && card.isCreature(game); + } + @Override public boolean checkTrigger(GameEvent event, Game game) { - return ((MilledBatchForOnePlayerEvent) event).getCards(game).count(StaticFilters.FILTER_CARD_CREATURE, game) > 0; + return !getFilteredEvents((MilledBatchForOnePlayerEvent) event, game).isEmpty(); } } diff --git a/Mage.Sets/src/mage/cards/z/ZurgoAndOjutai.java b/Mage.Sets/src/mage/cards/z/ZurgoAndOjutai.java index e033dbe1a28..4a9d8320e34 100644 --- a/Mage.Sets/src/mage/cards/z/ZurgoAndOjutai.java +++ b/Mage.Sets/src/mage/cards/z/ZurgoAndOjutai.java @@ -2,6 +2,7 @@ package mage.cards.z; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.SourceEnteredThisTurnCondition; @@ -73,7 +74,7 @@ public final class ZurgoAndOjutai extends CardImpl { } } -class ZurgoAndOjutaiTriggeredAbility extends TriggeredAbilityImpl { +class ZurgoAndOjutaiTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { ZurgoAndOjutaiTriggeredAbility() { super(Zone.BATTLEFIELD, new LookLibraryAndPickControllerEffect(3, 1, PutCards.HAND, PutCards.BOTTOM_ANY)); @@ -95,24 +96,22 @@ class ZurgoAndOjutaiTriggeredAbility extends TriggeredAbilityImpl { return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ALL; } + @Override + public boolean checkEvent(DamagedEvent event, Game game) { + Permanent permanent = game.getPermanent(event.getSourceId()); + Permanent defender = game.getPermanent(event.getTargetId()); + return permanent != null + && permanent.hasSubtype(SubType.DRAGON, game) + && permanent.isControlledBy(getControllerId()) + && ((defender != null && defender.isBattle(game)) || game.getPlayer(event.getTargetId()) != null); + } + @Override public boolean checkTrigger(GameEvent event, Game game) { - List permanents = ((DamagedBatchAllEvent) event) - .getEvents() + List permanents = getFilteredEvents((DamagedBatchAllEvent) event, game) .stream() - .filter(DamagedEvent::isCombatDamage) - .map(e -> { - Permanent permanent = game.getPermanent(e.getSourceId()); - Permanent defender = game.getPermanent(e.getTargetId()); - if (permanent != null - && permanent.hasSubtype(SubType.DRAGON, game) - && permanent.isControlledBy(this.getControllerId()) - && ((defender != null && defender.isBattle(game)) - || game.getPlayer(e.getTargetId()) != null)) { - return permanent; - } - return null; - }) + .map(GameEvent::getSourceId) + .map(game::getPermanent) .filter(Objects::nonNull) .collect(Collectors.toList()); if (permanents.isEmpty()) { diff --git a/Mage.Sets/src/mage/sets/AssassinsCreed.java b/Mage.Sets/src/mage/sets/AssassinsCreed.java index 531396e3e2c..09f13e42ce4 100644 --- a/Mage.Sets/src/mage/sets/AssassinsCreed.java +++ b/Mage.Sets/src/mage/sets/AssassinsCreed.java @@ -66,6 +66,7 @@ public final class AssassinsCreed extends ExpansionSet { cards.add(new SetCardInfo("Escarpment Fortress", 278, Rarity.RARE, mage.cards.e.EscarpmentFortress.class)); cards.add(new SetCardInfo("Evie Frye", 19, Rarity.RARE, mage.cards.e.EvieFrye.class)); cards.add(new SetCardInfo("Excalibur, Sword of Eden", 72, Rarity.RARE, mage.cards.e.ExcaliburSwordOfEden.class)); + cards.add(new SetCardInfo("Ezio Auditore da Firenze", 25, Rarity.MYTHIC, mage.cards.e.EzioAuditoreDaFirenze.class)); cards.add(new SetCardInfo("Ezio, Blade of Vengeance", 275, Rarity.MYTHIC, mage.cards.e.EzioBladeOfVengeance.class)); cards.add(new SetCardInfo("Ezio, Brash Novice", 55, Rarity.UNCOMMON, mage.cards.e.EzioBrashNovice.class)); cards.add(new SetCardInfo("Fatal Push", 90, Rarity.UNCOMMON, mage.cards.f.FatalPush.class)); diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java index 1ab3f889832..e8fdce8dc5e 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java @@ -77,6 +77,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Crypt Ghast", 368, Rarity.MYTHIC, mage.cards.c.CryptGhast.class)); cards.add(new SetCardInfo("Culling Ritual", 85, Rarity.RARE, mage.cards.c.CullingRitual.class)); cards.add(new SetCardInfo("Cultivate", 174, Rarity.COMMON, mage.cards.c.Cultivate.class)); + cards.add(new SetCardInfo("Curator Beastie", 30, Rarity.RARE, mage.cards.c.CuratorBeastie.class)); cards.add(new SetCardInfo("Damn", 369, Rarity.MYTHIC, mage.cards.d.Damn.class)); cards.add(new SetCardInfo("Darkmoss Bridge", 269, Rarity.COMMON, mage.cards.d.DarkmossBridge.class)); cards.add(new SetCardInfo("Deadbridge Chant", 215, Rarity.MYTHIC, mage.cards.d.DeadbridgeChant.class)); @@ -111,9 +112,11 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Flooded Grove", 276, Rarity.RARE, mage.cards.f.FloodedGrove.class)); cards.add(new SetCardInfo("Florian, Voldaren Scion", 217, Rarity.RARE, mage.cards.f.FlorianVoldarenScion.class)); cards.add(new SetCardInfo("Foreboding Ruins", 277, Rarity.RARE, mage.cards.f.ForebodingRuins.class)); + cards.add(new SetCardInfo("Formless Genesis", 34, Rarity.RARE, mage.cards.f.FormlessGenesis.class)); cards.add(new SetCardInfo("Geothermal Bog", 278, Rarity.COMMON, mage.cards.g.GeothermalBog.class)); cards.add(new SetCardInfo("Giant Adephage", 179, Rarity.MYTHIC, mage.cards.g.GiantAdephage.class)); cards.add(new SetCardInfo("Gleeful Arsonist", 27, Rarity.RARE, mage.cards.g.GleefulArsonist.class)); + cards.add(new SetCardInfo("Glitch Interpreter", 13, Rarity.RARE, mage.cards.g.GlitchInterpreter.class)); cards.add(new SetCardInfo("Gnarlwood Dryad", 180, Rarity.UNCOMMON, mage.cards.g.GnarlwoodDryad.class)); cards.add(new SetCardInfo("Golgari Rot Farm", 279, Rarity.UNCOMMON, mage.cards.g.GolgariRotFarm.class)); cards.add(new SetCardInfo("Golgari Signet", 246, Rarity.UNCOMMON, mage.cards.g.GolgariSignet.class)); @@ -219,6 +222,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Shark Typhoon", 127, Rarity.RARE, mage.cards.s.SharkTyphoon.class)); cards.add(new SetCardInfo("Shigeki, Jukai Visionary", 198, Rarity.RARE, mage.cards.s.ShigekiJukaiVisionary.class)); cards.add(new SetCardInfo("Shivan Gorge", 297, Rarity.RARE, mage.cards.s.ShivanGorge.class)); + cards.add(new SetCardInfo("Shriekwood Devourer", 35, Rarity.RARE, mage.cards.s.ShriekwoodDevourer.class)); cards.add(new SetCardInfo("Sigil of the Empty Throne", 103, Rarity.RARE, mage.cards.s.SigilOfTheEmptyThrone.class)); cards.add(new SetCardInfo("Sign in Blood", 156, Rarity.COMMON, mage.cards.s.SignInBlood.class)); cards.add(new SetCardInfo("Simic Growth Chamber", 298, Rarity.UNCOMMON, mage.cards.s.SimicGrowthChamber.class)); @@ -279,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("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 26cc85ba36d..e74abeb44e4 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.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author escplan9 */ public class GontisMachinationsTest extends CardTestPlayerBase { @@ -19,57 +18,59 @@ Whenever you lose life for the first time each turn, you get {E}. (You get an en 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 db6b4e2b1b2..036ee0f4441 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.junit.Test; 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 class ExquisiteBloodTest extends CardTestPlayerBase { } /** - * 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 class ExquisiteBloodTest extends CardTestPlayerBase { // 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 class ExquisiteBloodTest extends CardTestPlayerBase { 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 class ExquisiteBloodTest extends CardTestPlayerBase { 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 class ExquisiteBloodTest extends CardTestPlayerBase { // 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 class ExquisiteBloodTest extends CardTestPlayerBase { 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 class ExquisiteBloodTest extends CardTestPlayerBase { 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/cmr/BlazingSunsteelTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/cmr/BlazingSunsteelTest.java new file mode 100644 index 00000000000..d8863ac9b3c --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/cmr/BlazingSunsteelTest.java @@ -0,0 +1,42 @@ +package org.mage.test.cards.single.cmr; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class BlazingSunsteelTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.b.BlazingSunsteel Blazing Sunsteel} {1}{R} + * Artifact — Equipment + * Equipped creature gets +1/+0 for each opponent you have. + * Whenever equipped creature is dealt damage, it deals that much damage to any target. + * Equip {4} + */ + private static final String sunsteel = "Blazing Sunsteel"; + + @Test + public void test_Trigger() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, sunsteel); + addCard(Zone.BATTLEFIELD, playerA, "Memnite"); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip", "Memnite"); + castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Lightning Bolt", "Memnite"); + addTarget(playerA, playerB); // Sunsteel trigger + + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Memnite", 1); + assertGraveyardCount(playerA, "Lightning Bolt", 1); + assertLife(playerB, 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 00000000000..f71233c32be --- /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/mir/BindingAgonyTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mir/BindingAgonyTest.java new file mode 100644 index 00000000000..9360089029e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mir/BindingAgonyTest.java @@ -0,0 +1,62 @@ +package org.mage.test.cards.single.mir; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class BindingAgonyTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.b.BindingAgony Binding Agony} {1}{B} + * Enchantment — Aura + * Enchant creature + * Whenever enchanted creature is dealt damage, Binding Agony deals that much damage to that creature’s controller. + */ + private static final String agony = "Binding Agony"; + + @Test + public void test_Trigger_Once_DoubleBlocked() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); + addCard(Zone.HAND, playerA, agony); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + addCard(Zone.BATTLEFIELD, playerB, "Memnite"); + addCard(Zone.BATTLEFIELD, playerB, "Centaur Courser"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, agony, "Grizzly Bears"); + + attack(1, playerA, "Grizzly Bears"); + block(1, playerB, "Centaur Courser", "Grizzly Bears"); + block(1, playerB, "Memnite", "Grizzly Bears"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Grizzly Bears", 1); + assertLife(playerA, 20 - 1 - 3); + } + + @Test + public void test_NonCombat_NoTrigger() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears"); + addCard(Zone.HAND, playerA, agony); + addCard(Zone.BATTLEFIELD, playerA, "Badlands", 3); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, agony, "Grizzly Bears", true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Grizzly Bears"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerB, "Grizzly Bears", 1); + assertLife(playerB, 20 - 3); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mir/SunDropletTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mir/SunDropletTest.java new file mode 100644 index 00000000000..8b7bc77476e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mir/SunDropletTest.java @@ -0,0 +1,74 @@ +package org.mage.test.cards.single.mir; + +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 SunDropletTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.s.SunDroplet Sun Droplet} {2} + * Artifact + * Whenever you’re dealt damage, put that many charge counters on Sun Droplet. + * At the beginning of each upkeep, you may remove a charge counter from Sun Droplet. If you do, you gain 1 life. + */ + private static final String droplet = "Sun Droplet"; + + @Test + public void test_Trigger_Combat() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, droplet); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 2); // 2/2 + + attack(1, playerA, "Grizzly Bears"); + attack(1, playerA, "Grizzly Bears"); + + // Only one trigger. + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertCounterCount(playerB, droplet, CounterType.CHARGE, 2 * 2); + assertLife(playerB, 20 - 2 * 2); + } + + @Test + public void test_Trigger_NonCombat() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, droplet); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertCounterCount(playerB, droplet, CounterType.CHARGE, 3); + assertLife(playerB, 20 - 3); + } + + @Test + public void test_NoTrigger_OtherPlayer() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, droplet); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertCounterCount(playerB, droplet, CounterType.CHARGE, 0); + assertLife(playerA, 20 - 3); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mir/WarElementalTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mir/WarElementalTest.java new file mode 100644 index 00000000000..8c87d37dd1d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mir/WarElementalTest.java @@ -0,0 +1,81 @@ +package org.mage.test.cards.single.mir; + +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 WarElementalTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.w.WarElemental War Elemental} {R}{R}{R} + * Creature — Elemental + * When War Elemental enters the battlefield, sacrifice it unless an opponent was dealt damage this turn. + * Whenever an opponent is dealt damage, put that many +1/+1 counters on War Elemental. + * 1/1 + */ + private static final String elemental = "War Elemental"; + + @Test + public void test_Trigger_Combat() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, elemental); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); + addCard(Zone.BATTLEFIELD, playerA, "Elite Vanguard"); + addCard(Zone.BATTLEFIELD, playerB, "Memnite"); + + attack(1, playerA, "War Elemental"); + attack(1, playerA, "Grizzly Bears"); + attack(1, playerA, "Elite Vanguard"); + block(1, playerB, "Memnite", "Elite Vanguard"); + + // Only one trigger. + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, 1); + assertGraveyardCount(playerB, 1); + assertCounterCount(playerA, elemental, CounterType.P1P1, 1 + 2); + assertLife(playerB, 20 - 1 - 2); + } + + @Test + public void test_Trigger_NonCombat() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, elemental); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertCounterCount(playerA, elemental, CounterType.P1P1, 3); + assertLife(playerB, 20 - 3); + } + + @Test + public void test_NoTrigger_You() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, elemental); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertCounterCount(playerA, elemental, CounterType.P1P1, 0); + assertLife(playerA, 20 - 3); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/KayaSpiritsJusticeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/KayaSpiritsJusticeTest.java index 79a6ec97e27..20cc546706d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/KayaSpiritsJusticeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/KayaSpiritsJusticeTest.java @@ -31,7 +31,7 @@ public class KayaSpiritsJusticeTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Astrid Peth"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); - // Exile all creatures and graveyards + // Exile all creatures. Exile all graveyards. castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Farewell"); setModeChoice(playerA, "2"); setModeChoice(playerA, "4"); 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 00000000000..ec5b050ce86 --- /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.Tests/src/test/java/org/mage/test/cards/single/sth/WallOfEssenceTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/sth/WallOfEssenceTest.java new file mode 100644 index 00000000000..2048a9ea9f7 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/sth/WallOfEssenceTest.java @@ -0,0 +1,77 @@ +package org.mage.test.cards.single.sth; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class WallOfEssenceTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.w.WallOfEssence Wall of Essence} {1}{W} + * Creature — Wall + * Defender (This creature can’t attack.) + * Whenever Wall of Essence is dealt combat damage, you gain that much life. + * 0/4 + */ + private static final String wall = "Wall of Essence"; + + @Test + public void test_Trigger() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, wall); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); // 2/2 + + attack(1, playerA, "Grizzly Bears"); + block(1, playerB, wall, "Grizzly Bears"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertDamageReceived(playerB, wall, 2); + assertLife(playerB, 20 + 2); + } + + @Test + public void test_NotTrigger_OtherCombatDamage() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, wall); + addCard(Zone.BATTLEFIELD, playerB, "Memnite"); // 1/1 + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); // 2/2 + + attack(1, playerA, "Grizzly Bears"); + block(1, playerB, "Memnite", "Grizzly Bears"); + block(1, playerB, wall, "Grizzly Bears"); + + setChoice(playerA, "X=2"); // 2 damage on Memnite, no damage to Wall + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertDamageReceived(playerB, wall, 0); + assertGraveyardCount(playerB, "Memnite", 1); + assertLife(playerB, 20); + } + + @Test + public void test_NonCombat_NoTrigger() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, wall); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", wall); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertDamageReceived(playerB, wall, 3); + assertLife(playerB, 20); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/uds/PhyrexianNegatorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/uds/PhyrexianNegatorTest.java new file mode 100644 index 00000000000..0a7e17089f8 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/uds/PhyrexianNegatorTest.java @@ -0,0 +1,65 @@ +package org.mage.test.cards.single.uds; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class PhyrexianNegatorTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.p.PhyrexianNegator Phyrexian Negator} {2}{B} + * Creature — Phyrexian Horror + * Trample + * Whenever Phyrexian Negator is dealt damage, sacrifice that many permanents. + * 5/5 + */ + private static final String negator = "Phyrexian Negator"; + + @Test + public void test_Trigger_Combat() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, negator); + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears"); + addCard(Zone.BATTLEFIELD, playerB, "Memnite"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 10); + + attack(1, playerA, negator); + block(1, playerB, "Grizzly Bears", negator); + block(1, playerB, "Memnite", negator); + + setChoice(playerA, "X=2"); // damage to Bears + setChoice(playerA, "X=3"); // damage to Memnite + + setChoice(playerA, "Mountain", 3); // sac those. + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertDamageReceived(playerA, negator, 3); + assertPermanentCount(playerA, "Mountain", 10 - 3); + } + + @Test + public void test_Trigger_NonCombat() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, negator); + addCard(Zone.HAND, playerA, "Shock"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 10); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Shock", negator); + + setChoice(playerA, "Mountain", 2); // sac those. + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertDamageReceived(playerA, negator, 2); + assertPermanentCount(playerA, "Mountain", 10 - 2); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/xln/JaceCunningCastawayTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/xln/JaceCunningCastawayTest.java new file mode 100644 index 00000000000..439eb0e674e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/xln/JaceCunningCastawayTest.java @@ -0,0 +1,44 @@ +package org.mage.test.cards.single.xln; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class JaceCunningCastawayTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.j.JaceCunningCastaway Jace, Cunning Castaway} {1}{U}{U} + * Legendary Planeswalker — Jace + * +1: Whenever one or more creatures you control deal combat damage to a player this turn, draw a card, then discard a card. + * −2: Create a 2/2 blue Illusion creature token with “When this creature becomes the target of a spell, sacrifice it.” + * −5: Create two tokens that are copies of Jace, Cunning Castaway, except they’re not legendary. + * Loyalty: 3 + */ + private static final String jace = "Jace, Cunning Castaway"; + + @Test + public void test_PlusOne_Trigger() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, jace); + addCard(Zone.BATTLEFIELD, playerA, "Savannah Lions"); + addCard(Zone.BATTLEFIELD, playerA, "Alaborn Trooper"); + addCard(Zone.LIBRARY, playerA, "Taiga"); // for looting. + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1:"); + + attack(1, playerA, "Savannah Lions", playerB); + attack(1, playerA, "Alaborn Trooper", playerB); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerB, 20 - 2 - 2); + assertGraveyardCount(playerA, "Taiga", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/damage/FelixFiveBootsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/damage/FelixFiveBootsTest.java index 0b53ba6526e..6176bada321 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/damage/FelixFiveBootsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/damage/FelixFiveBootsTest.java @@ -3,7 +3,6 @@ package org.mage.test.cards.triggers.damage; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.counters.CounterType; -import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -101,7 +100,6 @@ public class FelixFiveBootsTest extends CardTestPlayerBase { } @Test - @Ignore // see #12095 public void testSelectRightPartOfBatch() { setStrictChooseMode(true); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/VengefulTownsfolkTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/VengefulTownsfolkTest.java index 182f315048c..0cb9e99549e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/VengefulTownsfolkTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/VengefulTownsfolkTest.java @@ -6,7 +6,7 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * Tests the {@link mage.abilities.common.DiesOneOrMoreCreatureTriggeredAbility} batching. + * Tests the {@link mage.abilities.common.DiesOneOrMoreTriggeredAbility} batching. * * @author Susucr */ diff --git a/Mage/src/main/java/mage/abilities/BatchTriggeredAbility.java b/Mage/src/main/java/mage/abilities/BatchTriggeredAbility.java new file mode 100644 index 00000000000..9823e20b6fd --- /dev/null +++ b/Mage/src/main/java/mage/abilities/BatchTriggeredAbility.java @@ -0,0 +1,38 @@ +package mage.abilities; + +import mage.game.Game; +import mage.game.events.BatchEvent; +import mage.game.events.GameEvent; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Batch triggers (e.g. 'When... one or more ..., ') + * require additional logic to check the events in the batch. + * Parametrized on the individual game event type. + * + * @see mage.game.events.BatchEvent + * + * @author Susucr, xenohedron + */ +public interface BatchTriggeredAbility extends TriggeredAbility { + + /** + * Some events in the batch may not be relevant to the trigger logic. + * If so, use this method to exclude them. + */ + default boolean checkEvent(T event, Game game) { + return true; + } + + /** + * For use in checkTrigger - streams all events that pass the event check + */ + default List getFilteredEvents(BatchEvent event, Game game) { + return event.getEvents() + .stream() + .filter(e -> checkEvent(e, game)) + .collect(Collectors.toList()); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTappedOneOrMoreTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTappedOneOrMoreTriggeredAbility.java index 7b96abf685c..f04ea6cb432 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTappedOneOrMoreTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTappedOneOrMoreTriggeredAbility.java @@ -1,5 +1,6 @@ package mage.abilities.common; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.constants.Zone; @@ -7,13 +8,15 @@ import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.TappedBatchEvent; +import mage.game.events.TappedEvent; +import mage.game.permanent.Permanent; /** * @author Susucr */ -public class BecomesTappedOneOrMoreTriggeredAbility extends TriggeredAbilityImpl { +public class BecomesTappedOneOrMoreTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { - protected FilterPermanent filter; + private final FilterPermanent filter; public BecomesTappedOneOrMoreTriggeredAbility(Zone zone, Effect effect, boolean optional, FilterPermanent filter) { super(zone, effect, optional); @@ -36,13 +39,14 @@ public class BecomesTappedOneOrMoreTriggeredAbility extends TriggeredAbilityImpl return event.getType() == GameEvent.EventType.TAPPED_BATCH; } + @Override + public boolean checkEvent(TappedEvent event, Game game) { + Permanent permanent = game.getPermanent(event.getTargetId()); + return permanent != null && filter.match(permanent, getControllerId(), this, game); + } + @Override public boolean checkTrigger(GameEvent event, Game game) { - TappedBatchEvent batchEvent = (TappedBatchEvent) event; - return batchEvent - .getTargetIds() - .stream() - .map(game::getPermanent) - .anyMatch(p -> filter.match(p, getControllerId(), this, game)); + return !getFilteredEvents((TappedBatchEvent) event, game).isEmpty(); } } diff --git a/Mage/src/main/java/mage/abilities/common/CombatDamageDealtToYouTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/CombatDamageDealtToYouTriggeredAbility.java index c2e3b4128b9..f300f6cda52 100644 --- a/Mage/src/main/java/mage/abilities/common/CombatDamageDealtToYouTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/CombatDamageDealtToYouTriggeredAbility.java @@ -1,17 +1,15 @@ package mage.abilities.common; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedBatchForPlayersEvent; -import mage.game.events.DamagedEvent; +import mage.game.events.DamagedBatchForOnePlayerEvent; +import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; -import java.util.UUID; - /** * A triggered ability for whenever one or more creatures deal combat damage to * you. Has an optional component for setting the target pointer to the opponent @@ -19,87 +17,47 @@ import java.util.UUID; * * @author alexander-novo */ -public class CombatDamageDealtToYouTriggeredAbility extends TriggeredAbilityImpl { +public class CombatDamageDealtToYouTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { - // Whether the ability should set a target targetting the opponent who + // Whether the ability should set a target targeting the opponent who // controls the creatures who dealt damage to you - private final boolean setTarget; + private final boolean setTargetPointer; - /** - * @param effect The effect that should happen when the ability resolves - */ public CombatDamageDealtToYouTriggeredAbility(Effect effect) { - this(effect, false); + this(Zone.BATTLEFIELD, effect, false, false); } - /** - * @param effect The effect that should happen when the ability resolves - * @param setTarget Whether or not the ability should set a target targetting - * the opponent who controls the creatures who dealt damage to - * you - */ - public CombatDamageDealtToYouTriggeredAbility(Effect effect, boolean setTarget) { - this(Zone.BATTLEFIELD, effect, setTarget, false); - } - - /** - * @param zone Which zone the ability shoudl take effect in - * @param effect The effect that should happen when the ability resolves - * @param setTarget Whether or not the ability should set a target targetting - * the opponent who controls the creatures who dealt damage to - * you - * @param optional Whether or not the ability is optional - */ - public CombatDamageDealtToYouTriggeredAbility(Zone zone, Effect effect, boolean setTarget, - boolean optional) { + public CombatDamageDealtToYouTriggeredAbility(Zone zone, Effect effect, boolean setTargetPointer, boolean optional) { super(zone, effect, optional); - - this.setTarget = setTarget; - + this.setTargetPointer = setTargetPointer; setTriggerPhrase(generateTriggerPhrase()); } private CombatDamageDealtToYouTriggeredAbility(final CombatDamageDealtToYouTriggeredAbility ability) { super(ability); - - this.setTarget = ability.setTarget; + this.setTargetPointer = ability.setTargetPointer; } @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_PLAYERS; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; } @Override public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchForPlayersEvent dEvent = (DamagedBatchForPlayersEvent) event; - - boolean isDamaged = false; - UUID damageSourceControllerID = null; - for (DamagedEvent damagedEvent : dEvent.getEvents()) { - if (damagedEvent.isCombatDamage() && damagedEvent.getPlayerId() == this.controllerId) { - isDamaged = true; - // TODO: current code support only one controller - // (it's can be potentially bugged in team mode with multiple attack players) - Permanent damageSource = game.getPermanent(damagedEvent.getSourceId()); - if (damageSource != null) { - damageSourceControllerID = damageSource.getControllerId(); - } - } + if (!isControlledBy(event.getTargetId()) || !((DamagedBatchForOnePlayerEvent) event).isCombatDamage()) { + return false; } - - if (isDamaged) { - if (this.setTarget && damageSourceControllerID != null) { - this.getEffects().setTargetPointer(new FixedTarget(damageSourceControllerID)); - } - return true; + if (setTargetPointer) { + // attacking player is active player + this.getEffects().setTargetPointer(new FixedTarget(game.getActivePlayerId())); } + return true; - return false; } private String generateTriggerPhrase() { - if (setTarget) { + if (setTargetPointer) { return "Whenever one or more creatures an opponent controls deal combat damage to you, "; } else { return "Whenever one or more creatures deal combat damage to you, "; @@ -110,4 +68,4 @@ public class CombatDamageDealtToYouTriggeredAbility extends TriggeredAbilityImpl public CombatDamageDealtToYouTriggeredAbility copy() { return new CombatDamageDealtToYouTriggeredAbility(this); } -} \ No newline at end of file +} diff --git a/Mage/src/main/java/mage/abilities/common/DealCombatDamageControlledTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealCombatDamageControlledTriggeredAbility.java deleted file mode 100644 index 8f9dbe97797..00000000000 --- a/Mage/src/main/java/mage/abilities/common/DealCombatDamageControlledTriggeredAbility.java +++ /dev/null @@ -1,39 +0,0 @@ -package mage.abilities.common; - -import mage.abilities.effects.Effect; -import mage.constants.SetTargetPointer; -import mage.constants.Zone; -import mage.filter.StaticFilters; -import mage.filter.common.FilterCreaturePermanent; - -/** - * @author Xanderhall, xenohedron - */ -public class DealCombatDamageControlledTriggeredAbility extends OneOrMoreDealDamageTriggeredAbility { - - public DealCombatDamageControlledTriggeredAbility(Effect effect) { - this(effect, SetTargetPointer.NONE); - } - - public DealCombatDamageControlledTriggeredAbility(Effect effect, FilterCreaturePermanent filter) { - this(Zone.BATTLEFIELD, effect, filter, SetTargetPointer.NONE, false); - } - - public DealCombatDamageControlledTriggeredAbility(Effect effect, SetTargetPointer setTargetPointer) { - this(Zone.BATTLEFIELD, effect, StaticFilters.FILTER_PERMANENT_CREATURES, setTargetPointer, false); - } - - public DealCombatDamageControlledTriggeredAbility(Zone zone, Effect effect, FilterCreaturePermanent filter, - SetTargetPointer setTargetPointer, boolean optional) { - super(zone, effect, filter, true, true, setTargetPointer, optional); - } - - protected DealCombatDamageControlledTriggeredAbility(final DealCombatDamageControlledTriggeredAbility ability) { - super(ability); - } - - @Override - public DealCombatDamageControlledTriggeredAbility copy() { - return new DealCombatDamageControlledTriggeredAbility(this); - } -} diff --git a/Mage/src/main/java/mage/abilities/common/DealsCombatDamageEquippedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealsCombatDamageEquippedTriggeredAbility.java index 9620d538d1c..40f17599216 100644 --- a/Mage/src/main/java/mage/abilities/common/DealsCombatDamageEquippedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealsCombatDamageEquippedTriggeredAbility.java @@ -1,10 +1,11 @@ package mage.abilities.common; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedBatchAllEvent; +import mage.game.events.DamagedBatchBySourceEvent; import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -12,7 +13,7 @@ import mage.game.permanent.Permanent; /** * @author TheElk801, xenohedron */ -public class DealsCombatDamageEquippedTriggeredAbility extends TriggeredAbilityImpl { +public class DealsCombatDamageEquippedTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { public DealsCombatDamageEquippedTriggeredAbility(Effect effect) { this(effect, false); @@ -34,26 +35,19 @@ public class DealsCombatDamageEquippedTriggeredAbility extends TriggeredAbilityI @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ALL; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_BY_SOURCE; } @Override public boolean checkTrigger(GameEvent event, Game game) { + if (!((DamagedBatchBySourceEvent) event).isCombatDamage()) { + return false; + } Permanent sourcePermanent = getSourcePermanentOrLKI(game); - if (sourcePermanent == null || sourcePermanent.getAttachedTo() == null) { + if (sourcePermanent == null || !event.getSourceId().equals(sourcePermanent.getAttachedTo())) { return false; } - int amount = ((DamagedBatchAllEvent) event) - .getEvents() - .stream() - .filter(DamagedEvent::isCombatDamage) - .filter(e -> e.getAttackerId().equals(sourcePermanent.getAttachedTo())) - .mapToInt(GameEvent::getAmount) - .sum(); - if (amount < 1) { - return false; - } - this.getEffects().setValue("damage", amount); + this.getEffects().setValue("damage", event.getAmount()); return true; } } diff --git a/Mage/src/main/java/mage/abilities/common/DealsCombatDamageTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealsCombatDamageTriggeredAbility.java index 5d85fbe626a..f829237387d 100644 --- a/Mage/src/main/java/mage/abilities/common/DealsCombatDamageTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealsCombatDamageTriggeredAbility.java @@ -1,10 +1,11 @@ package mage.abilities.common; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedBatchAllEvent; +import mage.game.events.DamagedBatchBySourceEvent; import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; @@ -15,7 +16,7 @@ import mage.game.events.GameEvent; * * @author LevelX, xenohedron */ -public class DealsCombatDamageTriggeredAbility extends TriggeredAbilityImpl { +public class DealsCombatDamageTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { public DealsCombatDamageTriggeredAbility(Effect effect, boolean optional) { super(Zone.BATTLEFIELD, effect, optional); @@ -34,22 +35,15 @@ public class DealsCombatDamageTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ALL; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_BY_SOURCE; } @Override public boolean checkTrigger(GameEvent event, Game game) { - int amount = ((DamagedBatchAllEvent) event) - .getEvents() - .stream() - .filter(DamagedEvent::isCombatDamage) - .filter(e -> e.getAttackerId().equals(getSourceId())) - .mapToInt(GameEvent::getAmount) - .sum(); - if (amount < 1) { + if (!event.getSourceId().equals(getSourceId()) || !((DamagedBatchBySourceEvent) event).isCombatDamage() ) { return false; } - this.getEffects().setValue("damage", amount); + this.getEffects().setValue("damage", event.getAmount()); return true; } } diff --git a/Mage/src/main/java/mage/abilities/common/DealsDamageGainLifeSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealsDamageGainLifeSourceTriggeredAbility.java deleted file mode 100644 index 342ca376a91..00000000000 --- a/Mage/src/main/java/mage/abilities/common/DealsDamageGainLifeSourceTriggeredAbility.java +++ /dev/null @@ -1,47 +0,0 @@ -package mage.abilities.common; - -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.dynamicvalue.common.SavedDamageValue; -import mage.abilities.effects.Effect; -import mage.abilities.effects.common.GainLifeEffect; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; - -/** - * @author LevelX2 - */ - -public class DealsDamageGainLifeSourceTriggeredAbility extends TriggeredAbilityImpl { - - public DealsDamageGainLifeSourceTriggeredAbility() { - super(Zone.BATTLEFIELD, new GainLifeEffect(SavedDamageValue.MUCH), false); - setTriggerPhrase("Whenever {this} deals damage, "); - } - - protected DealsDamageGainLifeSourceTriggeredAbility(final DealsDamageGainLifeSourceTriggeredAbility ability) { - super(ability); - } - - @Override - public DealsDamageGainLifeSourceTriggeredAbility copy() { - return new DealsDamageGainLifeSourceTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT - || event.getType() == GameEvent.EventType.DAMAGED_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getSourceId().equals(this.getSourceId())) { - for (Effect effect : this.getEffects()) { - effect.setValue("damage", event.getAmount()); - } - return true; - } - return false; - } -} diff --git a/Mage/src/main/java/mage/abilities/common/DealsDamageSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealsDamageSourceTriggeredAbility.java new file mode 100644 index 00000000000..dd195c8ff8c --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/DealsDamageSourceTriggeredAbility.java @@ -0,0 +1,50 @@ +package mage.abilities.common; + +import mage.abilities.BatchTriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.DamagedEvent; +import mage.game.events.GameEvent; + +/** + * @author xenohedron + */ + +public class DealsDamageSourceTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { + + public DealsDamageSourceTriggeredAbility(Effect effect) { + this(effect, false); + } + + public DealsDamageSourceTriggeredAbility(Effect effect, boolean optional) { + super(Zone.BATTLEFIELD, effect, optional); + setTriggerPhrase("Whenever {this} deals damage, "); + this.withRuleTextReplacement(true); + } + + protected DealsDamageSourceTriggeredAbility(final DealsDamageSourceTriggeredAbility ability) { + super(ability); + } + + @Override + public DealsDamageSourceTriggeredAbility copy() { + return new DealsDamageSourceTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_BY_SOURCE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + // all events in the batch are always relevant + if (event.getSourceId().equals(this.getSourceId())) { + this.getEffects().setValue("damage", event.getAmount()); + return true; + } + return false; + } +} diff --git a/Mage/src/main/java/mage/abilities/common/DealsDamageToAnyTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealsDamageToAnyTriggeredAbility.java new file mode 100644 index 00000000000..9f2ff4b1efd --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/DealsDamageToAnyTriggeredAbility.java @@ -0,0 +1,77 @@ +package mage.abilities.common; + +import mage.abilities.BatchTriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.SetTargetPointer; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.game.Game; +import mage.game.events.DamagedBatchBySourceEvent; +import mage.game.events.DamagedEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +/** + * @author xenohedron + */ +public class DealsDamageToAnyTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { + + private final FilterPermanent filter; + private final boolean onlyCombat; + private final SetTargetPointer setTargetPointer; + + public DealsDamageToAnyTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, + SetTargetPointer setTargetPointer, boolean onlyCombat, boolean optional) { + super(zone, effect, optional); + this.filter = filter; + this.onlyCombat = onlyCombat; + this.setTargetPointer = setTargetPointer; + setTriggerPhrase(getWhen() + CardUtil.addArticle(filter.getMessage()) + " deals " + + (onlyCombat ? "combat " : "") + "damage, "); + } + + protected DealsDamageToAnyTriggeredAbility(final DealsDamageToAnyTriggeredAbility ability) { + super(ability); + this.filter = ability.filter; + this.onlyCombat = ability.onlyCombat; + this.setTargetPointer = ability.setTargetPointer; + } + + @Override + public DealsDamageToAnyTriggeredAbility copy() { + return new DealsDamageToAnyTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_BY_SOURCE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + // all events in the batch are always relevant if triggers at all + if (onlyCombat && !((DamagedBatchBySourceEvent) event).isCombatDamage()) { + return false; + } + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); + if (permanent == null || !filter.match(permanent, getControllerId(), this, game)) { + return false; + } + getEffects().setValue("damage", event.getAmount()); + switch (setTargetPointer) { + case PERMANENT: + getEffects().setTargetPointer(new FixedTarget(permanent, game)); + return true; + case PLAYER: + getEffects().setTargetPointer(new FixedTarget(permanent.getControllerId())); + return true; + case NONE: + return true; + default: + throw new IllegalArgumentException("Unsupported SetTargetPointer in DealtDamageAttachedTriggeredAbility"); + } + } +} diff --git a/Mage/src/main/java/mage/abilities/common/DealtCombatDamageToSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealtCombatDamageToSourceTriggeredAbility.java new file mode 100644 index 00000000000..4256cd54b59 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/DealtCombatDamageToSourceTriggeredAbility.java @@ -0,0 +1,46 @@ +package mage.abilities.common; + +import mage.abilities.BatchTriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.DamagedBatchForOnePermanentEvent; +import mage.game.events.DamagedPermanentEvent; +import mage.game.events.GameEvent; + +/** + * @author xenohedron + */ +public class DealtCombatDamageToSourceTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { + + public DealtCombatDamageToSourceTriggeredAbility(Effect effect, boolean optional) { + super(Zone.BATTLEFIELD, effect, optional); + setTriggerPhrase("Whenever {this} is dealt combat damage, "); + this.withRuleTextReplacement(true); + } + + protected DealtCombatDamageToSourceTriggeredAbility(final DealtCombatDamageToSourceTriggeredAbility ability) { + super(ability); + } + + @Override + public DealtCombatDamageToSourceTriggeredAbility copy() { + return new DealtCombatDamageToSourceTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + // all events in the batch are always relevant if triggers at all + if (!getSourceId().equals(event.getTargetId()) || !((DamagedBatchForOnePermanentEvent) event).isCombatDamage()) { + return false; + } + this.getEffects().setValue("damage", event.getAmount()); + return true; + } +} diff --git a/Mage/src/main/java/mage/abilities/common/DealtDamageAnyTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealtDamageAnyTriggeredAbility.java new file mode 100644 index 00000000000..9c8a1237613 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/DealtDamageAnyTriggeredAbility.java @@ -0,0 +1,72 @@ +package mage.abilities.common; + +import mage.abilities.BatchTriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.SetTargetPointer; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.game.Game; +import mage.game.events.DamagedPermanentEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +/** + * @author xenohedron + */ +public class DealtDamageAnyTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { + + private final FilterPermanent filter; + private final SetTargetPointer setTargetPointer; + + public DealtDamageAnyTriggeredAbility(Effect effect, FilterPermanent filter, SetTargetPointer setTargetPointer, boolean optional) { + this(Zone.BATTLEFIELD, effect, filter, setTargetPointer, optional); + } + + public DealtDamageAnyTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, SetTargetPointer setTargetPointer, boolean optional) { + super(zone, effect, optional); + this.filter = filter; + this.setTargetPointer = setTargetPointer; + setTriggerPhrase(getWhen() + CardUtil.addArticle(filter.getMessage()) + " is dealt damage, "); + } + + protected DealtDamageAnyTriggeredAbility(final DealtDamageAnyTriggeredAbility ability) { + super(ability); + this.filter = ability.filter; + this.setTargetPointer = ability.setTargetPointer; + } + + @Override + public DealtDamageAnyTriggeredAbility copy() { + return new DealtDamageAnyTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + // all events in the batch are always relevant if triggers at all + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); + if (permanent == null || !filter.match(permanent, getControllerId(), this, game)) { + return false; + } + getEffects().setValue("damage", event.getAmount()); + switch (setTargetPointer) { + case PERMANENT: + getEffects().setTargetPointer(new FixedTarget(permanent, game)); + return true; + case PLAYER: + getEffects().setTargetPointer(new FixedTarget(permanent.getControllerId())); + return true; + case NONE: + return true; + default: + throw new IllegalArgumentException("Unsupported SetTargetPointer in DealtDamageAttachedTriggeredAbility"); + } + } +} diff --git a/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedTriggeredAbility.java index 91abbc177c2..bf7ed37e2fa 100644 --- a/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedTriggeredAbility.java @@ -1,10 +1,12 @@ package mage.abilities.common; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.constants.SetTargetPointer; import mage.constants.Zone; import mage.game.Game; +import mage.game.events.DamagedPermanentEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; @@ -14,12 +16,12 @@ import java.util.UUID; /** * @author LoneFox */ -public class DealtDamageAttachedTriggeredAbility extends TriggeredAbilityImpl { +public class DealtDamageAttachedTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { - protected SetTargetPointer setTargetPointer; + private final SetTargetPointer setTargetPointer; - public DealtDamageAttachedTriggeredAbility(Effect effect, boolean optional) { - this(Zone.BATTLEFIELD, effect, optional, SetTargetPointer.NONE); + public DealtDamageAttachedTriggeredAbility(Effect effect) { + this(Zone.BATTLEFIELD, effect, false, SetTargetPointer.NONE); } public DealtDamageAttachedTriggeredAbility(Zone zone, Effect effect, boolean optional, SetTargetPointer setTargetPointer) { @@ -45,21 +47,23 @@ public class DealtDamageAttachedTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - Permanent enchantment = game.getPermanent(sourceId); + // all events in the batch are always relevant if triggers at all + Permanent attachment = getSourcePermanentOrLKI(game); UUID targetId = event.getTargetId(); - if (enchantment != null && enchantment.getAttachedTo() != null && targetId.equals(enchantment.getAttachedTo())) { - for (Effect effect : this.getEffects()) { - effect.setValue("damage", event.getAmount()); - switch (setTargetPointer) { - case PERMANENT: - effect.setTargetPointer(new FixedTarget(targetId, game)); - break; - case PLAYER: - effect.setTargetPointer(new FixedTarget(game.getPermanentOrLKIBattlefield(targetId).getControllerId())); - break; - } + if (attachment != null && attachment.getAttachedTo() != null && targetId.equals(attachment.getAttachedTo())) { + getEffects().setValue("damage", event.getAmount()); + switch (setTargetPointer) { + case PERMANENT: + getEffects().setTargetPointer(new FixedTarget(targetId, game)); + return true; + case PLAYER: + getEffects().setTargetPointer(new FixedTarget(attachment.getControllerId())); + return true; + case NONE: + return true; + default: + throw new IllegalArgumentException("Unsupported SetTargetPointer in DealtDamageAttachedTriggeredAbility"); } - return true; } return false; } diff --git a/Mage/src/main/java/mage/abilities/common/DealtDamageToSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealtDamageToSourceTriggeredAbility.java index 28def61a738..198633bec65 100644 --- a/Mage/src/main/java/mage/abilities/common/DealtDamageToSourceTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealtDamageToSourceTriggeredAbility.java @@ -1,17 +1,18 @@ package mage.abilities.common; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.constants.AbilityWord; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedBatchForPermanentsEvent; +import mage.game.events.DamagedPermanentEvent; import mage.game.events.GameEvent; /** * @author LevelX2 */ -public class DealtDamageToSourceTriggeredAbility extends TriggeredAbilityImpl { +public class DealtDamageToSourceTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { public DealtDamageToSourceTriggeredAbility(Effect effect, boolean optional) { this(effect, optional, false); @@ -37,22 +38,16 @@ public class DealtDamageToSourceTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_PERMANENTS; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchForPermanentsEvent dEvent = (DamagedBatchForPermanentsEvent) event; - int damage = dEvent - .getEvents() - .stream() - .filter(damagedEvent -> getSourceId().equals(damagedEvent.getTargetId())) - .mapToInt(GameEvent::getAmount) - .sum(); - if (damage < 1) { + // all events in the batch are always relevant if triggers at all + if (!getSourceId().equals(event.getTargetId())) { return false; } - this.getEffects().setValue("damage", damage); + this.getEffects().setValue("damage", event.getAmount()); return true; } } diff --git a/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreCreaturesTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreCreaturesTriggeredAbility.java deleted file mode 100644 index debd9ca43da..00000000000 --- a/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreCreaturesTriggeredAbility.java +++ /dev/null @@ -1,53 +0,0 @@ -package mage.abilities.common; - -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.Effect; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeBatchEvent; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; - -/** - * @author Cguy7777 - */ -public class DiesOneOrMoreCreaturesTriggeredAbility extends TriggeredAbilityImpl { - - public DiesOneOrMoreCreaturesTriggeredAbility(Effect effect) { - this(effect, false); - } - - public DiesOneOrMoreCreaturesTriggeredAbility(Effect effect, boolean optional) { - super(Zone.BATTLEFIELD, effect, optional); - setTriggerPhrase("Whenever one or more creatures die, "); - } - - private DiesOneOrMoreCreaturesTriggeredAbility(final DiesOneOrMoreCreaturesTriggeredAbility ability) { - super(ability); - } - - @Override - public DiesOneOrMoreCreaturesTriggeredAbility copy() { - return new DiesOneOrMoreCreaturesTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE_BATCH; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - for (ZoneChangeEvent zEvent : ((ZoneChangeBatchEvent) event).getEvents()) { - if (!zEvent.isDiesEvent()) { - continue; - } - Permanent permanent = game.getPermanentOrLKIBattlefield(zEvent.getTargetId()); - if (permanent != null && permanent.isCreature(game)) { - return true; - } - } - return false; - } -} diff --git a/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreCreatureTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreTriggeredAbility.java similarity index 57% rename from Mage/src/main/java/mage/abilities/common/DiesOneOrMoreCreatureTriggeredAbility.java rename to Mage/src/main/java/mage/abilities/common/DiesOneOrMoreTriggeredAbility.java index 7377db0cd3b..655a7d57839 100644 --- a/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreCreatureTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreTriggeredAbility.java @@ -1,6 +1,7 @@ package mage.abilities.common; import mage.MageObject; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.constants.Zone; @@ -9,30 +10,29 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeBatchEvent; import mage.game.events.ZoneChangeEvent; - -import java.util.Objects; +import mage.game.permanent.Permanent; /** * @author Susucr */ -public class DiesOneOrMoreCreatureTriggeredAbility extends TriggeredAbilityImpl { +public class DiesOneOrMoreTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { private final FilterCreaturePermanent filter; - public DiesOneOrMoreCreatureTriggeredAbility(Effect effect, FilterCreaturePermanent filter, boolean optional) { + public DiesOneOrMoreTriggeredAbility(Effect effect, FilterCreaturePermanent filter, boolean optional) { super(Zone.BATTLEFIELD, effect, optional); this.filter = filter; this.setTriggerPhrase("Whenever one or more " + filter.getMessage() + " die, "); } - private DiesOneOrMoreCreatureTriggeredAbility(final DiesOneOrMoreCreatureTriggeredAbility ability) { + private DiesOneOrMoreTriggeredAbility(final DiesOneOrMoreTriggeredAbility ability) { super(ability); this.filter = ability.filter; } @Override - public DiesOneOrMoreCreatureTriggeredAbility copy() { - return new DiesOneOrMoreCreatureTriggeredAbility(this); + public DiesOneOrMoreTriggeredAbility copy() { + return new DiesOneOrMoreTriggeredAbility(this); } @Override @@ -40,16 +40,18 @@ public class DiesOneOrMoreCreatureTriggeredAbility extends TriggeredAbilityImpl return event.getType() == GameEvent.EventType.ZONE_CHANGE_BATCH; } + @Override + public boolean checkEvent(ZoneChangeEvent event, Game game) { + if (!event.isDiesEvent()) { + return false; + } + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); + return permanent != null && filter.match(permanent, getControllerId(), this, game); + } + @Override public boolean checkTrigger(GameEvent event, Game game) { - return ((ZoneChangeBatchEvent) event) - .getEvents() - .stream() - .filter(ZoneChangeEvent::isDiesEvent) - .map(ZoneChangeEvent::getTargetId) - .map(game::getPermanentOrLKIBattlefield) - .filter(Objects::nonNull) - .anyMatch(p -> filter.match(p, getControllerId(), this, game)); + return !getFilteredEvents((ZoneChangeBatchEvent) event, game).isEmpty(); } @Override diff --git a/Mage/src/main/java/mage/abilities/common/EntersBattlefieldOneOrMoreTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/EntersBattlefieldOneOrMoreTriggeredAbility.java index 28b604abceb..093aa5ea88a 100644 --- a/Mage/src/main/java/mage/abilities/common/EntersBattlefieldOneOrMoreTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/EntersBattlefieldOneOrMoreTriggeredAbility.java @@ -1,5 +1,6 @@ package mage.abilities.common; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.constants.TargetController; @@ -8,30 +9,29 @@ import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeBatchEvent; -import mage.players.Player; - -import java.util.UUID; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; /** * "Whenever one or more {filter} enter the battlefield under {target controller} control, * - * @author Alex-Vasile + * @author Alex-Vasile, xenohedron */ -public class EntersBattlefieldOneOrMoreTriggeredAbility extends TriggeredAbilityImpl { +public class EntersBattlefieldOneOrMoreTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { - private final FilterPermanent filterPermanent; + private final FilterPermanent filter; private final TargetController targetController; public EntersBattlefieldOneOrMoreTriggeredAbility(Effect effect, FilterPermanent filter, TargetController targetController) { super(Zone.BATTLEFIELD, effect); - this.filterPermanent = filter; + this.filter = filter; this.targetController = targetController; setTriggerPhrase(generateTriggerPhrase()); } private EntersBattlefieldOneOrMoreTriggeredAbility(final EntersBattlefieldOneOrMoreTriggeredAbility ability) { super(ability); - this.filterPermanent = ability.filterPermanent; + this.filter = ability.filter; this.targetController = ability.targetController; } @@ -41,28 +41,27 @@ public class EntersBattlefieldOneOrMoreTriggeredAbility extends TriggeredAbility } @Override - public boolean checkTrigger(GameEvent event, Game game) { - - Player controller = game.getPlayer(this.controllerId); - if (controller == null) { + public boolean checkEvent(ZoneChangeEvent event, Game game) { + if (event.getToZone() != Zone.BATTLEFIELD) { return false; } + Permanent permanent = event.getTarget(); + if (permanent == null || !filter.match(permanent, getControllerId(), this, game)) { + return false; + } + switch (targetController) { + case YOU: + return isControlledBy(permanent.getControllerId()); + case OPPONENT: + return game.getOpponents(getControllerId()).contains(permanent.getControllerId()); + default: + throw new IllegalArgumentException("Unsupported TargetController in EntersBattlefieldOneOrMoreTriggeredAbility: " + targetController); + } + } - ZoneChangeBatchEvent zEvent = (ZoneChangeBatchEvent) event; - return zEvent.getEvents().stream() - .filter(z -> z.getToZone() == Zone.BATTLEFIELD) - .filter(z -> filterPermanent.match(z.getTarget(), this.controllerId, this, game)) - .anyMatch(z -> { - UUID enteringPermanentControllerID = z.getTarget().getControllerId(); - switch (this.targetController) { - case YOU: - return enteringPermanentControllerID.equals(this.controllerId); - case OPPONENT: - return controller.hasOpponent(enteringPermanentControllerID, game); - default: - throw new IllegalArgumentException("Unsupported target: " + this.targetController); - } - }); + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return !getFilteredEvents((ZoneChangeBatchEvent) event, game).isEmpty(); } @Override @@ -71,10 +70,10 @@ public class EntersBattlefieldOneOrMoreTriggeredAbility extends TriggeredAbility } private String generateTriggerPhrase() { - StringBuilder sb = new StringBuilder("Whenever one or more " + filterPermanent.getMessage()); + StringBuilder sb = new StringBuilder("Whenever one or more " + filter.getMessage()); switch (targetController) { case YOU: - if (filterPermanent.getMessage().contains("you control")) { + if (filter.getMessage().contains("you control")) { sb.append(" enter, "); } else { sb.append(" you control enter, "); @@ -84,7 +83,7 @@ public class EntersBattlefieldOneOrMoreTriggeredAbility extends TriggeredAbility sb.append(" enter under an opponent's control, "); break; default: - throw new IllegalArgumentException("Unsupported TargetController in EntersBattlefieldOneOrMoreTriggeredAbility"); + throw new IllegalArgumentException("Unsupported TargetController in EntersBattlefieldOneOrMoreTriggeredAbility: " + targetController); } return sb.toString(); } diff --git a/Mage/src/main/java/mage/abilities/common/GainLoseLifeYourTurnTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/GainLoseLifeYourTurnTriggeredAbility.java index ecab496079d..1a366c33282 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 class GainLoseLifeYourTurnTriggeredAbility extends TriggeredAbilityImpl { @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 00000000000..4359304f4e5 --- /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 00000000000..e3b4d198b69 --- /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/common/OneOrMoreCombatDamagePlayerTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/OneOrMoreCombatDamagePlayerTriggeredAbility.java new file mode 100644 index 00000000000..f2aa652cfb2 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/OneOrMoreCombatDamagePlayerTriggeredAbility.java @@ -0,0 +1,39 @@ +package mage.abilities.common; + +import mage.abilities.effects.Effect; +import mage.constants.SetTargetPointer; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; + +/** + * @author Xanderhall, xenohedron + */ +public class OneOrMoreCombatDamagePlayerTriggeredAbility extends OneOrMoreDamagePlayerTriggeredAbility { + + public OneOrMoreCombatDamagePlayerTriggeredAbility(Effect effect) { + this(effect, SetTargetPointer.NONE); + } + + public OneOrMoreCombatDamagePlayerTriggeredAbility(Effect effect, FilterCreaturePermanent filter) { + this(Zone.BATTLEFIELD, effect, filter, SetTargetPointer.NONE, false); + } + + public OneOrMoreCombatDamagePlayerTriggeredAbility(Effect effect, SetTargetPointer setTargetPointer) { + this(Zone.BATTLEFIELD, effect, StaticFilters.FILTER_PERMANENT_CREATURES, setTargetPointer, false); + } + + public OneOrMoreCombatDamagePlayerTriggeredAbility(Zone zone, Effect effect, FilterCreaturePermanent filter, + SetTargetPointer setTargetPointer, boolean optional) { + super(zone, effect, filter, true, true, setTargetPointer, optional); + } + + protected OneOrMoreCombatDamagePlayerTriggeredAbility(final OneOrMoreCombatDamagePlayerTriggeredAbility ability) { + super(ability); + } + + @Override + public OneOrMoreCombatDamagePlayerTriggeredAbility copy() { + return new OneOrMoreCombatDamagePlayerTriggeredAbility(this); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/OneOrMoreDealDamageTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/OneOrMoreDamagePlayerTriggeredAbility.java similarity index 59% rename from Mage/src/main/java/mage/abilities/common/OneOrMoreDealDamageTriggeredAbility.java rename to Mage/src/main/java/mage/abilities/common/OneOrMoreDamagePlayerTriggeredAbility.java index c7cf17da6ed..706bf44e5ed 100644 --- a/Mage/src/main/java/mage/abilities/common/OneOrMoreDealDamageTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/OneOrMoreDamagePlayerTriggeredAbility.java @@ -1,5 +1,6 @@ package mage.abilities.common; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.constants.SetTargetPointer; @@ -8,30 +9,30 @@ import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.events.DamagedBatchForOnePlayerEvent; import mage.game.events.DamagedEvent; +import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; import java.util.List; -import java.util.stream.Collectors; /** * @author Xanderhall, xenohedron */ -public class OneOrMoreDealDamageTriggeredAbility extends TriggeredAbilityImpl { +public class OneOrMoreDamagePlayerTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { private final SetTargetPointer setTargetPointer; private final FilterPermanent filter; private final boolean onlyCombat; private final boolean onlyControlled; - public OneOrMoreDealDamageTriggeredAbility(Effect effect, FilterPermanent filter, boolean onlyCombat, boolean onlyControlled) { + public OneOrMoreDamagePlayerTriggeredAbility(Effect effect, FilterPermanent filter, boolean onlyCombat, boolean onlyControlled) { this(Zone.BATTLEFIELD, effect, filter, onlyCombat, onlyControlled, SetTargetPointer.NONE, false); } - public OneOrMoreDealDamageTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, - boolean onlyCombat, boolean onlyControlled, - SetTargetPointer setTargetPointer, boolean optional) { + public OneOrMoreDamagePlayerTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, + boolean onlyCombat, boolean onlyControlled, + SetTargetPointer setTargetPointer, boolean optional) { super(zone, effect, optional); this.setTargetPointer = setTargetPointer; this.filter = filter; @@ -40,7 +41,7 @@ public class OneOrMoreDealDamageTriggeredAbility extends TriggeredAbilityImpl { makeTriggerPhrase(); } - protected OneOrMoreDealDamageTriggeredAbility(final OneOrMoreDealDamageTriggeredAbility ability) { + protected OneOrMoreDamagePlayerTriggeredAbility(final OneOrMoreDamagePlayerTriggeredAbility ability) { super(ability); this.setTargetPointer = ability.setTargetPointer; this.filter = ability.filter; @@ -49,8 +50,8 @@ public class OneOrMoreDealDamageTriggeredAbility extends TriggeredAbilityImpl { } @Override - public OneOrMoreDealDamageTriggeredAbility copy() { - return new OneOrMoreDealDamageTriggeredAbility(this); + public OneOrMoreDamagePlayerTriggeredAbility copy() { + return new OneOrMoreDamagePlayerTriggeredAbility(this); } @Override @@ -59,30 +60,26 @@ public class OneOrMoreDealDamageTriggeredAbility extends TriggeredAbilityImpl { } @Override - public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchForOnePlayerEvent dEvent = (DamagedBatchForOnePlayerEvent) event; - if (onlyCombat && !dEvent.isCombatDamage()) { + public boolean checkEvent(DamagedPlayerEvent event, Game game) { + if (onlyCombat && !event.isCombatDamage()) { return false; } - List events = dEvent - .getEvents() - .stream() - .filter(e -> { - Permanent permanent = game.getPermanentOrLKIBattlefield(e.getSourceId()); - if (permanent == null) { - return false; - } - if (onlyControlled && !permanent.isControlledBy(this.getControllerId())) { - return false; - } - return filter.match(permanent, this.getControllerId(), this, game); - }) - .collect(Collectors.toList()); + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); + if (permanent == null) { + return false; + } + if (onlyControlled && !permanent.isControlledBy(this.getControllerId())) { + return false; + } + return filter.match(permanent, this.getControllerId(), this, game); + } + @Override + public boolean checkTrigger(GameEvent event, Game game) { + List events = getFilteredEvents((DamagedBatchForOnePlayerEvent) event, game); if (events.isEmpty()) { return false; } - this.getAllEffects().setValue("damage", events.stream().mapToInt(DamagedEvent::getAmount).sum()); switch (setTargetPointer) { case PLAYER: @@ -91,9 +88,8 @@ public class OneOrMoreDealDamageTriggeredAbility extends TriggeredAbilityImpl { case NONE: break; default: - throw new IllegalArgumentException("Invalid SetTargetPointer option"); + throw new IllegalArgumentException("Unsupported SetTargetPointer in OneOrMoreDamagePlayerTriggeredAbility"); } - return true; } diff --git a/Mage/src/main/java/mage/abilities/common/OneOrMoreMilledTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/OneOrMoreMilledTriggeredAbility.java index 3f418ddb6d8..300b9f314ff 100644 --- a/Mage/src/main/java/mage/abilities/common/OneOrMoreMilledTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/OneOrMoreMilledTriggeredAbility.java @@ -1,19 +1,21 @@ - package mage.abilities.common; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.dynamicvalue.common.SavedMilledValue; import mage.abilities.effects.Effect; +import mage.cards.Card; import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.MilledBatchAllEvent; +import mage.game.events.MilledCardEvent; /** * @author Susucr */ -public class OneOrMoreMilledTriggeredAbility extends TriggeredAbilityImpl { +public class OneOrMoreMilledTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { private final FilterCard filter; @@ -42,10 +44,16 @@ public class OneOrMoreMilledTriggeredAbility extends TriggeredAbilityImpl { return event.getType() == GameEvent.EventType.MILLED_CARDS_BATCH_FOR_ALL; } + @Override + public boolean checkEvent(MilledCardEvent event, Game game) { + Card card = event.getCard(game); + return card != null && filter.match(card, getControllerId(), this, game); + } + @Override public boolean checkTrigger(GameEvent event, Game game) { - int count = ((MilledBatchAllEvent) event).getCards(game).count(filter, getControllerId(), this, game); - if (count <= 0) { + int count = getFilteredEvents((MilledBatchAllEvent) event, game).size(); + if (count == 0) { return false; } getEffects().setValue(SavedMilledValue.VALUE_KEY, count); diff --git a/Mage/src/main/java/mage/abilities/common/OpponentDealtNoncombatDamageTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/OpponentDealtNoncombatDamageTriggeredAbility.java new file mode 100644 index 00000000000..583168ab04d --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/OpponentDealtNoncombatDamageTriggeredAbility.java @@ -0,0 +1,49 @@ +package mage.abilities.common; + +import mage.abilities.BatchTriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.*; + +/** + * @author xenohedron + */ +public class OpponentDealtNoncombatDamageTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { + + public OpponentDealtNoncombatDamageTriggeredAbility(Effect effect) { + this(Zone.BATTLEFIELD, effect, false); + } + + public OpponentDealtNoncombatDamageTriggeredAbility(Zone zone, Effect effect, boolean optional) { + super(zone, effect, optional); + setTriggerPhrase("Whenever an opponent is dealt noncombat damage, "); + } + + protected OpponentDealtNoncombatDamageTriggeredAbility(final OpponentDealtNoncombatDamageTriggeredAbility ability) { + super(ability); + } + + @Override + public OpponentDealtNoncombatDamageTriggeredAbility copy() { + return new OpponentDealtNoncombatDamageTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + // all events in the batch are always relevant if triggers at all + if (game.getOpponents(getControllerId()).contains(event.getTargetId()) + && !((DamagedBatchForOnePlayerEvent) event).isCombatDamage()) { + this.getAllEffects().setValue("damage", event.getAmount()); + return true; + } + return false; + } + +} diff --git a/Mage/src/main/java/mage/abilities/common/SacrificeOneOrMorePermanentsTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/SacrificeOneOrMorePermanentsTriggeredAbility.java index 59d5d8684d6..eaf5cffe693 100644 --- a/Mage/src/main/java/mage/abilities/common/SacrificeOneOrMorePermanentsTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/SacrificeOneOrMorePermanentsTriggeredAbility.java @@ -1,5 +1,6 @@ package mage.abilities.common; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.constants.SetTargetPointer; @@ -9,16 +10,18 @@ import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.SacrificedPermanentBatchEvent; +import mage.game.events.SacrificedPermanentEvent; import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.targetpointer.FixedTargets; -import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; /** * @author TheElk801, xenohedron */ -public class SacrificeOneOrMorePermanentsTriggeredAbility extends TriggeredAbilityImpl { +public class SacrificeOneOrMorePermanentsTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { private final FilterPermanent filter; private final SetTargetPointer setTargetPointer; @@ -64,31 +67,30 @@ public class SacrificeOneOrMorePermanentsTriggeredAbility extends TriggeredAbili } @Override - public boolean checkTrigger(GameEvent event, Game game) { - ArrayList matchingPermanents = new ArrayList<>(); - for (GameEvent sEvent : ((SacrificedPermanentBatchEvent) event).getEvents()) { - Permanent permanent = game.getPermanentOrLKIBattlefield(sEvent.getTargetId()); - if (permanent != null && filter.match(permanent, getControllerId(), this, game)) { - switch (sacrificingPlayer) { - case YOU: - if (!sEvent.getPlayerId().equals(getControllerId())) { - continue; - } - break; - case OPPONENT: - Player controller = game.getPlayer(getControllerId()); - if (controller == null || !controller.hasOpponent(sEvent.getPlayerId(), game)) { - continue; - } - break; - case ANY: - break; - default: - throw new IllegalArgumentException("Unsupported TargetController in SacrificePermanentTriggeredAbility: " + sacrificingPlayer); - } - matchingPermanents.add(permanent); - } + public boolean checkEvent(SacrificedPermanentEvent event, Game game) { + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); + if (permanent == null || !filter.match(permanent, getControllerId(), this, game)) { + return false; } + switch (sacrificingPlayer) { + case YOU: + return isControlledBy(event.getPlayerId()); + case OPPONENT: + return game.getOpponents(getControllerId()).contains(event.getPlayerId()); + case ANY: + return true; + default: + throw new IllegalArgumentException("Unsupported TargetController in SacrificePermanentTriggeredAbility: " + sacrificingPlayer); } + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + List matchingPermanents = getFilteredEvents((SacrificedPermanentBatchEvent) event, game) + .stream() + .map(GameEvent::getTargetId) + .map(game::getPermanentOrLKIBattlefield) + .filter(Objects::nonNull) + .collect(Collectors.toList()); if (matchingPermanents.isEmpty()) { return false; } diff --git a/Mage/src/main/java/mage/abilities/common/SpellControlledDealsDamageTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/SpellControlledDealsDamageTriggeredAbility.java new file mode 100644 index 00000000000..5d0b0c09e53 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/SpellControlledDealsDamageTriggeredAbility.java @@ -0,0 +1,54 @@ +package mage.abilities.common; + +import mage.abilities.BatchTriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.Zone; +import mage.filter.FilterSpell; +import mage.game.Game; +import mage.game.events.DamagedEvent; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.util.CardUtil; + +/** + * @author xenohedron + */ +public class SpellControlledDealsDamageTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { + + private final FilterSpell filter; + + public SpellControlledDealsDamageTriggeredAbility(Zone zone, Effect effect, FilterSpell filter, boolean optional) { + super(zone, effect, optional); + this.filter = filter; + setTriggerPhrase(getWhen() + CardUtil.addArticle(filter.getMessage()) + " you control deals damage, "); + } + + protected SpellControlledDealsDamageTriggeredAbility(final SpellControlledDealsDamageTriggeredAbility ability) { + super(ability); + this.filter = ability.filter; + } + + @Override + public SpellControlledDealsDamageTriggeredAbility copy() { + return new SpellControlledDealsDamageTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_BY_SOURCE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + // all events in the batch are always relevant if triggers at all + Spell spell = game.getSpellOrLKIStack(event.getSourceId()); + if (spell == null + || !isControlledBy(spell.getControllerId()) + || !filter.match(spell, getControllerId(), this, game)) { + return false; + } + getEffects().setValue("damage", event.getAmount()); + return true; + } +} diff --git a/Mage/src/main/java/mage/abilities/common/YoureDealtDamageTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/YoureDealtDamageTriggeredAbility.java new file mode 100644 index 00000000000..0527a243763 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/YoureDealtDamageTriggeredAbility.java @@ -0,0 +1,49 @@ +package mage.abilities.common; + +import mage.abilities.BatchTriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.DamagedPlayerEvent; +import mage.game.events.GameEvent; + +/** + * @author xenohedron + */ +public class YoureDealtDamageTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { + + public YoureDealtDamageTriggeredAbility(Effect effect, boolean optional) { + this(Zone.BATTLEFIELD, effect, optional); + } + + public YoureDealtDamageTriggeredAbility(Zone zone, Effect effect, boolean optional) { + super(zone, effect, optional); + setTriggerPhrase("Whenever you're dealt damage, "); + } + + protected YoureDealtDamageTriggeredAbility(final YoureDealtDamageTriggeredAbility ability) { + super(ability); + } + + @Override + public YoureDealtDamageTriggeredAbility copy() { + return new YoureDealtDamageTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + // all events in the batch are always relevant + if (isControlledBy(event.getTargetId())) { + this.getAllEffects().setValue("damage", event.getAmount()); + return true; + } + return false; + } + +} 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 00000000000..8df824f6664 --- /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/abilities/keyword/FlankingAbility.java b/Mage/src/main/java/mage/abilities/keyword/FlankingAbility.java index 88504d87b50..1abf74bf3d0 100644 --- a/Mage/src/main/java/mage/abilities/keyword/FlankingAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/FlankingAbility.java @@ -49,7 +49,7 @@ public class FlankingAbility extends TriggeredAbilityImpl { @Override public String getRule() { - return "flanking"; + return "flanking (Whenever a creature without flanking blocks this creature, the blocking creature gets -1/-1 until end of turn.)"; } @Override diff --git a/Mage/src/main/java/mage/designations/Initiative.java b/Mage/src/main/java/mage/designations/Initiative.java index 244ac075bfe..7a23c6043d5 100644 --- a/Mage/src/main/java/mage/designations/Initiative.java +++ b/Mage/src/main/java/mage/designations/Initiative.java @@ -1,6 +1,7 @@ package mage.designations; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.hint.common.CurrentDungeonHint; @@ -8,9 +9,10 @@ import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Controllable; import mage.game.Game; -import mage.game.events.DamagedEvent; -import mage.game.events.DamagedBatchForPlayersEvent; +import mage.game.events.DamagedBatchForOnePlayerEvent; +import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; import java.util.Objects; @@ -41,7 +43,7 @@ public class Initiative extends Designation { } } -class InitiativeDamageTriggeredAbility extends TriggeredAbilityImpl { +class InitiativeDamageTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { InitiativeDamageTriggeredAbility() { super(Zone.ALL, new InitiativeTakeEffect()); @@ -58,17 +60,22 @@ class InitiativeDamageTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_PLAYERS; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; + } + + @Override + public boolean checkEvent(DamagedPlayerEvent event, Game game) { + if (!event.isCombatDamage() || !event.getTargetId().equals(game.getInitiativeId())) { + return false; + } + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); + return permanent != null && permanent.isCreature(game); } @Override public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchForPlayersEvent dEvent = (DamagedBatchForPlayersEvent) event; - UUID playerId = dEvent - .getEvents() + UUID playerId = getFilteredEvents((DamagedBatchForOnePlayerEvent) event, game) .stream() - .filter(DamagedEvent::isCombatDamage) - .filter(e -> e.getTargetId().equals(game.getInitiativeId())) .map(GameEvent::getSourceId) .map(game::getPermanent) .filter(Objects::nonNull) @@ -84,7 +91,7 @@ class InitiativeDamageTriggeredAbility extends TriggeredAbilityImpl { @Override public String getRule() { - return "Whenever one or more creatures a player controls deals combat damage to you, that player takes the initiative."; + return "Whenever one or more creatures a player controls deal combat damage to the player who has the initiative, the controller of those creatures takes the initiative."; } } diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index 4fc323b6ddd..e0c3d41d452 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -845,6 +845,8 @@ public class GameState implements Serializable, Copyable { // DAMAGED_BATCH_FOR_PERMANENTS + DAMAGED_BATCH_FOR_ONE_PERMANENT addSimultaneousDamageToPermanentBatches((DamagedPermanentEvent) damagedEvent, game); } + // DAMAGED_BATCH_BY_SOURCE + addSimultaneousDamageBySourceBatched(damagedEvent, game); // DAMAGED_BATCH_FOR_ALL addSimultaneousDamageToBatchForAll(damagedEvent, game); } @@ -895,6 +897,22 @@ public class GameState implements Serializable, Copyable { } } + public void addSimultaneousDamageBySourceBatched(DamagedEvent damageEvent, Game game) { + // find existing batch first + boolean isBatchUsed = false; + for (GameEvent event : simultaneousEvents) { + if (event instanceof DamagedBatchBySourceEvent + && damageEvent.getSourceId().equals(event.getSourceId())) { + ((DamagedBatchBySourceEvent) event).addEvent(damageEvent); + isBatchUsed = true; + } + } + // new batch if necessary + if (!isBatchUsed) { + addSimultaneousEvent(new DamagedBatchBySourceEvent(damageEvent), game); + } + } + public void addSimultaneousDamageToBatchForAll(DamagedEvent damagedEvent, Game game) { boolean isBatchUsed = false; for (GameEvent event : simultaneousEvents) { @@ -957,12 +975,17 @@ public class GameState implements Serializable, Copyable { // 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; } } @@ -970,6 +993,9 @@ public class GameState implements Serializable, Copyable { 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/command/emblems/LolthSpiderQueenEmblem.java b/Mage/src/main/java/mage/game/command/emblems/LolthSpiderQueenEmblem.java index 50c625e009d..b45be6875a4 100644 --- a/Mage/src/main/java/mage/game/command/emblems/LolthSpiderQueenEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/LolthSpiderQueenEmblem.java @@ -1,7 +1,7 @@ package mage.game.command.emblems; import mage.abilities.Ability; -import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; import mage.abilities.condition.Condition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.Effect; @@ -26,7 +26,7 @@ public final class LolthSpiderQueenEmblem extends Emblem { public LolthSpiderQueenEmblem() { super("Emblem Lolth"); this.getAbilities().add(new ConditionalInterveningIfTriggeredAbility( - new DealCombatDamageControlledTriggeredAbility( + new OneOrMoreCombatDamagePlayerTriggeredAbility( Zone.COMMAND, new LolthSpiderQueenEmblemEffect(), StaticFilters.FILTER_PERMANENT_CREATURES, SetTargetPointer.PLAYER, false ), LolthSpiderQueenEmblemCondition.instance, "Whenever an opponent " + "is dealt combat damage by one or more creatures you control, " + diff --git a/Mage/src/main/java/mage/game/events/BatchEvent.java b/Mage/src/main/java/mage/game/events/BatchEvent.java index f2dcdf76259..704f1f5e66a 100644 --- a/Mage/src/main/java/mage/game/events/BatchEvent.java +++ b/Mage/src/main/java/mage/game/events/BatchEvent.java @@ -39,6 +39,9 @@ public abstract class BatchEvent extends GameEvent { if (firstEvent instanceof BatchEvent) { // sanity check, if you need it then think twice and research carefully throw new UnsupportedOperationException("Wrong code usage: nesting batch events not supported"); } + if (!eventType.isBatch()) { // sanity check, a batch event should use a batch EventType + throw new IllegalArgumentException("Wrong code usage: batch event should use batch EventType: " + eventType); + } this.addEvent(firstEvent); } @@ -50,6 +53,9 @@ public abstract class BatchEvent extends GameEvent { this.singleTargetId = false; this.singleSourceId = false; this.singlePlayerId = false; + if (!eventType.isBatch()) { // sanity check, a batch event should use a batch EventType + throw new IllegalArgumentException("Wrong code usage: batch event should use batch EventType: " + eventType); + } } public void addEvent(T event) { diff --git a/Mage/src/main/java/mage/game/events/DamagedBatchBySourceEvent.java b/Mage/src/main/java/mage/game/events/DamagedBatchBySourceEvent.java new file mode 100644 index 00000000000..90c8bbd2040 --- /dev/null +++ b/Mage/src/main/java/mage/game/events/DamagedBatchBySourceEvent.java @@ -0,0 +1,19 @@ +package mage.game.events; + +/** + * Batch all simultaneous damage events dealt by a single source. + * + * @author Susucr + */ +public class DamagedBatchBySourceEvent extends BatchEvent { + + public DamagedBatchBySourceEvent(DamagedEvent firstEvent) { + super(EventType.DAMAGED_BATCH_BY_SOURCE, false, true, false, firstEvent); + } + + public boolean isCombatDamage() { + return getEvents() + .stream() + .anyMatch(DamagedEvent::isCombatDamage); + } +} diff --git a/Mage/src/main/java/mage/game/events/DamagedEvent.java b/Mage/src/main/java/mage/game/events/DamagedEvent.java index 40778542b75..211be5f1380 100644 --- a/Mage/src/main/java/mage/game/events/DamagedEvent.java +++ b/Mage/src/main/java/mage/game/events/DamagedEvent.java @@ -10,7 +10,7 @@ public abstract class DamagedEvent extends GameEvent { protected boolean combat; protected int excess; - public DamagedEvent(EventType type, UUID targetId, UUID attackerId, UUID playerId, int amount, boolean combat) { + protected DamagedEvent(EventType type, UUID targetId, UUID attackerId, UUID playerId, int amount, boolean combat) { super(type, targetId, null, playerId, amount, false); this.combat = combat; this.excess = 0; @@ -21,10 +21,6 @@ public abstract class DamagedEvent extends GameEvent { return combat; } - public boolean isPreventable() { - return flag; - } - public void setExcess(int excess) { this.excess = Math.max(excess, 0); } @@ -33,7 +29,4 @@ public abstract class DamagedEvent extends GameEvent { return excess; } - public UUID getAttackerId() { - return getSourceId(); - } } diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index 413b2261cc5..90a34cf5ee3 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -73,7 +73,7 @@ public class GameEvent implements Serializable { */ ZONE_CHANGE, ZONE_CHANGE_GROUP, // between two specific zones only; TODO: rework all usages to ZONE_CHANGE_BATCH instead, see #11895 - ZONE_CHANGE_BATCH, // all zone changes that occurred from a single effect + ZONE_CHANGE_BATCH(true), // all zone changes that occurred from a single effect DRAW_TWO_OR_MORE_CARDS, // event calls for multi draws only (if player draws 2+ cards at once) DRAW_CARD, DREW_CARD, EXPLORE, EXPLORED, // targetId is exploring permanent, playerId is its controller @@ -116,11 +116,11 @@ public class GameEvent implements Serializable { combines all MILLED_CARD events for a player milling card at the same time in a single batch playerId the id of the player whose batch it is */ - MILLED_CARDS_BATCH_FOR_ONE_PLAYER, + MILLED_CARDS_BATCH_FOR_ONE_PLAYER(true), /* MILLED_CARDS_BATCH_FOR_ALL, combines all MILLED_CARD events for any player in a single batch */ - MILLED_CARDS_BATCH_FOR_ALL, + MILLED_CARDS_BATCH_FOR_ALL(true), /* DAMAGED_PLAYER targetId the id of the damaged player @@ -134,18 +134,21 @@ public class GameEvent implements Serializable { /* DAMAGED_BATCH_FOR_PLAYERS, combines all player damage events to a single batch (event) */ - DAMAGED_BATCH_FOR_PLAYERS, + DAMAGED_BATCH_FOR_PLAYERS(true), /* DAMAGED_BATCH_FOR_ONE_PLAYER combines all player damage events to a single batch (event) and split it per damaged player targetId the id of the damaged player (playerId won't work for batch) */ - DAMAGED_BATCH_FOR_ONE_PLAYER, - + DAMAGED_BATCH_FOR_ONE_PLAYER(true), + /* DAMAGED_BATCH_BY_SOURCE + combine all damage events from a single source to a single batch (event) + */ + DAMAGED_BATCH_BY_SOURCE(true), /* DAMAGED_BATCH_FOR_ALL includes all damage events, both permanent damage and player damage, in single batch event */ - DAMAGED_BATCH_FOR_ALL, + DAMAGED_BATCH_FOR_ALL(true), /* DAMAGED_BATCH_FIRED * Does not contain any info on damage events, and can fire even when all damage is prevented. * Fire any time a DAMAGED_BATCH_FOR_ALL could have fired (combat & noncombat). @@ -163,7 +166,6 @@ public class GameEvent implements Serializable { 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 @@ -171,10 +173,17 @@ public class GameEvent implements Serializable { amount amount of life loss flag true = from combat damage - other from non combat damage */ - LOST_LIFE_BATCH, + + 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 @@ -426,7 +435,7 @@ public class GameEvent implements Serializable { /* TAPPED_BATCH combine all TAPPED events occuring at the same time in a single event */ - TAPPED_BATCH, + TAPPED_BATCH(true), UNTAP, /* UNTAPPED, targetId untapped permanent @@ -439,7 +448,7 @@ public class GameEvent implements Serializable { /* UNTAPPED_BATCH combine all UNTAPPED events occuring at the same time in a single event */ - UNTAPPED_BATCH, + UNTAPPED_BATCH(true), FLIP, FLIPPED, TRANSFORMING, TRANSFORMED, ADAPT, @@ -494,12 +503,12 @@ public class GameEvent implements Serializable { /* DAMAGED_BATCH_FOR_PERMANENTS combine all permanent damage events to a single batch (event) */ - DAMAGED_BATCH_FOR_PERMANENTS, + DAMAGED_BATCH_FOR_PERMANENTS(true), /* DAMAGED_BATCH_FOR_ONE_PERMANENT combines all permanent damage events to a single batch (event) and split it per damaged permanent */ - DAMAGED_BATCH_FOR_ONE_PERMANENT, + DAMAGED_BATCH_FOR_ONE_PERMANENT(true), DESTROY_PERMANENT, /* DESTROY_PERMANENT_BY_LEGENDARY_RULE @@ -515,7 +524,7 @@ public class GameEvent implements Serializable { flag true if no regeneration is allowed */ DESTROYED_PERMANENT, - SACRIFICE_PERMANENT, SACRIFICED_PERMANENT, SACRIFICED_PERMANENT_BATCH, + SACRIFICE_PERMANENT, SACRIFICED_PERMANENT, SACRIFICED_PERMANENT_BATCH(true), FIGHTED_PERMANENT, BATCH_FIGHT, EXPLOITED_CREATURE, @@ -669,7 +678,21 @@ public class GameEvent implements Serializable { */ GAVE_GIFT, // custom events - must store some unique data to track - CUSTOM_EVENT + CUSTOM_EVENT; + + private final boolean isBatch; + + EventType() { + this(false); + } + + EventType(boolean isBatch) { + this.isBatch = isBatch; + } + + public boolean isBatch() { + return isBatch; + } } public GameEvent(EventType type, UUID targetId, Ability source, UUID playerId) { diff --git a/Mage/src/main/java/mage/game/events/LifeLostBatchEvent.java b/Mage/src/main/java/mage/game/events/LifeLostBatchEvent.java index 80c8f0b0b48..495694d1d67 100644 --- a/Mage/src/main/java/mage/game/events/LifeLostBatchEvent.java +++ b/Mage/src/main/java/mage/game/events/LifeLostBatchEvent.java @@ -1,5 +1,6 @@ package mage.game.events; +import java.util.Collection; import java.util.UUID; /** @@ -11,10 +12,9 @@ public class LifeLostBatchEvent extends BatchEvent { super(EventType.LOST_LIFE_BATCH, false, false, false, firstEvent); } - public int getLifeLostByPlayer(UUID playerID) { - return getEvents() - .stream() - .filter(ev -> ev.getTargetId().equals(playerID)) + public static int getLifeLostByPlayer(Collection events, UUID playerId) { + return events.stream() + .filter(ev -> ev.getTargetId().equals(playerId)) .mapToInt(GameEvent::getAmount) .sum(); } 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 00000000000..fe521928a6d --- /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/game/events/MilledBatchAllEvent.java b/Mage/src/main/java/mage/game/events/MilledBatchAllEvent.java index 2b15385c91e..14fda62410f 100644 --- a/Mage/src/main/java/mage/game/events/MilledBatchAllEvent.java +++ b/Mage/src/main/java/mage/game/events/MilledBatchAllEvent.java @@ -1,12 +1,5 @@ package mage.game.events; -import mage.cards.Cards; -import mage.cards.CardsImpl; -import mage.game.Game; - -import java.util.Objects; -import java.util.stream.Collectors; - /** * @author Susucr */ @@ -16,12 +9,4 @@ public class MilledBatchAllEvent extends BatchEvent { super(EventType.MILLED_CARDS_BATCH_FOR_ALL, false, false, false, event); } - public Cards getCards(Game game) { - return new CardsImpl(getEvents() - .stream() - .map(mce -> mce.getCard(game)) - .filter(Objects::nonNull) - .collect(Collectors.toSet()) - ); - } } diff --git a/Mage/src/main/java/mage/game/events/MilledBatchForOnePlayerEvent.java b/Mage/src/main/java/mage/game/events/MilledBatchForOnePlayerEvent.java index 97dd387a2b6..ab168d9efd2 100644 --- a/Mage/src/main/java/mage/game/events/MilledBatchForOnePlayerEvent.java +++ b/Mage/src/main/java/mage/game/events/MilledBatchForOnePlayerEvent.java @@ -1,12 +1,5 @@ package mage.game.events; -import mage.cards.Cards; -import mage.cards.CardsImpl; -import mage.game.Game; - -import java.util.Objects; -import java.util.stream.Collectors; - /** * @author Susucr */ @@ -16,12 +9,4 @@ public class MilledBatchForOnePlayerEvent extends BatchEvent { super(EventType.MILLED_CARDS_BATCH_FOR_ONE_PLAYER, false, false, true, event); } - public Cards getCards(Game game) { - return new CardsImpl(getEvents() - .stream() - .map(mce -> mce.getCard(game)) - .filter(Objects::nonNull) - .collect(Collectors.toSet()) - ); - } } diff --git a/Mage/src/main/java/mage/game/permanent/token/ShapeshifterDeathtouchToken.java b/Mage/src/main/java/mage/game/permanent/token/ShapeshifterDeathtouchToken.java new file mode 100644 index 00000000000..32826ef9804 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/ShapeshifterDeathtouchToken.java @@ -0,0 +1,35 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.keyword.ChangelingAbility; +import mage.abilities.keyword.DeathtouchAbility; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class ShapeshifterDeathtouchToken extends TokenImpl { + + public ShapeshifterDeathtouchToken() { + this(0); + } + + public ShapeshifterDeathtouchToken(int amount) { + super("Shapeshifter Token", "X/X colorless Shapeshifter creature token with changeling and deathtouch"); + cardType.add(CardType.CREATURE); + subtype.add(SubType.SHAPESHIFTER); + power = new MageInt(amount); + toughness = new MageInt(amount); + addAbility(new ChangelingAbility()); + addAbility(DeathtouchAbility.getInstance()); + } + + private ShapeshifterDeathtouchToken(final ShapeshifterDeathtouchToken token) { + super(token); + } + + public ShapeshifterDeathtouchToken copy() { + return new ShapeshifterDeathtouchToken(this); + } +} diff --git a/Mage/src/main/java/mage/target/targetadjustment/DamagedPlayerControlsTargetAdjuster.java b/Mage/src/main/java/mage/target/targetadjustment/DamagedPlayerControlsTargetAdjuster.java index 921adc6a80f..73694ca6d41 100644 --- a/Mage/src/main/java/mage/target/targetadjustment/DamagedPlayerControlsTargetAdjuster.java +++ b/Mage/src/main/java/mage/target/targetadjustment/DamagedPlayerControlsTargetAdjuster.java @@ -1,6 +1,7 @@ package mage.target.targetadjustment; import mage.abilities.Ability; +import mage.abilities.common.OneOrMoreDamagePlayerTriggeredAbility; import mage.filter.Filter; import mage.filter.predicate.card.OwnerIdPredicate; import mage.filter.predicate.permanent.ControllerIdPredicate; @@ -21,7 +22,7 @@ public class DamagedPlayerControlsTargetAdjuster extends GenericTargetAdjuster { /** * Use with {@link mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility} with setTargetPointer enabled, - * or {@link mage.abilities.common.OneOrMoreDealDamageTriggeredAbility} with "SetTargetPointer.PLAYER" or similar. + * or {@link OneOrMoreDamagePlayerTriggeredAbility} with "SetTargetPointer.PLAYER" or similar. * Adjusts the target to only target something the damaged player controls (or owns with alternative constructor) * And then removes the effects' target pointer that the triggered ability set */ 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 00000000000..2f040b1628c --- /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); + } +}