diff --git a/Mage.Sets/src/mage/cards/b/BloodHypnotist.java b/Mage.Sets/src/mage/cards/b/BloodHypnotist.java index bd4095a27e3..9dcafb890cf 100644 --- a/Mage.Sets/src/mage/cards/b/BloodHypnotist.java +++ b/Mage.Sets/src/mage/cards/b/BloodHypnotist.java @@ -3,7 +3,7 @@ package mage.cards.b; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.CantBlockAbility; -import mage.abilities.common.SacrificePermanentTriggeredAbility; +import mage.abilities.common.SacrificeOneOrMorePermanentsTriggeredAbility; import mage.abilities.effects.common.combat.CantBlockTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -21,7 +21,7 @@ import java.util.UUID; */ public final class BloodHypnotist extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent(SubType.BLOOD, "one or more Blood tokens"); + private static final FilterPermanent filter = new FilterPermanent(SubType.BLOOD, "Blood tokens"); static { filter.add(TokenPredicate.TRUE); @@ -38,7 +38,7 @@ public final class BloodHypnotist extends CardImpl { this.addAbility(new CantBlockAbility()); // Whenever you sacrifice one or more Blood tokens, target creature can't block this turn. This ability triggers only once each turn. - Ability ability = new SacrificePermanentTriggeredAbility( + Ability ability = new SacrificeOneOrMorePermanentsTriggeredAbility( new CantBlockTargetEffect(Duration.EndOfTurn), filter ).setTriggersLimitEachTurn(1); ability.addTarget(new TargetCreaturePermanent()); diff --git a/Mage.Sets/src/mage/cards/c/CamelliaTheSeedmiser.java b/Mage.Sets/src/mage/cards/c/CamelliaTheSeedmiser.java new file mode 100644 index 00000000000..c38fd5f9ebc --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CamelliaTheSeedmiser.java @@ -0,0 +1,76 @@ +package mage.cards.c; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SacrificeOneOrMorePermanentsTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.ForageCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.constants.*; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.counters.CounterType; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.permanent.token.SquirrelToken; + +/** + * + * @author Grath + */ +public final class CamelliaTheSeedmiser extends CardImpl { + + private static final FilterControlledCreaturePermanent filterSquirrels = + new FilterControlledCreaturePermanent("Squirrels"); + + + static { + filterSquirrels.add(SubType.SQUIRREL.getPredicate()); + filterSquirrels.add(AnotherPredicate.instance); + } + + private static final FilterControlledPermanent filterFood = new FilterControlledPermanent(SubType.FOOD, "Foods"); + + public CamelliaTheSeedmiser(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SQUIRREL); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Menace + this.addAbility(new MenaceAbility(false)); + + // Other Squirrels you control have menace. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD,new GainAbilityControlledEffect( + new MenaceAbility(false), + Duration.WhileOnBattlefield, + filterSquirrels, + true + ))); + // Whenever you sacrifice one or more Foods, create a 1/1 green Squirrel creature token. + this.addAbility(new SacrificeOneOrMorePermanentsTriggeredAbility(new CreateTokenEffect(new SquirrelToken()), filterFood)); + // {2}, Forage: Put a +1/+1 counter on each other Squirrel you control. + Ability ability = new SimpleActivatedAbility(new AddCountersAllEffect(CounterType.P1P1.createInstance(), filterSquirrels), new ManaCostsImpl<>("{2}")); + ability.addCost(new ForageCost()); + this.addAbility(ability); + } + + private CamelliaTheSeedmiser(final CamelliaTheSeedmiser card) { + super(card); + } + + @Override + public CamelliaTheSeedmiser copy() { + return new CamelliaTheSeedmiser(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/ForgeBoss.java b/Mage.Sets/src/mage/cards/f/ForgeBoss.java index 6475f99d5fd..2bdf6c504d5 100644 --- a/Mage.Sets/src/mage/cards/f/ForgeBoss.java +++ b/Mage.Sets/src/mage/cards/f/ForgeBoss.java @@ -1,7 +1,7 @@ package mage.cards.f; import mage.MageInt; -import mage.abilities.common.SacrificePermanentTriggeredAbility; +import mage.abilities.common.SacrificeOneOrMorePermanentsTriggeredAbility; import mage.abilities.effects.common.DamagePlayersEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -18,7 +18,7 @@ import java.util.UUID; */ public final class ForgeBoss extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("one or more other creatures"); + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("other creatures"); static { filter.add(AnotherPredicate.instance); @@ -33,7 +33,7 @@ public final class ForgeBoss extends CardImpl { this.toughness = new MageInt(4); // Whenever you sacrifice one or more other creatures, Forge Boss deals 2 damage to each opponent. This ability triggers only once each turn. - this.addAbility(new SacrificePermanentTriggeredAbility( + this.addAbility(new SacrificeOneOrMorePermanentsTriggeredAbility( new DamagePlayersEffect(2, TargetController.OPPONENT), filter ).setTriggersLimitEachTurn(1)); } diff --git a/Mage.Sets/src/mage/cards/f/ForgeNeverwinterCharlatan.java b/Mage.Sets/src/mage/cards/f/ForgeNeverwinterCharlatan.java index b2d2dfe4267..4bf912ae0cc 100644 --- a/Mage.Sets/src/mage/cards/f/ForgeNeverwinterCharlatan.java +++ b/Mage.Sets/src/mage/cards/f/ForgeNeverwinterCharlatan.java @@ -1,7 +1,7 @@ package mage.cards.f; import mage.MageInt; -import mage.abilities.common.SacrificePermanentTriggeredAbility; +import mage.abilities.common.SacrificeOneOrMorePermanentsTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.dynamicvalue.DynamicValue; @@ -19,6 +19,7 @@ import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.StaticFilters; import mage.filter.common.FilterControlledPermanent; +import mage.filter.common.FilterCreaturePermanent; import mage.game.permanent.token.TreasureToken; import java.util.UUID; @@ -53,10 +54,10 @@ public final class ForgeNeverwinterCharlatan extends CardImpl { ).setText("{this} gets +2/+0 for each Treasure you control")).addHint(hint)); // Whenever one or more players sacrifice one or more creatures, you create a tapped Treasure token. This ability triggers only once each turn. - this.addAbility(new SacrificePermanentTriggeredAbility(Zone.BATTLEFIELD, + this.addAbility(new SacrificeOneOrMorePermanentsTriggeredAbility(Zone.BATTLEFIELD, new CreateTokenEffect(new TreasureToken(), 1, true), - StaticFilters.FILTER_PERMANENT_CREATURE, TargetController.ANY, SetTargetPointer.NONE, false - ).setTriggersLimitEachTurn(1).setTriggerPhrase("Whenever one or more players sacrifice one or more creatures, ")); + new FilterCreaturePermanent("creatures"), TargetController.ANY, SetTargetPointer.NONE, false + ).setTriggersLimitEachTurn(1)); } private ForgeNeverwinterCharlatan(final ForgeNeverwinterCharlatan card) { diff --git a/Mage.Sets/src/mage/sets/Bloomburrow.java b/Mage.Sets/src/mage/sets/Bloomburrow.java index 9ecfd1102cb..184dcbe0d0d 100644 --- a/Mage.Sets/src/mage/sets/Bloomburrow.java +++ b/Mage.Sets/src/mage/sets/Bloomburrow.java @@ -55,6 +55,7 @@ public final class Bloomburrow extends ExpansionSet { cards.add(new SetCardInfo("Byway Barterer", 129, Rarity.RARE, mage.cards.b.BywayBarterer.class)); cards.add(new SetCardInfo("Cache Grab", 167, Rarity.COMMON, mage.cards.c.CacheGrab.class)); cards.add(new SetCardInfo("Calamitous Tide", 43, Rarity.UNCOMMON, mage.cards.c.CalamitousTide.class)); + cards.add(new SetCardInfo("Camellia, the Seedmiser", 207, Rarity.RARE, mage.cards.c.CamelliaTheSeedmiser.class)); cards.add(new SetCardInfo("Caretaker's Talent", 6, Rarity.RARE, mage.cards.c.CaretakersTalent.class)); cards.add(new SetCardInfo("Carrot Cake", 7, Rarity.COMMON, mage.cards.c.CarrotCake.class)); cards.add(new SetCardInfo("Charmed Sleep", 388, Rarity.COMMON, mage.cards.c.CharmedSleep.class)); diff --git a/Mage/src/main/java/mage/abilities/common/SacrificeOneOrMorePermanentsTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/SacrificeOneOrMorePermanentsTriggeredAbility.java new file mode 100644 index 00000000000..d984b6a8046 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/SacrificeOneOrMorePermanentsTriggeredAbility.java @@ -0,0 +1,126 @@ +package mage.abilities.common; + +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.SetTargetPointer; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.SacrificedPermanentBatchEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.targetpointer.FixedTargets; + +import java.util.ArrayList; + +/** + * @author TheElk801, xenohedron + */ +public class SacrificeOneOrMorePermanentsTriggeredAbility extends TriggeredAbilityImpl { + + private final FilterPermanent filter; + private final SetTargetPointer setTargetPointer; + + private final TargetController sacrificingPlayer; + + /** + * Whenever you sacrifice one or more "[filter]", "[effect]". + * zone = battlefield, setTargetPointer = NONE, optional = false + */ + public SacrificeOneOrMorePermanentsTriggeredAbility(Effect effect, FilterPermanent filter) { + this(Zone.BATTLEFIELD, effect, filter, TargetController.YOU, SetTargetPointer.NONE, false); + } + + public SacrificeOneOrMorePermanentsTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, + TargetController sacrificingPlayer, + SetTargetPointer setTargetPointer, boolean optional) { + super(zone, effect, optional); + if (Zone.BATTLEFIELD.match(zone)) { + setLeavesTheBattlefieldTrigger(true); + } + this.filter = filter; + this.setTargetPointer = setTargetPointer; + this.sacrificingPlayer = sacrificingPlayer; + setTriggerPhrase(generateTriggerPhrase()); + } + + protected SacrificeOneOrMorePermanentsTriggeredAbility(final SacrificeOneOrMorePermanentsTriggeredAbility ability) { + super(ability); + this.filter = ability.filter; + this.setTargetPointer = ability.setTargetPointer; + this.sacrificingPlayer = ability.sacrificingPlayer; + } + + @Override + public SacrificeOneOrMorePermanentsTriggeredAbility copy() { + return new SacrificeOneOrMorePermanentsTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.SACRIFICED_PERMANENT_BATCH; + } + + @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); + } + } + if (matchingPermanents.isEmpty()) { + return false; + } + this.getEffects().setValue("sacrificedPermanents", matchingPermanents); + switch (setTargetPointer) { + case PERMANENT: + this.getEffects().setTargetPointer(new FixedTargets(matchingPermanents, game)); + break; + case NONE: + break; + default: + throw new IllegalArgumentException("Unsupported SetTargetPointer in SacrificePermanentTriggeredAbility: " + setTargetPointer); + } + return true; + } + + private String generateTriggerPhrase() { + String targetControllerText; + switch (sacrificingPlayer) { + case YOU: + targetControllerText = "you sacrifice one or more "; + break; + case OPPONENT: + targetControllerText = "an opponent sacrifices one or more "; + break; + case ANY: + targetControllerText = "one or more players sacrifices one or more "; + break; + default: + throw new IllegalArgumentException("Unsupported TargetController in SacrificePermanentTriggeredAbility: " + sacrificingPlayer); + } + return getWhen() + targetControllerText + filter.getMessage() + ", "; + } + +} diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index bc23042c5a7..b8f2f55caa6 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -930,6 +930,24 @@ public class GameState implements Serializable, Copyable { } } + public void addSimultaneousSacrificedPermanentToBatch(GameEvent sacrificedEvent, Game game) { + // Combine multiple sacrificed permanent events in the single event (batch) + + // existing batch + boolean isBatchUsed = false; + for (GameEvent event : simultaneousEvents) { + if (event instanceof SacrificedPermanentBatchEvent) { + ((SacrificedPermanentBatchEvent) event).addEvent(sacrificedEvent); + isBatchUsed = true; + } + } + + // new batch + if (!isBatchUsed) { + addSimultaneousEvent(new SacrificedPermanentBatchEvent(sacrificedEvent), game); + } + } + public void addSimultaneousLifeLossToBatch(LifeLostEvent lifeLossEvent, Game game) { // Combine multiple life loss events in the single event (batch) // see GameEvent.LOST_LIFE_BATCH diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index 3c8cdbdf64b..73d61926f72 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -514,7 +514,7 @@ public class GameEvent implements Serializable { flag true if no regeneration is allowed */ DESTROYED_PERMANENT, - SACRIFICE_PERMANENT, SACRIFICED_PERMANENT, + SACRIFICE_PERMANENT, SACRIFICED_PERMANENT, SACRIFICED_PERMANENT_BATCH, FIGHTED_PERMANENT, BATCH_FIGHT, EXPLOITED_CREATURE, diff --git a/Mage/src/main/java/mage/game/events/SacrificedPermanentBatchEvent.java b/Mage/src/main/java/mage/game/events/SacrificedPermanentBatchEvent.java new file mode 100644 index 00000000000..7740256f76a --- /dev/null +++ b/Mage/src/main/java/mage/game/events/SacrificedPermanentBatchEvent.java @@ -0,0 +1,8 @@ +package mage.game.events; + +public class SacrificedPermanentBatchEvent extends BatchEvent { + + public SacrificedPermanentBatchEvent(GameEvent sacrificedEvent) { + super(EventType.SACRIFICED_PERMANENT_BATCH, false, false, false, sacrificedEvent); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index d5e175f6495..1d985b38f8f 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -1421,7 +1421,9 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { if (player != null) { game.informPlayers(player.getLogName() + " sacrificed " + this.getLogName() + CardUtil.getSourceLogName(game, source)); } - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.SACRIFICED_PERMANENT, objectId, source, controllerId)); + GameEvent sacrificedEvent = GameEvent.getEvent(GameEvent.EventType.SACRIFICED_PERMANENT, objectId, source, controllerId); + game.fireEvent(sacrificedEvent); + game.getState().addSimultaneousSacrificedPermanentToBatch(sacrificedEvent, game); return true; } return false;