Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add life lost event and batch event to implement Ob Nixilis, Captive Kingpin #11974

Merged
merged 12 commits into from
Mar 21, 2024
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.getAmount(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
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 addSimultaneousLifeLoss(LifeLostEvent lifeLossEvent, Game game) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current naming logic of all batch methods is wrong - it does not add real events to sim queue, only batches. So it must be renamed to addSimultaneousXxxEventToBatches - damaged, loseLife, tapped, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to rename just this function in this PR for now, unless youre good with renaming the rest in here.

// 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
69 changes: 69 additions & 0 deletions Mage/src/main/java/mage/game/events/LifeLostBatchEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package mage.game.events;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

/**
* @author jimga150
*/
public class LifeLostBatchEvent extends GameEvent implements BatchGameEvent<LifeLostEvent> {

private final Set<LifeLostEvent> events = new HashSet<>();

public LifeLostBatchEvent(LifeLostEvent event) {
super(EventType.LOST_LIFE_BATCH, null, null, null);
addEvent(event);
}

@Override
public Set<LifeLostEvent> getEvents() {
return events;
}

@Override
public Set<UUID> getTargets() {
return events.stream()
.map(GameEvent::getTargetId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}

@Override
public int getAmount() {
return events
.stream()
.mapToInt(GameEvent::getAmount)
.sum();
}

public int getAmount(UUID targetID) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Raname to getLifeLoseByPlayer

return events
.stream()
.filter(ev -> ev.getTargetId().equals(targetID))
.mapToInt(GameEvent::getAmount)
.sum();
}

public boolean isCombatDamage() {
return events.stream().anyMatch(LifeLostEvent::isCombatDamage);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be wrong usage. Related card must use events list or use method like getLifeLoseByCombatDamage(isCombatDamage::Boolaean) > 0, not isCombatDamage.

}

@Override
@Deprecated // events can store a diff value, so search it from events list instead
public UUID getTargetId() {
throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list or use CardUtil.getEventTargets(event)");
}

@Override
@Deprecated // events can store a diff value, so search it from events list instead
public UUID getSourceId() {
throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list.");
}

public void addEvent(LifeLostEvent event) {
this.events.add(event);
}
}
19 changes: 19 additions & 0 deletions Mage/src/main/java/mage/game/events/LifeLostEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package mage.game.events;

import mage.abilities.Ability;

import java.util.UUID;

/**
* @author jimga150
*/
public class LifeLostEvent extends GameEvent{
public LifeLostEvent(UUID playerId, Ability source, int amount, boolean atCombat){
super(GameEvent.EventType.LOST_LIFE,
playerId, source, playerId, amount, atCombat);
}

public boolean isCombatDamage() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be renamed same as the batch event for clarity

(also... is there existing card that cares about it? if not, maybe better to just remove entirely.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, currently no card uses this, but its a short road to walk.

return flag;
}
}
5 changes: 3 additions & 2 deletions Mage/src/main/java/mage/players/PlayerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -2175,8 +2175,9 @@ public int loseLife(int amount, Game game, Ability source, boolean atCombat, UUI
+ (atCombat ? " at combat" : "") + CardUtil.getSourceLogName(game, " from ", needId, "", ""));
}
if (event.getAmount() > 0) {
game.fireEvent(new GameEvent(GameEvent.EventType.LOST_LIFE,
playerId, source, playerId, event.getAmount(), atCombat));
LifeLostEvent lifeLostEvent = new LifeLostEvent(playerId, source, event.getAmount(), atCombat);
game.fireEvent(lifeLostEvent);
game.getState().addSimultaneousLifeLoss(lifeLostEvent, game);
}
return event.getAmount();
}
Expand Down
Loading