diff --git a/Mage.Sets/src/mage/cards/j/JaceCunningCastaway.java b/Mage.Sets/src/mage/cards/j/JaceCunningCastaway.java index fbcadcb0dab0..4ec15e73e0f2 100644 --- a/Mage.Sets/src/mage/cards/j/JaceCunningCastaway.java +++ b/Mage.Sets/src/mage/cards/j/JaceCunningCastaway.java @@ -2,9 +2,11 @@ package mage.cards.j; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.LoyaltyAbility; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.CreateTokenCopyTargetEffect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.DrawDiscardControllerEffect; @@ -13,14 +15,15 @@ import mage.constants.*; import mage.game.Game; import mage.game.events.DamagedBatchForOnePlayerEvent; +import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.permanent.token.JaceCunningCastawayIllusionToken; import mage.target.targetpointer.FixedTarget; -import java.util.ArrayList; -import java.util.List; +import java.util.Optional; import java.util.UUID; +import java.util.stream.Stream; /** * @author TheElk801 @@ -36,7 +39,7 @@ public JaceCunningCastaway(UUID ownerId, CardSetInfo setInfo) { this.setStartingLoyalty(3); // +1: Whenever one or more creatures you control deal combat damage to a player this turn, draw a card, then discard a card. - this.addAbility(new LoyaltyAbility(new JaceCunningCastawayEffect1(), 1)); + this.addAbility(new LoyaltyAbility(new CreateDelayedTriggeredAbilityEffect(new JaceCunningCastawayDamageTriggeredAbility()), 1)); // -2: Create a 2/2 blue Illusion creature token with "When this creature becomes the target of a spell, sacrifice it." this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new JaceCunningCastawayIllusionToken()), -2)); @@ -55,33 +58,7 @@ public JaceCunningCastaway copy() { } } -class JaceCunningCastawayEffect1 extends OneShotEffect { - - JaceCunningCastawayEffect1() { - super(Outcome.DrawCard); - this.staticText = "Whenever one or more creatures you control deal combat damage to a player this turn, draw a card, then discard a card"; - } - - private JaceCunningCastawayEffect1(final JaceCunningCastawayEffect1 effect) { - super(effect); - } - - @Override - public JaceCunningCastawayEffect1 copy() { - return new JaceCunningCastawayEffect1(this); - } - - @Override - public boolean apply(Game game, Ability source) { - DelayedTriggeredAbility delayedAbility = new JaceCunningCastawayDamageTriggeredAbility(); - game.addDelayedTriggeredAbility(delayedAbility, source); - return true; - } -} - -class JaceCunningCastawayDamageTriggeredAbility extends DelayedTriggeredAbility { - - private final List damagedPlayerIds = new ArrayList<>(); +class JaceCunningCastawayDamageTriggeredAbility extends DelayedTriggeredAbility implements BatchTriggeredAbility { JaceCunningCastawayDamageTriggeredAbility() { super(new DrawDiscardControllerEffect(1, 1), Duration.EndOfTurn, false); @@ -102,17 +79,24 @@ public boolean checkEventType(GameEvent event, Game game) { } @Override - public boolean checkTrigger(GameEvent event, Game game) { - - DamagedBatchForOnePlayerEvent dEvent = (DamagedBatchForOnePlayerEvent) event; - - int damageFromYours = dEvent.getEvents() + public Stream filterBatchEvent(GameEvent event, Game game) { + return ((DamagedBatchForOnePlayerEvent) event) + .getEvents() .stream() - .filter(ev -> ev.getSourceId().equals(controllerId)) - .mapToInt(GameEvent::getAmount) - .sum(); + .filter(DamagedPlayerEvent::isCombatDamage) + .filter(e -> Optional + .of(e) + .map(DamagedPlayerEvent::getSourceId) + .map(game::getPermanentOrLKIBattlefield) + .filter(p -> p.isCreature(game)) + .filter(p -> p.isControlledBy(getControllerId())) + .isPresent()) + .filter(e -> e.getAmount() > 0); + } - return dEvent.isCombatDamage() && damageFromYours > 0; + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return filterBatchEvent(event, game).findAny().isPresent(); } @Override diff --git a/Mage.Sets/src/mage/cards/t/TamiyoFieldResearcher.java b/Mage.Sets/src/mage/cards/t/TamiyoFieldResearcher.java index e752c317d7bd..53c019d06dd8 100644 --- a/Mage.Sets/src/mage/cards/t/TamiyoFieldResearcher.java +++ b/Mage.Sets/src/mage/cards/t/TamiyoFieldResearcher.java @@ -3,6 +3,7 @@ import mage.MageObjectReference; import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.LoyaltyAbility; import mage.abilities.effects.OneShotEffect; @@ -12,29 +13,26 @@ import mage.abilities.effects.common.TapTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.Outcome; +import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.command.emblems.TamiyoFieldResearcherEmblem; +import mage.game.events.DamagedBatchBySourceEvent; import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.UUID; -import mage.constants.SuperType; +import java.util.stream.Stream; /** - * * @author LevelX2 */ public final class TamiyoFieldResearcher extends CardImpl { @@ -113,7 +111,10 @@ public boolean apply(Game game, Ability source) { } } -class TamiyoFieldResearcherDelayedTriggeredAbility extends DelayedTriggeredAbility { +// batch per source: +// > If Tamiyo’s first ability targets two creatures, and both deal combat damage at the same time, the delayed triggered ability triggers twice. +// > (2016-08-23) +class TamiyoFieldResearcherDelayedTriggeredAbility extends DelayedTriggeredAbility implements BatchTriggeredAbility { private int startingTurn; private List creatures; @@ -132,18 +133,27 @@ private TamiyoFieldResearcherDelayedTriggeredAbility(final TamiyoFieldResearcher @Override public boolean checkEventType(GameEvent event, Game game) { - return event instanceof DamagedEvent; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_BY_SOURCE; + } + + @Override + public Stream filterBatchEvent(GameEvent event, Game game) { + return ((DamagedBatchBySourceEvent) event) + .getEvents() + .stream() + .filter(DamagedEvent::isCombatDamage) + .filter(e -> Optional + .of(e) + .map(DamagedEvent::getSourceId) + .map(id -> new MageObjectReference(id, game)) + .filter(mor -> creatures.contains(mor)) + .isPresent()) + .filter(e -> e.getAmount() > 0); } @Override public boolean checkTrigger(GameEvent event, Game game) { - if (((DamagedEvent) event).isCombatDamage()) { - Permanent damageSource = game.getPermanent(event.getSourceId()); - if (damageSource != null) { - return creatures.contains(new MageObjectReference(damageSource, game)); - } - } - return false; + return filterBatchEvent(event, game).findAny().isPresent(); } @Override diff --git a/Mage.Sets/src/mage/cards/v/VesselOfTheAllConsuming.java b/Mage.Sets/src/mage/cards/v/VesselOfTheAllConsuming.java index 31987548af94..0916c4a753fb 100644 --- a/Mage.Sets/src/mage/cards/v/VesselOfTheAllConsuming.java +++ b/Mage.Sets/src/mage/cards/v/VesselOfTheAllConsuming.java @@ -17,7 +17,6 @@ import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; -import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.watchers.Watcher; @@ -25,8 +24,8 @@ import java.util.AbstractMap; import java.util.HashMap; import java.util.Map; -import java.util.UUID; import java.util.Map.Entry; +import java.util.UUID; /** * @author TheElk801 @@ -90,7 +89,8 @@ public VesselOfTheAllConsumingTriggeredAbility copy() { @Override public boolean checkEventType(GameEvent event, Game game) { - return event instanceof DamagedEvent; + return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT + || event.getType() == GameEvent.EventType.DAMAGED_PLAYER; } @Override diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/TamiyoFieldResearcherTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/TamiyoFieldResearcherTest.java new file mode 100644 index 000000000000..591770356e44 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/TamiyoFieldResearcherTest.java @@ -0,0 +1,255 @@ + +package org.mage.test.cards.single.emn; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * {@link mage.cards.t.TamiyoFieldResearcher Tamiyo, Field Researcher} + * {1}{G}{W}{U} + * 4 loyalty + * +1: Choose up to two target creatures. Until your next turn, whenever either of those creatures deals combat damage, you draw a card. + * −2: Tap up to two target nonland permanents. They don't untap during their controller's next untap step. + * −7: Draw three cards. You get an emblem with "You may cast nonland cards from your hand without paying their mana costs." + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class TamiyoFieldResearcherTest extends CardTestPlayerBase { + + /** + * Reported bug: I activated Tamiyo's +1 ability on a 5/5 Gideon and his 2/2 Knight Ally, but when they both attacked + * and dealt damage I only drew one card when I'm pretty sure I was supposed to draw for each of the two. + */ + @Test + public void testFieldResearcherFirstEffectOnGideon() { + addCard(Zone.BATTLEFIELD, playerA, "Tamiyo, Field Researcher", 1); + + /* Gideon, Ally of Zendikar {2}{W}{W} - 4 loyalty + * +1: Until end of turn, Gideon, Ally of Zendikar becomes a 5/5 Human Soldier Ally creature with indestructible + * that's still a planeswalker. Prevent all damage that would be dealt to him this turn. + * 0: Create a 2/2 white Knight Ally creature token. + **/ + addCard(Zone.BATTLEFIELD, playerA, "Gideon, Ally of Zendikar", 1); + + // put 2/2 knight ally token on battlefield + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "0: Create a"); + + // next, activate Gideon to make him a 5/5 human soldier ally creature + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Until end of turn"); + waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN); + // finally, use Tamiyo +1 on both creatures + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two"); + addTarget(playerA, "Knight Ally Token^Gideon, Ally of Zendikar"); // both token and Gideon as creature + + // attack with both unblocked + attack(3, playerA, "Knight Ally Token"); + attack(3, playerA, "Gideon, Ally of Zendikar"); + + setStopAt(3, PhaseStep.END_COMBAT); + execute(); + + assertLife(playerB, 13); // 5 + 2 damage, 20 - 7 = 13 + assertPermanentCount(playerA, "Tamiyo, Field Researcher", 1); + assertPermanentCount(playerA, "Gideon, Ally of Zendikar", 1); + assertPermanentCount(playerA, "Knight Ally Token", 1); + assertHandCount(playerA, 3); // two cards drawn from each creature dealing damage + 1 card drawn on turn + } + + /** + * Testing more basic scenario with Tamiyo, Field of Researcher +1 effect + */ + @Test + public void testFieldResearcherFirstEffectSimpleCreatureAttacks() { + addCard(Zone.HAND, playerA, "Tamiyo, Field Researcher", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, "Bronze Sable", 1); // 2/1 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tamiyo, Field Researcher", true); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two"); + addTarget(playerA, "Bronze Sable"); + + attack(1, playerA, "Bronze Sable"); + + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + + assertLife(playerB, 18); + assertHandCount(playerA, 1); + } + + /** + * Testing more basic scenario with Tamiyo, Field of Researcher +1 effect + */ + @Test + public void testFieldResearcherFirstEffectSimpleCreaturesAttacks() { + addCard(Zone.HAND, playerA, "Tamiyo, Field Researcher", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, "Bronze Sable", 1); // 2/1 + addCard(Zone.BATTLEFIELD, playerA, "Sylvan Advocate", 1); // 2/3 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tamiyo, Field Researcher", true); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two"); + addTarget(playerA, "Bronze Sable^Sylvan Advocate"); + + attack(1, playerA, "Bronze Sable"); + attack(1, playerA, "Sylvan Advocate"); + + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + + assertLife(playerB, 16); + assertHandCount(playerA, 2); + } + + /** + * Testing more basic scenarios with Tamiyo, Field of Researcher +1 effect + */ + @Test + public void testFieldResearcherFirstEffectAttackAndBlock() { + addCard(Zone.HAND, playerA, "Tamiyo, Field Researcher", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, "Sylvan Advocate", 1); // 2/3 + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tamiyo, Field Researcher", true); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two"); + addTarget(playerA, "Sylvan Advocate"); + + attack(1, playerA, "Sylvan Advocate"); + attack(2, playerB, "Memnite"); + block(2, playerA, "Sylvan Advocate", "Memnite"); + + setStopAt(2, PhaseStep.END_COMBAT); + execute(); + + assertLife(playerB, 18); + assertHandCount(playerA, 2); // Sylvan Advocate dealt combat damage twice + } + + /** + * Reported bug: Tamiyo's +1 ability remains on the creature for the entirety of the game. + */ + @Test + public void testFieldResearcherFirstEffectOnlyPersistsUntilYourNextTurn() { + addCard(Zone.HAND, playerA, "Tamiyo, Field Researcher", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, "Sylvan Advocate", 1); // 2/3 + + addCard(Zone.HAND, playerB, "Hero's Downfall", 1); + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1); + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 3); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tamiyo, Field Researcher", true); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two"); + addTarget(playerA, "Sylvan Advocate"); + + attack(1, playerA, "Sylvan Advocate"); + + attack(2, playerB, "Memnite"); + block(2, playerA, "Sylvan Advocate", "Memnite"); + + castSpell(3, PhaseStep.UPKEEP, playerB, "Hero's Downfall"); + addTarget(playerB, "Tamiyo, Field Researcher"); + + attack(3, playerA, "Sylvan Advocate"); // should not get extra card + + setStopAt(3, PhaseStep.END_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Tamiyo, Field Researcher", 1); + assertGraveyardCount(playerB, "Hero's Downfall", 1); + assertLife(playerB, 16); + assertHandCount(playerA, 3); // 2 cards drawn from Advocate + 1 card during T3 draw step. + } + + /** + * I activated his +1 ability once. then, the next turn, i activated it one more time, and then + * i get to draw 3 cards of three creatures. So i think the first activation wasn't away. + */ + @Test + public void testDrawEffectGetsRemoved() { + addCard(Zone.HAND, playerA, "Tamiyo, Field Researcher", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, "Sylvan Advocate", 1); // 2/3 + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); // 2/2 + addCard(Zone.BATTLEFIELD, playerA, "Pillarfield Ox", 1); // 2/4 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tamiyo, Field Researcher", true); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two", "Sylvan Advocate"); + + attack(1, playerA, "Sylvan Advocate"); + + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two", "Pillarfield Ox^Silvercoat Lion"); + + attack(3, playerA, "Pillarfield Ox"); + attack(3, playerA, "Silvercoat Lion"); + + setStopAt(3, PhaseStep.END_COMBAT); + execute(); + + assertPermanentCount(playerA, "Tamiyo, Field Researcher", 1); + assertLife(playerB, 14); + assertHandCount(playerA, 4); // 3 cards drawn from attackers + 1 card during T3 draw step. + } + + @Test + public void testFieldResearcherFirstAbilityTargetOpponentCreature() { + addCard(Zone.HAND, playerA, "Tamiyo, Field Researcher", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + + addCard(Zone.BATTLEFIELD, playerB, "Bronze Sable", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tamiyo, Field Researcher", true); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two"); + addTarget(playerA, "Bronze Sable"); + + attack(2, playerB, "Bronze Sable"); + + setStopAt(2, PhaseStep.END_COMBAT); + execute(); + + assertLife(playerA, 18); + assertHandCount(playerA, 1); + } + + @Test + public void testFieldResearcherFirstAbilityTargetOpponentCreatures() { + addCard(Zone.HAND, playerA, "Tamiyo, Field Researcher", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + + addCard(Zone.BATTLEFIELD, playerB, "Bronze Sable", 1); + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tamiyo, Field Researcher", true); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two"); + addTarget(playerA, "Bronze Sable^Memnite"); + + attack(2, playerB, "Bronze Sable"); + attack(2, playerB, "Memnite"); + + setStopAt(2, PhaseStep.END_COMBAT); + execute(); + + assertLife(playerA, 17); + assertHandCount(playerA, 2); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/xln/JaceCunningCastawayTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/xln/JaceCunningCastawayTest.java new file mode 100644 index 000000000000..439eb0e674e4 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/xln/JaceCunningCastawayTest.java @@ -0,0 +1,44 @@ +package org.mage.test.cards.single.xln; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class JaceCunningCastawayTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.j.JaceCunningCastaway Jace, Cunning Castaway} {1}{U}{U} + * Legendary Planeswalker — Jace + * +1: Whenever one or more creatures you control deal combat damage to a player this turn, draw a card, then discard a card. + * −2: Create a 2/2 blue Illusion creature token with “When this creature becomes the target of a spell, sacrifice it.” + * −5: Create two tokens that are copies of Jace, Cunning Castaway, except they’re not legendary. + * Loyalty: 3 + */ + private static final String jace = "Jace, Cunning Castaway"; + + @Test + public void test_PlusOne_Trigger() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, jace); + addCard(Zone.BATTLEFIELD, playerA, "Savannah Lions"); + addCard(Zone.BATTLEFIELD, playerA, "Alaborn Trooper"); + addCard(Zone.LIBRARY, playerA, "Taiga"); // for looting. + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1:"); + + attack(1, playerA, "Savannah Lions", playerB); + attack(1, playerA, "Alaborn Trooper", playerB); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerB, 20 - 2 - 2); + assertGraveyardCount(playerA, "Taiga", 1); + } +} diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index 8b00464843e3..ceae115bb811 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -838,6 +838,8 @@ public void addSimultaneousDamage(DamagedEvent damagedEvent, Game game) { // DAMAGED_BATCH_FOR_PERMANENTS + DAMAGED_BATCH_FOR_ONE_PERMANENT addSimultaneousDamageToPermanentBatches((DamagedPermanentEvent) damagedEvent, game); } + // DAMAGED_BATCH_BY_SOURCE + addSimultaneousDamageBySourceBatched(damagedEvent, game); // DAMAGED_BATCH_FOR_ALL addSimultaneousDamageToBatchForAll(damagedEvent, game); } @@ -888,6 +890,22 @@ public void addSimultaneousDamageToPermanentBatches(DamagedPermanentEvent damage } } + public void addSimultaneousDamageBySourceBatched(DamagedEvent damageEvent, Game game) { + // find existing batch first + boolean isBatchUsed = false; + for (GameEvent event : simultaneousEvents) { + if (event instanceof DamagedBatchBySourceEvent + && damageEvent.getSourceId().equals(event.getSourceId())) { + ((DamagedBatchBySourceEvent) event).addEvent(damageEvent); + isBatchUsed = true; + } + } + // new batch if necessary + if (!isBatchUsed) { + addSimultaneousEvent(new DamagedBatchBySourceEvent(damageEvent), game); + } + } + public void addSimultaneousDamageToBatchForAll(DamagedEvent damagedEvent, Game game) { boolean isBatchUsed = false; for (GameEvent event : simultaneousEvents) { diff --git a/Mage/src/main/java/mage/game/events/DamagedBatchBySourceEvent.java b/Mage/src/main/java/mage/game/events/DamagedBatchBySourceEvent.java new file mode 100644 index 000000000000..31918ff62868 --- /dev/null +++ b/Mage/src/main/java/mage/game/events/DamagedBatchBySourceEvent.java @@ -0,0 +1,19 @@ +package mage.game.events; + +/** + * Batch all simultaneous damage events dealt by a single source. + * + * @author Susucr + */ +public class DamagedBatchBySourceEvent extends BatchEvent { + + public DamagedBatchBySourceEvent(DamagedEvent firstEvent) { + super(EventType.DAMAGED_BATCH_BY_SOURCE, false, true, firstEvent); + } + + public boolean isCombatDamage() { + return getEvents() + .stream() + .anyMatch(DamagedEvent::isCombatDamage); + } +} diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index d741fffd47f4..23e317b82cd1 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -124,7 +124,10 @@ combines all player damage events to a single batch (event) and split it per dam targetId the id of the damaged player (playerId won't work for batch) */ DAMAGED_BATCH_FOR_ONE_PLAYER(true), - + /* DAMAGED_BATCH_BY_SOURCE + combine all damage events from a single source to a single batch (event) + */ + DAMAGED_BATCH_BY_SOURCE(true), /* DAMAGED_BATCH_FOR_ALL includes all damage events, both permanent damage and player damage, in single batch event */