Skip to content

Commit

Permalink
add new framework for batch triggers
Browse files Browse the repository at this point in the history
apply for tapped, untapped, sacrificed, milled

simplify Ob Nixilis, Captive Kingpin
  • Loading branch information
xenohedron committed Nov 17, 2024
1 parent 9e9b863 commit d89ec5b
Show file tree
Hide file tree
Showing 12 changed files with 169 additions and 128 deletions.
34 changes: 14 additions & 20 deletions Mage.Sets/src/mage/cards/o/ObNixilisCaptiveKingpin.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -58,7 +60,7 @@ public ObNixilisCaptiveKingpin copy() {
}
}

class ObNixilisCaptiveKingpinAbility extends TriggeredAbilityImpl {
class ObNixilisCaptiveKingpinAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility<LifeLostEvent> {

ObNixilisCaptiveKingpinAbility(Effect effect) {
super(Zone.BATTLEFIELD, effect);
Expand All @@ -75,27 +77,19 @@ public boolean checkEventType(GameEvent event, Game game) {
}

@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;
public boolean checkEvent(LifeLostEvent event, Game game) {
return game.getOpponents(getControllerId()).contains(event.getTargetId());
}

int lifeLost = lifeLostBatchEvent.getLifeLostByPlayer(targetPlayer);
if (lifeLost != 1) {
allis1 = false;
break;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
List<LifeLostEvent> 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
Expand Down
35 changes: 16 additions & 19 deletions Mage.Sets/src/mage/cards/t/TheMillenniumCalendar.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -23,7 +24,6 @@
import mage.game.events.UntappedEvent;
import mage.game.permanent.Permanent;

import java.util.Objects;
import java.util.UUID;

/**
Expand Down Expand Up @@ -61,13 +61,13 @@ public TheMillenniumCalendar copy() {
}
}

class TheMillenniumCalendarTriggeredAbility extends TriggeredAbilityImpl {
class TheMillenniumCalendarTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility<UntappedEvent> {

public TheMillenniumCalendarTriggeredAbility() {
TheMillenniumCalendarTriggeredAbility() {
super(Zone.BATTLEFIELD, null, false);
}

protected TheMillenniumCalendarTriggeredAbility(final TheMillenniumCalendarTriggeredAbility ability) {
private TheMillenniumCalendarTriggeredAbility(final TheMillenniumCalendarTriggeredAbility ability) {
super(ability);
}

Expand All @@ -81,27 +81,24 @@ public boolean checkEventType(GameEvent event, Game game) {
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();
Expand All @@ -121,7 +118,7 @@ public String getRule() {
*/
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"));
Expand Down
14 changes: 11 additions & 3 deletions Mage.Sets/src/mage/cards/z/ZellixSanityFlayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,25 @@

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;
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;

Expand Down Expand Up @@ -60,7 +62,7 @@ public ZellixSanityFlayer copy() {
}
}

class ZellixSanityFlayerTriggeredAbility extends TriggeredAbilityImpl {
class ZellixSanityFlayerTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility<MilledCardEvent> {

ZellixSanityFlayerTriggeredAbility() {
super(Zone.BATTLEFIELD, new CreateTokenEffect(new Horror2Token()));
Expand All @@ -82,8 +84,14 @@ public boolean checkEventType(GameEvent event, Game game) {
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();
}
}
38 changes: 38 additions & 0 deletions Mage/src/main/java/mage/abilities/BatchTriggeredAbility.java
Original file line number Diff line number Diff line change
@@ -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<T extends GameEvent> 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<T> getFilteredEvents(BatchEvent<T> event, Game game) {
return event.getEvents()
.stream()
.filter(e -> checkEvent(e, game))
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
package mage.abilities.common;

import mage.abilities.BatchTriggeredAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
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<TappedEvent> {

protected FilterPermanent filter;
private final FilterPermanent filter;

public BecomesTappedOneOrMoreTriggeredAbility(Zone zone, Effect effect, boolean optional, FilterPermanent filter) {
super(zone, effect, optional);
Expand All @@ -36,13 +39,14 @@ public boolean checkEventType(GameEvent event, Game game) {
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();
}
}
Original file line number Diff line number Diff line change
@@ -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<MilledCardEvent> {

private final FilterCard filter;

Expand Down Expand Up @@ -42,10 +44,16 @@ public boolean checkEventType(GameEvent event, Game game) {
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);
Expand Down
Loading

0 comments on commit d89ec5b

Please sign in to comment.