[DSC] Implement Demonic Covenant

This commit is contained in:
theelk801 2025-04-30 10:16:07 -04:00
parent 51b24a7b8a
commit 563e7fb712
3 changed files with 198 additions and 0 deletions

View file

@ -0,0 +1,129 @@
package mage.cards.d;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.LoseLifeSourceControllerEffect;
import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.Cards;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.Controllable;
import mage.game.Game;
import mage.game.events.DefenderAttackedEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.DemonToken;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.Arrays;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class DemonicCovenant extends CardImpl {
public DemonicCovenant(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.KINDRED, CardType.ENCHANTMENT}, "{4}{B}{B}");
this.subtype.add(SubType.DEMON);
// Whenever one or more Demons you control attack a player, you draw a card and lose 1 life.
this.addAbility(new DemonicCovenantTriggeredAbility());
// At the beginning of your end step, create a 5/5 black Demon creature token with flying, then mill two cards. If two cards that share all their card types were milled this way, sacrifice Demonic Covenant.
Ability ability = new BeginningOfEndStepTriggeredAbility(new CreateTokenEffect(new DemonToken()));
ability.addEffect(new DemonicCovenantEffect());
this.addAbility(ability);
}
private DemonicCovenant(final DemonicCovenant card) {
super(card);
}
@Override
public DemonicCovenant copy() {
return new DemonicCovenant(this);
}
}
class DemonicCovenantTriggeredAbility extends TriggeredAbilityImpl {
DemonicCovenantTriggeredAbility() {
super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1, true));
this.addEffect(new LoseLifeSourceControllerEffect(1).setText("and lose 1 life"));
this.setTriggerPhrase("Whenever one or more Demons you control attack a player, ");
}
private DemonicCovenantTriggeredAbility(final DemonicCovenantTriggeredAbility ability) {
super(ability);
}
@Override
public DemonicCovenantTriggeredAbility copy() {
return new DemonicCovenantTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DEFENDER_ATTACKED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return game.getPlayer(event.getTargetId()) != null
&& ((DefenderAttackedEvent) event)
.getAttackers(game)
.stream()
.filter(permanent -> permanent.hasSubtype(SubType.DEMON, game))
.map(Controllable::getControllerId)
.anyMatch(this::isControlledBy);
}
}
class DemonicCovenantEffect extends OneShotEffect {
DemonicCovenantEffect() {
super(Outcome.Benefit);
staticText = ", then mill two cards. If two cards that share " +
"all their card types were milled this way, sacrifice {this}";
}
private DemonicCovenantEffect(final DemonicCovenantEffect effect) {
super(effect);
}
@Override
public DemonicCovenantEffect copy() {
return new DemonicCovenantEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player == null) {
return false;
}
Cards cards = player.millCards(2, source, game);
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
return cards.size() >= 2
&& permanent != null
&& CardUtil
.checkAnyPairs(
cards.getCards(game),
(c1, c2) -> Arrays
.stream(CardType.values())
.allMatch(cardType -> c1.getCardType(game).contains(cardType)
== c2.getCardType(game).contains(cardType))
)
&& permanent.sacrifice(source, game);
}
}

View file

@ -89,6 +89,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet {
cards.add(new SetCardInfo("Deluge of Doom", 18, Rarity.RARE, mage.cards.d.DelugeOfDoom.class));
cards.add(new SetCardInfo("Demolisher Spawn", 31, Rarity.RARE, mage.cards.d.DemolisherSpawn.class));
cards.add(new SetCardInfo("Demon of Fate's Design", 137, Rarity.RARE, mage.cards.d.DemonOfFatesDesign.class));
cards.add(new SetCardInfo("Demonic Covenant", 19, Rarity.RARE, mage.cards.d.DemonicCovenant.class));
cards.add(new SetCardInfo("Diabolic Vision", 87, Rarity.UNCOMMON, mage.cards.d.DiabolicVision.class));
cards.add(new SetCardInfo("Dig Through Time", 115, Rarity.RARE, mage.cards.d.DigThroughTime.class));
cards.add(new SetCardInfo("Dimir Aqueduct", 270, Rarity.UNCOMMON, mage.cards.d.DimirAqueduct.class));

View file

@ -58,6 +58,9 @@ import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.ToIntFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -2230,6 +2233,71 @@ public final class CardUtil {
return stream.filter(clazz::isInstance).map(clazz::cast).filter(Objects::nonNull);
}
public static <T> boolean checkAnyPairs(Collection<T> collection, BiPredicate<T, T> predicate) {
return streamPairsWithMap(collection, (t1, t2) -> predicate.test(t1, t2)).anyMatch(x -> x);
}
public static <T> Stream<T> streamAllPairwiseMatches(Collection<T> collection, BiPredicate<T, T> predicate) {
return streamPairsWithMap(
collection,
(t1, t2) -> predicate.test(t1, t2)
? Stream.of(t1, t2)
: Stream.<T>empty()
).flatMap(Function.identity()).distinct();
}
private static class IntPairIterator implements Iterator<AbstractMap.SimpleImmutableEntry<Integer, Integer>> {
private final int amount;
private int firstCounter = 0;
private int secondCounter = 1;
IntPairIterator(int amount) {
this.amount = amount;
}
@Override
public boolean hasNext() {
return firstCounter + 1 < amount;
}
@Override
public AbstractMap.SimpleImmutableEntry<Integer, Integer> next() {
AbstractMap.SimpleImmutableEntry<Integer, Integer> value
= new AbstractMap.SimpleImmutableEntry(firstCounter, secondCounter);
secondCounter++;
if (secondCounter == amount) {
firstCounter++;
secondCounter = firstCounter + 1;
}
return value;
}
public int getMax() {
// amount choose 2
return (amount * amount - amount) / 2;
}
}
public static <T, U> Stream<U> streamPairsWithMap(Collection<T> collection, BiFunction<T, T, U> function) {
if (collection.size() < 2) {
return Stream.empty();
}
List<T> list;
if (collection instanceof List) {
list = (List<T>) collection;
} else {
list = new ArrayList<>(collection);
}
IntPairIterator it = new IntPairIterator(list.size());
return Stream
.generate(it::next)
.limit(it.getMax())
.map(pair -> function.apply(
list.get(pair.getKey()),
list.get(pair.getValue())
));
}
public static void AssertNoControllerOwnerPredicates(Target target) {
List<Predicate> list = new ArrayList<>();
Predicates.collectAllComponents(target.getFilter().getPredicates(), target.getFilter().getExtraPredicates(), list);