Refactor batch events (#11995)

* create new abstract class for batch event framework

* adjust CardUtil.getEventTargets to support new framework

* update TappedBatchEvent to new framework

* update UntappedBatchEvent to new framework

* slight cleanup

* update LifeLostBatchEvent to new framework

* update ZoneChangeBatchEvent to new framework

* complete refactor by moving damage events to new framework

* remove old code no longer used

* clean up some nonsense code in star wars card

* fix watcher checking id before event type

* fix wrong id usage

* fix missed wrong id usage
This commit is contained in:
xenohedron 2024-03-28 23:19:20 -04:00 committed by GitHub
parent 8869d282b2
commit cb28fb5a56
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 308 additions and 635 deletions

View file

@ -40,7 +40,7 @@ public class BecomesTappedOneOrMoreTriggeredAbility extends TriggeredAbilityImpl
public boolean checkTrigger(GameEvent event, Game game) {
TappedBatchEvent batchEvent = (TappedBatchEvent) event;
return batchEvent
.getTargets()
.getTargetIds()
.stream()
.map(game::getPermanent)
.anyMatch(p -> filter.match(p, getControllerId(), this, game));

View file

@ -4,7 +4,7 @@ import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.DamagedBatchEvent;
import mage.game.events.DamagedBatchAllEvent;
import mage.game.events.DamagedEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
@ -14,21 +14,17 @@ import mage.game.permanent.Permanent;
*/
public class DealsCombatDamageEquippedTriggeredAbility extends TriggeredAbilityImpl {
private boolean usedThisStep;
public DealsCombatDamageEquippedTriggeredAbility(Effect effect) {
this(effect, false);
}
public DealsCombatDamageEquippedTriggeredAbility(Effect effect, boolean optional) {
super(Zone.BATTLEFIELD, effect, optional);
this.usedThisStep = false;
setTriggerPhrase("Whenever equipped creature deals combat damage, ");
}
protected DealsCombatDamageEquippedTriggeredAbility(final DealsCombatDamageEquippedTriggeredAbility ability) {
super(ability);
this.usedThisStep = ability.usedThisStep;
}
@Override
@ -38,23 +34,16 @@ public class DealsCombatDamageEquippedTriggeredAbility extends TriggeredAbilityI
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event instanceof DamagedBatchEvent || event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_PRE;
return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ALL;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_PRE) {
usedThisStep = false; // clear before damage
return false;
}
if (usedThisStep || !(event instanceof DamagedBatchEvent)) {
return false; // trigger only on DamagedEvent and if not yet triggered this step
}
Permanent sourcePermanent = getSourcePermanentOrLKI(game);
if (sourcePermanent == null || sourcePermanent.getAttachedTo() == null) {
return false;
}
int amount = ((DamagedBatchEvent) event)
int amount = ((DamagedBatchAllEvent) event)
.getEvents()
.stream()
.filter(DamagedEvent::isCombatDamage)
@ -64,11 +53,7 @@ public class DealsCombatDamageEquippedTriggeredAbility extends TriggeredAbilityI
if (amount < 1) {
return false;
}
usedThisStep = true;
this.getEffects().setValue("damage", amount);
// TODO: this value saved will not be correct if both permanent and player damaged by a single creature
// Need to rework engine logic to fire a single DamagedBatchEvent including both permanents and players
// Only Sword of Hours is currently affected.
return true;
}
}

View file

@ -4,7 +4,7 @@ import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.DamagedBatchEvent;
import mage.game.events.DamagedBatchAllEvent;
import mage.game.events.DamagedEvent;
import mage.game.events.GameEvent;
@ -17,18 +17,14 @@ import mage.game.events.GameEvent;
*/
public class DealsCombatDamageTriggeredAbility extends TriggeredAbilityImpl {
private boolean usedThisStep;
public DealsCombatDamageTriggeredAbility(Effect effect, boolean optional) {
super(Zone.BATTLEFIELD, effect, optional);
this.usedThisStep = false;
setTriggerPhrase(getWhen() + "{this} deals combat damage, ");
this.replaceRuleText = true;
}
protected DealsCombatDamageTriggeredAbility(final DealsCombatDamageTriggeredAbility ability) {
super(ability);
this.usedThisStep = ability.usedThisStep;
}
@Override
@ -38,33 +34,22 @@ public class DealsCombatDamageTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event instanceof DamagedBatchEvent || event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_PRE;
return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ALL;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_PRE) {
usedThisStep = false; // clear before damage
return false;
}
if (usedThisStep || !(event instanceof DamagedBatchEvent)) {
return false; // trigger only on DamagedEvent and if not yet triggered this step
}
int amount = ((DamagedBatchEvent) event)
int amount = ((DamagedBatchAllEvent) event)
.getEvents()
.stream()
.filter(DamagedEvent::isCombatDamage)
.filter(e -> e.getAttackerId().equals(this.sourceId))
.filter(e -> e.getAttackerId().equals(getSourceId()))
.mapToInt(GameEvent::getAmount)
.sum();
if (amount < 1) {
return false;
}
usedThisStep = true;
this.getEffects().setValue("damage", amount);
// TODO: this value saved will not be correct if both permanent and player damaged by a single creature
// Need to rework engine logic to fire a single DamagedBatchEvent including both permanents and players
// Only Aisha of Sparks and Smoke is currently affected.
return true;
}
}

View file

@ -6,7 +6,7 @@ import mage.constants.SetTargetPointer;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.game.Game;
import mage.game.events.DamagedBatchEvent;
import mage.game.events.DamagedBatchForOnePlayerEvent;
import mage.game.events.DamagedEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
@ -60,7 +60,7 @@ public class OneOrMoreDealDamageTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
DamagedBatchEvent dEvent = (DamagedBatchEvent) event;
DamagedBatchForOnePlayerEvent dEvent = (DamagedBatchForOnePlayerEvent) event;
if (onlyCombat && !dEvent.isCombatDamage()) {
return false;
}
@ -86,7 +86,7 @@ public class OneOrMoreDealDamageTriggeredAbility extends TriggeredAbilityImpl {
this.getAllEffects().setValue("damage", events.stream().mapToInt(DamagedEvent::getAmount).sum());
switch (setTargetPointer) {
case PLAYER:
this.getAllEffects().setTargetPointer(new FixedTarget(event.getPlayerId()));
this.getAllEffects().setTargetPointer(new FixedTarget(event.getTargetId()));
break;
case NONE:
break;

View file

@ -808,7 +808,80 @@ public class GameState implements Serializable, Copyable<GameState> {
return !simultaneousEvents.isEmpty();
}
public void addSimultaneousLifeLossEventToBatches(LifeLostEvent lifeLossEvent, Game game) {
public void addSimultaneousDamage(DamagedEvent damagedEvent, Game game) {
// Combine multiple damage events in the single event (batch)
// Note: one event can be stored in multiple batches
if (damagedEvent instanceof DamagedPlayerEvent) {
// DAMAGED_BATCH_FOR_PLAYERS + DAMAGED_BATCH_FOR_ONE_PLAYER
addSimultaneousDamageToPlayerBatches((DamagedPlayerEvent) damagedEvent, game);
} else if (damagedEvent instanceof DamagedPermanentEvent) {
// DAMAGED_BATCH_FOR_PERMANENTS + DAMAGED_BATCH_FOR_ONE_PERMANENT
addSimultaneousDamageToPermanentBatches((DamagedPermanentEvent) damagedEvent, game);
}
// DAMAGED_BATCH_FOR_ALL
addSimultaneousDamageToBatchForAll(damagedEvent, game);
}
public void addSimultaneousDamageToPlayerBatches(DamagedPlayerEvent damagedPlayerEvent, Game game) {
// find existing batches first
boolean isTotalBatchUsed = false;
boolean isPlayerBatchUsed = false;
for (GameEvent event : simultaneousEvents) {
if (event instanceof DamagedBatchForPlayersEvent) {
((DamagedBatchForPlayersEvent) event).addEvent(damagedPlayerEvent);
isTotalBatchUsed = true;
} else if (event instanceof DamagedBatchForOnePlayerEvent
&& damagedPlayerEvent.getTargetId().equals(event.getTargetId())) {
((DamagedBatchForOnePlayerEvent) event).addEvent(damagedPlayerEvent);
isPlayerBatchUsed = true;
}
}
// new batches if necessary
if (!isTotalBatchUsed) {
addSimultaneousEvent(new DamagedBatchForPlayersEvent(damagedPlayerEvent), game);
}
if (!isPlayerBatchUsed) {
addSimultaneousEvent(new DamagedBatchForOnePlayerEvent(damagedPlayerEvent), game);
}
}
public void addSimultaneousDamageToPermanentBatches(DamagedPermanentEvent damagedPermanentEvent, Game game) {
// find existing batches first
boolean isTotalBatchUsed = false;
boolean isSingleBatchUsed = false;
for (GameEvent event : simultaneousEvents) {
if (event instanceof DamagedBatchForPermanentsEvent) {
((DamagedBatchForPermanentsEvent) event).addEvent(damagedPermanentEvent);
isTotalBatchUsed = true;
} else if (event instanceof DamagedBatchForOnePermanentEvent
&& damagedPermanentEvent.getTargetId().equals(event.getTargetId())) {
((DamagedBatchForOnePermanentEvent) event).addEvent(damagedPermanentEvent);
isSingleBatchUsed = true;
}
}
// new batches if necessary
if (!isTotalBatchUsed) {
addSimultaneousEvent(new DamagedBatchForPermanentsEvent(damagedPermanentEvent), game);
}
if (!isSingleBatchUsed) {
addSimultaneousEvent(new DamagedBatchForOnePermanentEvent(damagedPermanentEvent), game);
}
}
public void addSimultaneousDamageToBatchForAll(DamagedEvent damagedEvent, Game game) {
boolean isBatchUsed = false;
for (GameEvent event : simultaneousEvents) {
if (event instanceof DamagedBatchAllEvent) {
((DamagedBatchAllEvent) event).addEvent(damagedEvent);
isBatchUsed = true;
}
}
if (!isBatchUsed) {
addSimultaneousEvent(new DamagedBatchAllEvent(damagedEvent), game);
}
}
public void addSimultaneousLifeLossToBatch(LifeLostEvent lifeLossEvent, Game game) {
// Combine multiple life loss events in the single event (batch)
// see GameEvent.LOST_LIFE_BATCH
@ -827,69 +900,7 @@ public class GameState implements Serializable, Copyable<GameState> {
}
}
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)
// * per player (see GameEvent.DAMAGED_BATCH_FOR_ONE_PLAYER)
// * per permanent (see GameEvent.DAMAGED_BATCH_FOR_ONE_PERMANENT)
//
// Warning, one event can be stored in multiple batches,
// example: DAMAGED_BATCH_FOR_PLAYERS + DAMAGED_BATCH_FOR_ONE_PLAYER
boolean isPlayerDamage = damagedEvent instanceof DamagedPlayerEvent;
boolean isPermanentDamage = damagedEvent instanceof DamagedPermanentEvent;
// existing batch
boolean isDamageBatchUsed = false;
boolean isPlayerBatchUsed = false;
boolean isPermanentBatchUsed = false;
for (GameEvent event : simultaneousEvents) {
if (isPlayerDamage && event instanceof DamagedBatchForOnePlayerEvent) {
// per player
DamagedBatchForOnePlayerEvent oldPlayerBatch = (DamagedBatchForOnePlayerEvent) event;
if (oldPlayerBatch.getDamageClazz().isInstance(damagedEvent)
&& event.getPlayerId().equals(damagedEvent.getTargetId())) {
oldPlayerBatch.addEvent(damagedEvent);
isPlayerBatchUsed = true;
}
} else if (isPermanentDamage && event instanceof DamagedBatchForOnePermanentEvent) {
// per permanent
DamagedBatchForOnePermanentEvent oldPermanentBatch = (DamagedBatchForOnePermanentEvent) event;
if (oldPermanentBatch.getDamageClazz().isInstance(damagedEvent)
&& CardUtil.getEventTargets(event).contains(damagedEvent.getTargetId())) {
oldPermanentBatch.addEvent(damagedEvent);
isPermanentBatchUsed = true;
}
} else if ((event instanceof DamagedBatchEvent)
&& ((DamagedBatchEvent) event).getDamageClazz().isInstance(damagedEvent)) {
// per damage type
// If the batch event isn't DAMAGED_BATCH_FOR_ONE_PLAYER, the targetIDs need not match,
// since "event" is a generic batch in this case
// (either DAMAGED_BATCH_FOR_PERMANENTS or DAMAGED_BATCH_FOR_PLAYERS)
// Just needs to be a permanent-damaging event for DAMAGED_BATCH_FOR_PERMANENTS,
// or a player-damaging event for DAMAGED_BATCH_FOR_PLAYERS
((DamagedBatchEvent) event).addEvent(damagedEvent);
isDamageBatchUsed = true;
}
}
// new batch
if (!isDamageBatchUsed) {
addSimultaneousEvent(DamagedBatchEvent.makeEvent(damagedEvent), game);
}
if (!isPlayerBatchUsed && isPlayerDamage) {
DamagedBatchEvent event = new DamagedBatchForOnePlayerEvent(damagedEvent);
addSimultaneousEvent(event, game);
}
if (!isPermanentBatchUsed && isPermanentDamage) {
DamagedBatchEvent event = new DamagedBatchForOnePermanentEvent(damagedEvent);
addSimultaneousEvent(event, game);
}
}
public void addSimultaneousTapped(TappedEvent tappedEvent, Game game) {
public void addSimultaneousTappedToBatch(TappedEvent tappedEvent, Game game) {
// Combine multiple tapped events in the single event (batch)
boolean isTappedBatchUsed = false;
@ -904,13 +915,11 @@ public class GameState implements Serializable, Copyable<GameState> {
// new batch
if (!isTappedBatchUsed) {
TappedBatchEvent batch = new TappedBatchEvent();
batch.addEvent(tappedEvent);
addSimultaneousEvent(batch, game);
addSimultaneousEvent(new TappedBatchEvent(tappedEvent), game);
}
}
public void addSimultaneousUntapped(UntappedEvent untappedEvent, Game game) {
public void addSimultaneousUntappedToBatch(UntappedEvent untappedEvent, Game game) {
// Combine multiple untapped events in the single event (batch)
boolean isUntappedBatchUsed = false;
@ -925,9 +934,7 @@ public class GameState implements Serializable, Copyable<GameState> {
// new batch
if (!isUntappedBatchUsed) {
UntappedBatchEvent batch = new UntappedBatchEvent();
batch.addEvent(untappedEvent);
addSimultaneousEvent(batch, game);
addSimultaneousEvent(new UntappedBatchEvent(untappedEvent), game);
}
}

View file

@ -0,0 +1,101 @@
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;
/**
* Special events created by game engine to track batches of events that occur simultaneously,
* for triggers that need such information
* @author xenohedron
*/
public abstract class BatchEvent<T extends GameEvent> extends GameEvent {
private final Set<T> events = new HashSet<>();
private final boolean singleTargetId;
/**
* @param eventType specific type of event
* @param singleTargetId if true, all included events must have same target id
* @param firstEvent added to initialize the batch (batch is never empty)
*/
protected BatchEvent(EventType eventType, boolean singleTargetId, T firstEvent) {
super(eventType, (singleTargetId ? firstEvent.getTargetId() : null), null, null);
this.singleTargetId = singleTargetId;
if (firstEvent instanceof BatchEvent) { // sanity check, if you need it then think twice and research carefully
throw new UnsupportedOperationException("Wrong code usage: nesting batch events not supported");
}
this.addEvent(firstEvent);
}
/**
* For alternate event structure logic used by ZoneChangeBatchEvent, list of events starts empty.
*/
protected BatchEvent(EventType eventType) {
super(eventType, null, null, null);
this.singleTargetId = false;
}
public void addEvent(T event) {
if (singleTargetId && !getTargetId().equals(event.getTargetId())) {
throw new IllegalStateException("Wrong code usage. Batch event initiated with single target id, but trying to add event with different target id");
}
this.events.add(event);
}
public Set<T> getEvents() {
return events;
}
public Set<UUID> getTargetIds() {
return events.stream()
.map(GameEvent::getTargetId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
public Set<UUID> getSourceIds() {
return events.stream()
.map(GameEvent::getSourceId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
public Set<UUID> getPlayerIds() {
return events.stream()
.map(GameEvent::getPlayerId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
@Override
public int getAmount() {
return events
.stream()
.mapToInt(GameEvent::getAmount)
.sum();
}
@Override // events can store a diff value, so search it from events list instead
public UUID getTargetId() {
if (singleTargetId) {
return super.getTargetId();
}
throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list or use CardUtil.getEventTargets(event)");
}
@Override // events can store a diff value, so search it from events list instead
@Deprecated // no use case currently supported
public UUID getSourceId() {
throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list");
}
@Override // events can store a diff value, so search it from events list instead
@Deprecated // no use case currently supported
public UUID getPlayerId() {
throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list");
}
}

View file

@ -1,19 +0,0 @@
package mage.game.events;
import java.util.Set;
import java.util.UUID;
/**
* Game event with batch support (batch is an event that can contain multiple events, example: DAMAGED_BATCH_FOR_PLAYERS)
* <p>
* Used by game engine to support event lifecycle for triggers
*
* @author JayDi85
*/
public interface BatchGameEvent<T extends GameEvent> {
Set<T> getEvents();
Set<UUID> getTargets();
}

View file

@ -0,0 +1,11 @@
package mage.game.events;
/**
* @author xenohedron
*/
public class DamagedBatchAllEvent extends BatchEvent<DamagedEvent> {
public DamagedBatchAllEvent(DamagedEvent firstEvent) {
super(EventType.DAMAGED_BATCH_FOR_ALL, false, firstEvent);
}
}

View file

@ -1,78 +0,0 @@
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 TheElk801
*/
public abstract class DamagedBatchEvent extends GameEvent implements BatchGameEvent<DamagedEvent> {
private final Class<? extends DamagedEvent> damageClazz;
private final Set<DamagedEvent> events = new HashSet<>();
protected DamagedBatchEvent(EventType type, Class<? extends DamagedEvent> damageClazz) {
super(type, null, null, null);
this.damageClazz = damageClazz;
}
@Override
public Set<DamagedEvent> 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 boolean isCombatDamage() {
return events.stream().anyMatch(DamagedEvent::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(DamagedEvent event) {
this.events.add(event);
}
public Class<? extends DamagedEvent> getDamageClazz() {
return damageClazz;
}
public static DamagedBatchEvent makeEvent(DamagedEvent damagedEvent) {
DamagedBatchEvent event;
if (damagedEvent instanceof DamagedPlayerEvent) {
event = new DamagedBatchForPlayersEvent(damagedEvent);
} else if (damagedEvent instanceof DamagedPermanentEvent) {
event = new DamagedBatchForPermanentsEvent(damagedEvent);
} else {
throw new IllegalArgumentException("Wrong code usage. Unknown damage event for a new batch: " + damagedEvent.getClass().getName());
}
return event;
}
}

View file

@ -1,10 +1,8 @@
package mage.game.events;
public class DamagedBatchForOnePermanentEvent extends DamagedBatchEvent {
public class DamagedBatchForOnePermanentEvent extends BatchEvent<DamagedPermanentEvent> {
public DamagedBatchForOnePermanentEvent(DamagedEvent firstEvent) {
super(GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT, DamagedPermanentEvent.class);
addEvent(firstEvent);
setTargetId(firstEvent.getTargetId());
public DamagedBatchForOnePermanentEvent(DamagedPermanentEvent firstEvent) {
super(GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT, true, firstEvent);
}
}

View file

@ -3,11 +3,15 @@ package mage.game.events;
/**
* @author Susucr
*/
public class DamagedBatchForOnePlayerEvent extends DamagedBatchEvent {
public class DamagedBatchForOnePlayerEvent extends BatchEvent<DamagedPlayerEvent> {
public DamagedBatchForOnePlayerEvent(DamagedEvent firstEvent) {
super(EventType.DAMAGED_BATCH_FOR_ONE_PLAYER, DamagedPlayerEvent.class);
setPlayerId(firstEvent.getPlayerId());
addEvent(firstEvent);
public DamagedBatchForOnePlayerEvent(DamagedPlayerEvent firstEvent) {
super(EventType.DAMAGED_BATCH_FOR_ONE_PLAYER, true, firstEvent);
}
public boolean isCombatDamage() {
return getEvents()
.stream()
.anyMatch(DamagedEvent::isCombatDamage);
}
}

View file

@ -3,10 +3,9 @@ package mage.game.events;
/**
* @author TheElk801
*/
public class DamagedBatchForPermanentsEvent extends DamagedBatchEvent {
public class DamagedBatchForPermanentsEvent extends BatchEvent<DamagedPermanentEvent> {
public DamagedBatchForPermanentsEvent(DamagedEvent firstEvent) {
super(EventType.DAMAGED_BATCH_FOR_PERMANENTS, DamagedPermanentEvent.class);
addEvent(firstEvent);
public DamagedBatchForPermanentsEvent(DamagedPermanentEvent firstEvent) {
super(EventType.DAMAGED_BATCH_FOR_PERMANENTS, false, firstEvent);
}
}

View file

@ -3,10 +3,9 @@ package mage.game.events;
/**
* @author TheElk801
*/
public class DamagedBatchForPlayersEvent extends DamagedBatchEvent {
public class DamagedBatchForPlayersEvent extends BatchEvent<DamagedPlayerEvent> {
public DamagedBatchForPlayersEvent(DamagedEvent firstEvent) {
super(GameEvent.EventType.DAMAGED_BATCH_FOR_PLAYERS, DamagedPlayerEvent.class);
addEvent(firstEvent);
public DamagedBatchForPlayersEvent(DamagedPlayerEvent firstEvent) {
super(GameEvent.EventType.DAMAGED_BATCH_FOR_PLAYERS, false, firstEvent);
}
}

View file

@ -120,10 +120,15 @@ public class GameEvent implements Serializable {
/* DAMAGED_BATCH_FOR_ONE_PLAYER
combines all player damage events to a single batch (event) and split it per damaged player
playerId the id of the damaged player
targetId the id of the damaged player (playerId won't work for batch)
*/
DAMAGED_BATCH_FOR_ONE_PLAYER,
/* DAMAGED_BATCH_FOR_ALL
includes all damage events, both permanent damage and player damage, in single batch event
*/
DAMAGED_BATCH_FOR_ALL,
/* DAMAGE_CAUSES_LIFE_LOSS,
targetId the id of the damaged player
sourceId sourceId of the ability which caused the damage, can be null for default events like combat

View file

@ -1,69 +1,22 @@
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> {
public class LifeLostBatchEvent extends BatchEvent<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 LifeLostBatchEvent(LifeLostEvent firstEvent) {
super(EventType.LOST_LIFE_BATCH, false, firstEvent);
}
public int getLifeLostByPlayer(UUID playerID) {
return events
return getEvents()
.stream()
.filter(ev -> ev.getTargetId().equals(playerID))
.mapToInt(GameEvent::getAmount)
.sum();
}
public boolean isLifeLostByCombatDamage() {
return events.stream().anyMatch(LifeLostEvent::isLifeLostByCombatDamage);
}
@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);
}
}

View file

@ -7,13 +7,10 @@ import java.util.UUID;
/**
* @author jimga150
*/
public class LifeLostEvent extends GameEvent{
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);
super(GameEvent.EventType.LOST_LIFE, playerId, source, playerId, amount, atCombat);
}
public boolean isLifeLostByCombatDamage() {
return flag;
}
}

View file

@ -1,56 +1,12 @@
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 Susucr
*/
public class TappedBatchEvent extends GameEvent implements BatchGameEvent<TappedEvent> {
public class TappedBatchEvent extends BatchEvent<TappedEvent> {
private final Set<TappedEvent> events = new HashSet<>();
public TappedBatchEvent() {
super(EventType.TAPPED_BATCH, null, null, null);
public TappedBatchEvent(TappedEvent firstEvent) {
super(EventType.TAPPED_BATCH, false, firstEvent);
}
@Override
public Set<TappedEvent> 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();
}
@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(TappedEvent event) {
this.events.add(event);
}
}

View file

@ -1,56 +1,12 @@
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 Susucr
*/
public class UntappedBatchEvent extends GameEvent implements BatchGameEvent<UntappedEvent> {
public class UntappedBatchEvent extends BatchEvent<UntappedEvent> {
private final Set<UntappedEvent> events = new HashSet<>();
public UntappedBatchEvent() {
super(EventType.UNTAPPED_BATCH, null, null, null);
public UntappedBatchEvent(UntappedEvent firstEvent) {
super(EventType.UNTAPPED_BATCH, false, firstEvent);
}
@Override
public Set<UntappedEvent> 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();
}
@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(UntappedEvent event) {
this.events.add(event);
}
}

View file

@ -6,6 +6,7 @@ import java.util.UUID;
* @author Susucr
*/
public class UntappedEvent extends GameEvent {
public UntappedEvent(UUID targetId, UUID playerId, boolean duringUntapPhase) {
super(EventType.UNTAPPED, targetId, null, playerId, 0, duringUntapPhase);
}

View file

@ -1,54 +1,8 @@
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;
public class ZoneChangeBatchEvent extends GameEvent implements BatchGameEvent<ZoneChangeEvent> {
private final Set<ZoneChangeEvent> events = new HashSet<>();
public class ZoneChangeBatchEvent extends BatchEvent<ZoneChangeEvent> {
public ZoneChangeBatchEvent() {
super(EventType.ZONE_CHANGE_BATCH, null, null, null);
}
@Override
public Set<ZoneChangeEvent> 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();
}
@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(ZoneChangeEvent event) {
this.events.add(event);
super(EventType.ZONE_CHANGE_BATCH);
}
}

View file

@ -599,7 +599,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
game.getTurnStepType() == PhaseStep.UNTAP
);
game.fireEvent(event);
game.getState().addSimultaneousUntapped(event, game);
game.getState().addSimultaneousUntappedToBatch(event, game);
return true;
}
return false;
@ -617,7 +617,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
this.tapped = true;
TappedEvent event = new TappedEvent(objectId, source, source == null ? null : source.getControllerId(), forCombat);
game.fireEvent(event);
game.getState().addSimultaneousTapped(event, game);
game.getState().addSimultaneousTappedToBatch(event, game);
return true;
}
return false;

View file

@ -2177,7 +2177,7 @@ public abstract class PlayerImpl implements Player, Serializable {
if (event.getAmount() > 0) {
LifeLostEvent lifeLostEvent = new LifeLostEvent(playerId, source, event.getAmount(), atCombat);
game.fireEvent(lifeLostEvent);
game.getState().addSimultaneousLifeLossEventToBatches(lifeLostEvent, game);
game.getState().addSimultaneousLifeLossToBatch(lifeLostEvent, game);
}
return event.getAmount();
}

View file

@ -30,7 +30,7 @@ import mage.game.CardState;
import mage.game.Game;
import mage.game.GameState;
import mage.game.command.Commander;
import mage.game.events.BatchGameEvent;
import mage.game.events.BatchEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentCard;
@ -2203,14 +2203,11 @@ public final class CardUtil {
/**
* One single event can be a batch (contain multiple events)
*
* @param event
* @return
*/
public static Set<UUID> getEventTargets(GameEvent event) {
Set<UUID> res = new HashSet<>();
if (event instanceof BatchGameEvent) {
res.addAll(((BatchGameEvent<?>) event).getTargets());
if (event instanceof BatchEvent) {
res.addAll(((BatchEvent<?>) event).getTargetIds());
} else if (event != null && event.getTargetId() != null) {
res.add(event.getTargetId());
}