Skip to content

Commit

Permalink
[MAT] implemented Ob Nixilis, Captive Kingpin, refactored life lose a…
Browse files Browse the repository at this point in the history
…nd batches events (#11974)
  • Loading branch information
jimga150 committed Mar 21, 2024
1 parent 0987e01 commit 50c75f0
Show file tree
Hide file tree
Showing 8 changed files with 424 additions and 2 deletions.
105 changes: 105 additions & 0 deletions Mage.Sets/src/mage/cards/o/ObNixilisCaptiveKingpin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package mage.cards.o;

import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.constants.*;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.TrampleAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.events.*;
import mage.util.CardUtil;

/**
*
* @author jimga150
*/
public final class ObNixilisCaptiveKingpin extends CardImpl {

public ObNixilisCaptiveKingpin(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{R}");

this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.DEMON);
this.power = new MageInt(4);
this.toughness = new MageInt(3);

// Flying
this.addAbility(FlyingAbility.getInstance());

// Trample
this.addAbility(TrampleAbility.getInstance());

// Whenever one or more opponents each lose exactly 1 life, put a +1/+1 counter on Ob Nixilis, Captive Kingpin. Exile the top card of your library. Until your next end step, you may play that card.
Ability ability = new ObNixilisCaptiveKingpinAbility(
new AddCountersSourceEffect(CounterType.P1P1.createInstance())
);
ability.addEffect(new ExileTopXMayPlayUntilEffect(1, Duration.UntilYourNextEndStep)
.withTextOptions("that card", false));

this.addAbility(ability);

}

private ObNixilisCaptiveKingpin(final ObNixilisCaptiveKingpin card) {
super(card);
}

@Override
public ObNixilisCaptiveKingpin copy() {
return new ObNixilisCaptiveKingpin(this);
}
}

class ObNixilisCaptiveKingpinAbility extends TriggeredAbilityImpl {

ObNixilisCaptiveKingpinAbility(Effect effect) {
super(Zone.BATTLEFIELD, effect);
setTriggerPhrase("Whenever one or more opponents each lose exactly 1 life, ");
}

private ObNixilisCaptiveKingpinAbility(final ObNixilisCaptiveKingpinAbility ability) {
super(ability);
}

@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.LOST_LIFE_BATCH;
}

@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;
}
}
return opponentLostLife && allis1;
}

@Override
public ObNixilisCaptiveKingpinAbility copy() {
return new ObNixilisCaptiveKingpinAbility(this);
}
}
1 change: 1 addition & 0 deletions Mage.Sets/src/mage/sets/MarchOfTheMachineTheAftermath.java
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ private MarchOfTheMachineTheAftermath() {
cards.add(new SetCardInfo("Niv-Mizzet, Supreme", 219, Rarity.RARE, mage.cards.n.NivMizzetSupreme.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Niv-Mizzet, Supreme", 40, Rarity.RARE, mage.cards.n.NivMizzetSupreme.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Niv-Mizzet, Supreme", 90, Rarity.RARE, mage.cards.n.NivMizzetSupreme.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Ob Nixilis, Captive Kingpin", 41, Rarity.MYTHIC, mage.cards.o.ObNixilisCaptiveKingpin.class));
cards.add(new SetCardInfo("Open the Way", 123, Rarity.RARE, mage.cards.o.OpenTheWay.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Open the Way", 163, Rarity.RARE, mage.cards.o.OpenTheWay.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Open the Way", 23, Rarity.RARE, mage.cards.o.OpenTheWay.class, NON_FULL_USE_VARIOUS));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package org.mage.test.cards.triggers.damage;

import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.counters.CounterType;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestCommander4Players;

public class ObNixilisCaptiveKingpinTest extends CardTestCommander4Players {

// - 1 opponent dealt 1 damage -> Ob Nixilis triggers
// - 1 opponent dealt 2 damage -> No trigger
// - 2 opponents dealt 1 damage each -> Ob Nixilis triggers
// - 2 opponents dealt 2 damage each -> No trigger
// - opponent pays 1 life-> Ob Nixilis triggers
// - opponent pays 2 life -> No trigger
// - 1 opponent loses 1 life -> Ob Nixilis triggers
// - 1 opponent loses 2 life -> No trigger
// - 2 opponents lose 1 life each -> Ob Nixilis triggers
// - 2 opponents lose 2 life each -> No trigger
// - controller loses 1 life -> No trigger

@Test
public void damageController1Point() {
addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1);
addCard(Zone.BATTLEFIELD, playerD, "Memnite");

attack(2, playerD, "Memnite", playerA);

setStopAt(2, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();

assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 0);
}

@Test
public void damage1Opp1Point() {
addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1);
addCard(Zone.BATTLEFIELD, playerA, "Memnite");

attack(1, playerA, "Memnite", playerB);

setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();

assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 1);
}

@Test
public void damage1Opp2Points() {
addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1);
addCard(Zone.BATTLEFIELD, playerA, "Expedition Envoy");

attack(1, playerA, "Expedition Envoy", playerB);

setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();

assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 0);
}

@Test
public void damage2Opp1Point() {
addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1);
addCard(Zone.BATTLEFIELD, playerA, "Memnite", 2);

attack(1, playerA, "Memnite", playerB);
attack(1, playerA, "Memnite", playerC);

setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();

assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 1);
}

@Test
public void damage2Opp2Points() {
addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1);
addCard(Zone.BATTLEFIELD, playerA, "Expedition Envoy", 2);

attack(1, playerA, "Expedition Envoy", playerB);
attack(1, playerA, "Expedition Envoy", playerC);

setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();

assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 0);
}

@Test
public void payLife1Opp1Point() {
addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1);
addCard(Zone.BATTLEFIELD, playerB, "Arid Mesa");
// addCard(Zone.LIBRARY, playerA, "Mountain");

activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{T}, Pay 1 life");

addTarget(playerB, "Mountain");

setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();

assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 1);
}

@Test
public void payLife1Opp2Point() {
addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1);

addCard(Zone.BATTLEFIELD, playerB, "Forest", 2);

// {2}, Pay 2 life: Draw a card.
addCard(Zone.BATTLEFIELD, playerB, "Book of Rass");

activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{2}, Pay 2 life");

setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();

assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 0);
}

@Test
public void loseLife1Opp1Point() {
addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);

// {1}{B}, {T}: Target player loses 1 life.
addCard(Zone.BATTLEFIELD, playerA, "Acolyte of Xathrid");

addTarget(playerA, playerC);

activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{B}, {T}");

setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();

assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 1);
}

@Test
public void loseLife1Opp2Point() {
addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);

// Target player draws two cards and loses 2 life.
addCard(Zone.HAND, playerA, "Blood Pact");

addTarget(playerA, playerD);

castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Blood Pact");

setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();

assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 0);
}

@Test
public void loseLifeAll1Point() {
addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);

// {2}{W}: Target player gains 1 life.
// {2}{B}: Each player loses 1 life.
addCard(Zone.BATTLEFIELD, playerA, "Orzhov Guildmage");

activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{B}");

setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();

assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 1);
}

@Test
public void loseLifeAll2Point() {
addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4);

// Each player loses 2 life. You draw two cards.
addCard(Zone.HAND, playerA, "Crushing Disappointment");

castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Crushing Disappointment");

setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();

assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 0);
}

}

19 changes: 19 additions & 0 deletions Mage/src/main/java/mage/game/GameState.java
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,25 @@ public boolean hasSimultaneousEvents() {
return !simultaneousEvents.isEmpty();
}

public void addSimultaneousLifeLossEventToBatches(LifeLostEvent lifeLossEvent, Game game) {
// Combine multiple life loss events in the single event (batch)
// see GameEvent.LOST_LIFE_BATCH

// existing batch
boolean isLifeLostBatchUsed = false;
for (GameEvent event : simultaneousEvents) {
if (event instanceof LifeLostBatchEvent) {
((LifeLostBatchEvent) event).addEvent(lifeLossEvent);
isLifeLostBatchUsed = true;
}
}

// new batch
if (!isLifeLostBatchUsed) {
addSimultaneousEvent(new LifeLostBatchEvent(lifeLossEvent), game);
}
}

public void addSimultaneousDamage(DamagedEvent damagedEvent, Game game) {
// Combine multiple damage events in the single event (batch)
// * per damage type (see GameEvent.DAMAGED_BATCH_FOR_PERMANENTS, GameEvent.DAMAGED_BATCH_FOR_PLAYERS)
Expand Down
4 changes: 4 additions & 0 deletions Mage/src/main/java/mage/game/events/GameEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ combines all player damage events to a single batch (event) and split it per dam
amount amount of life loss
flag true = from combat damage - other from non combat damage
*/
LOST_LIFE_BATCH,
/* LOST_LIFE_BATCH
combines all player life lost events to a single batch (event)
*/
PLAY_LAND, LAND_PLAYED,
CREATURE_CHAMPIONED,
/* CREATURE_CHAMPIONED
Expand Down
Loading

0 comments on commit 50c75f0

Please sign in to comment.