(WIP) [SNC] Implemented Ob Nixilis, the Adversary (#8838)

* [SNC] Implemented Ob Nixilis, the Adversary

* Allow starting loyalty to be changed on the stack

Co-authored-by: Evan Kranzler <theelk801@gmail.com>
This commit is contained in:
Daniel Bomar 2022-04-18 21:04:51 -05:00 committed by GitHub
parent bf70d0b675
commit d745141b7b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 233 additions and 1 deletions

View file

@ -0,0 +1,225 @@
package mage.cards.o;
import java.util.List;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.LoyaltyAbility;
import mage.abilities.StaticAbility;
import mage.abilities.common.delayed.ReflexiveTriggeredAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.common.SacrificeTargetCost;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.DrawCardTargetEffect;
import mage.abilities.effects.common.InfoEffect;
import mage.abilities.effects.common.LoseLifeTargetEffect;
import mage.cards.Card;
import mage.constants.*;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.filter.StaticFilters;
import mage.filter.predicate.mageobject.MageObjectReferencePredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.DevilToken;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
import mage.target.TargetPlayer;
import mage.target.common.TargetControlledPermanent;
import mage.util.functions.StackObjectCopyApplier;
/**
*
* @author weirddan455
*/
public final class ObNixilisTheAdversary extends CardImpl {
public ObNixilisTheAdversary(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{1}{B}{R}");
this.addSuperType(SuperType.LEGENDARY);
this.subtype.add(SubType.NIXILIS);
this.setStartingLoyalty(3);
// Casualty X. The copy isn't legendary and has starting loyalty X.
this.addAbility(new ObNixilisTheAdversaryCasualtyAbility(this));
// +1: Each opponent loses 2 life unless they discard a card. If you control a Demon or Devil, you gain 2 life.
this.addAbility(new LoyaltyAbility(new ObNixilisTheAdversaryDiscardEffect(), 1));
// 2: Create a 1/1 red Devil creature token with "When this creature dies, it deals 1 damage to any target."
this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new DevilToken()), -2));
// 7: Target player draws seven cards and loses 7 life.
Ability ability = new LoyaltyAbility(new DrawCardTargetEffect(7), -7);
ability.addEffect(new LoseLifeTargetEffect(7).setText("and loses 7 life"));
ability.addTarget(new TargetPlayer());
this.addAbility(ability);
}
private ObNixilisTheAdversary(final ObNixilisTheAdversary card) {
super(card);
}
@Override
public ObNixilisTheAdversary copy() {
return new ObNixilisTheAdversary(this);
}
}
class ObNixilisTheAdversaryCasualtyAbility extends StaticAbility {
public ObNixilisTheAdversaryCasualtyAbility(Card card) {
super(Zone.ALL, new InfoEffect(
"Casualty X. <i>(As you cast this spell, " +
"you may sacrifice a creature with power X. " +
"When you do, copy this spell. The copy becomes a token.)</i>"
));
card.getSpellAbility().addCost(new ObNixilisTheAdversaryCost());
this.setRuleAtTheTop(true);
}
private ObNixilisTheAdversaryCasualtyAbility(final ObNixilisTheAdversaryCasualtyAbility ability) {
super(ability);
}
@Override
public ObNixilisTheAdversaryCasualtyAbility copy() {
return new ObNixilisTheAdversaryCasualtyAbility(this);
}
}
class ObNixilisTheAdversaryCost extends SacrificeTargetCost {
public ObNixilisTheAdversaryCost() {
super(new TargetControlledPermanent(0, 1, StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT, true));
this.text = "";
}
private ObNixilisTheAdversaryCost(final ObNixilisTheAdversaryCost cost) {
super(cost);
}
@Override
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
if (!super.pay(ability, game, source, controllerId, noMana, costToPay)) {
return false;
}
List<Permanent> sacrificedPermanents = getPermanents();
if (!sacrificedPermanents.isEmpty()) {
StackObjectCopyApplier applier = new ObNixilisTheAdversaryApplier(sacrificedPermanents.get(0).getPower().getValue());
game.fireReflexiveTriggeredAbility(new ReflexiveTriggeredAbility(
new ObNixilisTheAdversaryCopyEffect(applier), false, "when you do, copy this spell"
), source);
}
return true;
}
@Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
return true;
}
@Override
public ObNixilisTheAdversaryCost copy() {
return new ObNixilisTheAdversaryCost(this);
}
}
class ObNixilisTheAdversaryCopyEffect extends OneShotEffect {
private final StackObjectCopyApplier applier;
public ObNixilisTheAdversaryCopyEffect(StackObjectCopyApplier applier) {
super(Outcome.Copy);
this.applier = applier;
this.staticText = "copy {this}";
}
private ObNixilisTheAdversaryCopyEffect(final ObNixilisTheAdversaryCopyEffect effect) {
super(effect);
this.applier = effect.applier;
}
@Override
public ObNixilisTheAdversaryCopyEffect copy() {
return new ObNixilisTheAdversaryCopyEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Spell spell = game.getSpellOrLKIStack(source.getSourceId());
if (spell == null) {
return false;
}
spell.createCopyOnStack(game, source, source.getControllerId(), true, 1, applier);
return true;
}
}
class ObNixilisTheAdversaryApplier implements StackObjectCopyApplier {
private final int loyalty;
public ObNixilisTheAdversaryApplier(int loyalty) {
this.loyalty = loyalty;
}
@Override
public void modifySpell(StackObject stackObject, Game game) {
stackObject.getSuperType().remove(SuperType.LEGENDARY);
stackObject.setStartingLoyalty(loyalty);
}
@Override
public MageObjectReferencePredicate getNextNewTargetType(int copyNumber) {
return null;
}
}
class ObNixilisTheAdversaryDiscardEffect extends OneShotEffect {
public ObNixilisTheAdversaryDiscardEffect() {
super(Outcome.LoseLife);
this.staticText = "Each opponent loses 2 life unless they discard a card. If you control a Demon or Devil, you gain 2 life";
}
private ObNixilisTheAdversaryDiscardEffect(final ObNixilisTheAdversaryDiscardEffect effect) {
super(effect);
}
@Override
public ObNixilisTheAdversaryDiscardEffect copy() {
return new ObNixilisTheAdversaryDiscardEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
UUID controllerId = source.getControllerId();
Player controller = game.getPlayer(controllerId);
if (controller == null) {
return false;
}
for (UUID opponentId : game.getOpponents(controllerId)) {
Player opponent = game.getPlayer(opponentId);
if (opponent == null) {
continue;
}
if (opponent.getHand().isEmpty() || !opponent.chooseUse(
Outcome.Discard, "Discard a card or lose 2 life?",
null, "Discard Card", "Lose 2 Life", source, game)
|| opponent.discard(1, false, false, source, game).isEmpty()) {
opponent.loseLife(2, game, source, false);
}
}
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(controllerId)) {
if (permanent.hasSubtype(SubType.DEMON, game) || permanent.hasSubtype(SubType.DEVIL, game)) {
controller.gainLife(2, game, source);
break;
}
}
return true;
}
}

View file

@ -164,6 +164,7 @@ public final class StreetsOfNewCapenna extends ExpansionSet {
cards.add(new SetCardInfo("Murder", 88, Rarity.COMMON, mage.cards.m.Murder.class));
cards.add(new SetCardInfo("Night Clubber", 89, Rarity.UNCOMMON, mage.cards.n.NightClubber.class));
cards.add(new SetCardInfo("Nimble Larcenist", 205, Rarity.UNCOMMON, mage.cards.n.NimbleLarcenist.class));
cards.add(new SetCardInfo("Ob Nixilis, the Adversary", 206, Rarity.MYTHIC, mage.cards.o.ObNixilisTheAdversary.class));
cards.add(new SetCardInfo("Obscura Ascendancy", 207, Rarity.RARE, mage.cards.o.ObscuraAscendancy.class));
cards.add(new SetCardInfo("Obscura Charm", 208, Rarity.UNCOMMON, mage.cards.o.ObscuraCharm.class));
cards.add(new SetCardInfo("Obscura Initiate", 50, Rarity.COMMON, mage.cards.o.ObscuraInitiate.class));

View file

@ -62,6 +62,7 @@ public class Spell extends StackObjectImpl implements Card {
private boolean countered;
private boolean resolving = false;
private UUID commandedBy = null; // for Word of Command
private int startingLoyalty;
private ActivationManaAbilityStep currentActivatingManaAbilitiesStep = ActivationManaAbilityStep.BEFORE;
@ -78,6 +79,7 @@ public class Spell extends StackObjectImpl implements Card {
this.color = affectedCard.getColor(null).copy();
this.frameColor = affectedCard.getFrameColor(null).copy();
this.frameStyle = affectedCard.getFrameStyle();
this.startingLoyalty = affectedCard.getStartingLoyalty();
this.id = ability.getId();
this.zoneChangeCounter = affectedCard.getZoneChangeCounter(game); // sync card's ZCC with spell (copy spell settings)
this.ability = ability;
@ -131,6 +133,7 @@ public class Spell extends StackObjectImpl implements Card {
this.currentActivatingManaAbilitiesStep = spell.currentActivatingManaAbilitiesStep;
this.targetChanged = spell.targetChanged;
this.startingLoyalty = spell.startingLoyalty;
}
public boolean activate(Game game, boolean noMana) {
@ -654,11 +657,12 @@ public class Spell extends StackObjectImpl implements Card {
@Override
public int getStartingLoyalty() {
return card.getStartingLoyalty();
return this.startingLoyalty;
}
@Override
public void setStartingLoyalty(int startingLoyalty) {
this.startingLoyalty = startingLoyalty;
}
@Override

View file

@ -109,6 +109,8 @@ public class CopyTokenFunction implements Function<Token, Card> {
if (spell != null) {
// copied spell puts to battlefield as token, so that token's ZCC must be synced with spell instead card (card can be moved before resolve)
target.setZoneChangeCounter(spell.getZoneChangeCounter(game), game);
// Copy starting loyalty from spell (Ob Nixilis, the Adversary)
target.setStartingLoyalty(spell.getStartingLoyalty());
} else {
target.setZoneChangeCounter(source.getZoneChangeCounter(game), game);
}