From d5e56f523dcc54457acc335692db57fc715c66e1 Mon Sep 17 00:00:00 2001 From: "Alex W. Jackson" Date: Thu, 8 Sep 2022 21:41:15 -0400 Subject: [PATCH] Add new EventType CREATURE_BLOCKS, which fires once per blocker (rather than once per blocker per attacker). Updated some abilities and cards to use it (still incomplete). Fixes #4285 --- Mage.Sets/src/mage/cards/b/BalduvianWarlord.java | 4 ++-- Mage.Sets/src/mage/cards/b/BattleCry.java | 6 +++--- Mage.Sets/src/mage/cards/b/BattleStrain.java | 5 ++--- Mage.Sets/src/mage/cards/c/Camouflage.java | 14 ++++++++++---- Mage.Sets/src/mage/cards/c/CarnageGladiator.java | 11 ++++------- Mage.Sets/src/mage/cards/f/FalseOrders.java | 1 + Mage.Sets/src/mage/cards/h/HeatOfBattle.java | 4 ++-- .../src/mage/cards/m/MageHuntersOnslaught.java | 4 ++-- Mage.Sets/src/mage/cards/s/SorrowsPath.java | 8 ++++---- .../AttacksOrBlocksAttachedTriggeredAbility.java | 10 ++++++++-- .../common/AttacksOrBlocksTriggeredAbility.java | 4 ++-- .../common/BecomesTargetTriggeredAbility.java | 6 +++--- .../common/BlocksCreatureTriggeredAbility.java | 2 +- .../common/BlocksSourceTriggeredAbility.java | 6 ++---- Mage/src/main/java/mage/game/combat/Combat.java | 3 +++ Mage/src/main/java/mage/game/events/GameEvent.java | 1 + 16 files changed, 50 insertions(+), 39 deletions(-) diff --git a/Mage.Sets/src/mage/cards/b/BalduvianWarlord.java b/Mage.Sets/src/mage/cards/b/BalduvianWarlord.java index 43aa8dba258..91d23562437 100644 --- a/Mage.Sets/src/mage/cards/b/BalduvianWarlord.java +++ b/Mage.Sets/src/mage/cards/b/BalduvianWarlord.java @@ -33,8 +33,7 @@ public final class BalduvianWarlord extends CardImpl { public BalduvianWarlord(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); - this.subtype.add(SubType.HUMAN); - this.subtype.add(SubType.BARBARIAN); + this.subtype.add(SubType.HUMAN, SubType.BARBARIAN); this.power = new MageInt(3); this.toughness = new MageInt(2); @@ -152,6 +151,7 @@ class BalduvianWarlordUnblockEffect extends OneShotEffect { ); } game.fireEvent(new BlockerDeclaredEvent(chosenPermanent.getId(), permanent.getId(), permanent.getControllerId())); + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CREATURE_BLOCKS, permanent.getId(), source, null)); } CombatGroup blockGroup = findBlockingGroup(permanent, game); // a new blockingGroup is formed, so it's necessary to find it again if (blockGroup != null) { diff --git a/Mage.Sets/src/mage/cards/b/BattleCry.java b/Mage.Sets/src/mage/cards/b/BattleCry.java index c5f60fcb347..6ff5e60eefe 100644 --- a/Mage.Sets/src/mage/cards/b/BattleCry.java +++ b/Mage.Sets/src/mage/cards/b/BattleCry.java @@ -22,7 +22,7 @@ import mage.target.targetpointer.FixedTarget; */ public final class BattleCry extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(""); + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("white creatures you control"); static { filter.add(new ColorPredicate(ObjectColor.WHITE)); @@ -65,12 +65,12 @@ class BattleCryTriggeredAbility extends DelayedTriggeredAbility { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.BLOCKER_DECLARED; + return event.getType() == GameEvent.EventType.CREATURE_BLOCKS; } @Override public boolean checkTrigger(GameEvent event, Game game) { - getEffects().get(0).setTargetPointer(new FixedTarget(event.getSourceId(), game)); + getEffects().get(0).setTargetPointer(new FixedTarget(event.getTargetId(), game)); return true; } diff --git a/Mage.Sets/src/mage/cards/b/BattleStrain.java b/Mage.Sets/src/mage/cards/b/BattleStrain.java index 3949a7688d7..1dfca99c34d 100644 --- a/Mage.Sets/src/mage/cards/b/BattleStrain.java +++ b/Mage.Sets/src/mage/cards/b/BattleStrain.java @@ -10,7 +10,6 @@ import mage.constants.CardType; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; @@ -55,12 +54,12 @@ class BattleStrainTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.BLOCKER_DECLARED; + return event.getType() == GameEvent.EventType.CREATURE_BLOCKS; } @Override public boolean checkTrigger(GameEvent event, Game game) { - Permanent blocker = game.getPermanent(event.getSourceId()); + Permanent blocker = game.getPermanent(event.getTargetId()); if (blocker != null) { getEffects().get(0).setTargetPointer(new FixedTarget(blocker.getControllerId())); return true; diff --git a/Mage.Sets/src/mage/cards/c/Camouflage.java b/Mage.Sets/src/mage/cards/c/Camouflage.java index d9728714b12..a08c9de1cce 100644 --- a/Mage.Sets/src/mage/cards/c/Camouflage.java +++ b/Mage.Sets/src/mage/cards/c/Camouflage.java @@ -3,8 +3,10 @@ package mage.cards.c; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.CastOnlyDuringPhaseStepSourceAbility; @@ -71,7 +73,7 @@ class CamouflageEffect extends ContinuousRuleModifyingEffectImpl { public CamouflageEffect copy() { return new CamouflageEffect(this); } - + @Override public boolean checksEventType(GameEvent event, Game game) { return event.getType() == GameEvent.EventType.DECLARING_BLOCKERS; @@ -98,7 +100,7 @@ class CamouflageEffect extends ContinuousRuleModifyingEffectImpl { for (Permanent permanent : game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), defenderId, game)) { permanent.setBlocking(0); } - + boolean declinedChoice = false; while (masterList.size() < attackerCount) { List newPile = new ArrayList<>(); @@ -133,7 +135,7 @@ class CamouflageEffect extends ContinuousRuleModifyingEffectImpl { } } masterList.add(newPile); - + StringBuilder sb = new StringBuilder("Blocker pile of ").append(defender.getLogName()).append(" (no. " + masterList.size() + "): "); int i = 0; for (Permanent permanent : newPile) { @@ -168,6 +170,7 @@ class CamouflageEffect extends ContinuousRuleModifyingEffectImpl { } List> allPiles = masterMap.get(playerId); + Set blockerIds = new HashSet<>(); for (List pile : allPiles) { if (available.isEmpty()) { break; @@ -180,19 +183,22 @@ class CamouflageEffect extends ContinuousRuleModifyingEffectImpl { CombatGroup group = game.getCombat().findGroup(attacker.getId()); if (group != null) { if (blocker.canBlock(attacker.getId(), game) && (blocker.getMaxBlocks() == 0 || group.getAttackers().size() <= blocker.getMaxBlocks())) { + blockerIds.add(blocker.getId()); boolean notYetBlocked = group.getBlockers().isEmpty(); group.addBlockerToGroup(blocker.getId(), blocker.getControllerId(), game); game.getCombat().addBlockingGroup(blocker.getId(), attacker.getId(), blocker.getControllerId(), game); if (notYetBlocked) { game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CREATURE_BLOCKED, attacker.getId(), source, null)); } - // TODO: find an alternate event solution for multi-blockers (as per issue #4285), this will work fine for single blocker creatures though game.fireEvent(new BlockerDeclaredEvent(attacker.getId(), blocker.getId(), blocker.getControllerId())); } } } } } + for (UUID blockerId : blockerIds) { + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CREATURE_BLOCKS, blockerId, source, null)); + } } } return true; diff --git a/Mage.Sets/src/mage/cards/c/CarnageGladiator.java b/Mage.Sets/src/mage/cards/c/CarnageGladiator.java index faf2008fa50..33167dfa6d1 100644 --- a/Mage.Sets/src/mage/cards/c/CarnageGladiator.java +++ b/Mage.Sets/src/mage/cards/c/CarnageGladiator.java @@ -1,5 +1,3 @@ - - package mage.cards.c; import java.util.UUID; @@ -26,8 +24,7 @@ public final class CarnageGladiator extends CardImpl { public CarnageGladiator (UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{B}{R}"); - this.subtype.add(SubType.SKELETON); - this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.SKELETON, SubType.WARRIOR); this.power = new MageInt(4); this.toughness = new MageInt(2); @@ -69,12 +66,12 @@ class CarnageGladiatorTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.BLOCKER_DECLARED; + return event.getType() == GameEvent.EventType.CREATURE_BLOCKS; } @Override public boolean checkTrigger(GameEvent event, Game game) { - Permanent blocker = game.getPermanent(event.getSourceId()); + Permanent blocker = game.getPermanent(event.getTargetId()); if (blocker != null) { getEffects().get(0).setTargetPointer(new FixedTarget(blocker.getControllerId())); return true; @@ -86,4 +83,4 @@ class CarnageGladiatorTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever a creature blocks, that creature's controller loses 1 life."; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/f/FalseOrders.java b/Mage.Sets/src/mage/cards/f/FalseOrders.java index 64e9ecc0ce0..5baee9e18a3 100644 --- a/Mage.Sets/src/mage/cards/f/FalseOrders.java +++ b/Mage.Sets/src/mage/cards/f/FalseOrders.java @@ -178,6 +178,7 @@ class FalseOrdersUnblockEffect extends OneShotEffect { ); } game.fireEvent(new BlockerDeclaredEvent(chosenPermanent.getId(), permanent.getId(), permanent.getControllerId())); + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CREATURE_BLOCKS, permanent.getId(), source, null)); } CombatGroup blockGroup = findBlockingGroup(permanent, game); // a new blockingGroup is formed, so it's necessary to find it again if (blockGroup != null) { diff --git a/Mage.Sets/src/mage/cards/h/HeatOfBattle.java b/Mage.Sets/src/mage/cards/h/HeatOfBattle.java index 303687d8336..e1d7a981da4 100644 --- a/Mage.Sets/src/mage/cards/h/HeatOfBattle.java +++ b/Mage.Sets/src/mage/cards/h/HeatOfBattle.java @@ -48,12 +48,12 @@ class HeatOfBattleTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.BLOCKER_DECLARED; + return event.getType() == GameEvent.EventType.CREATURE_BLOCKS; } @Override public boolean checkTrigger(GameEvent event, Game game) { - Permanent blocker = game.getPermanent(event.getSourceId()); + Permanent blocker = game.getPermanent(event.getTargetId()); if (blocker != null) { getEffects().get(0).setTargetPointer(new FixedTarget(blocker.getControllerId())); return true; diff --git a/Mage.Sets/src/mage/cards/m/MageHuntersOnslaught.java b/Mage.Sets/src/mage/cards/m/MageHuntersOnslaught.java index 7b512387e3e..812c091acd2 100644 --- a/Mage.Sets/src/mage/cards/m/MageHuntersOnslaught.java +++ b/Mage.Sets/src/mage/cards/m/MageHuntersOnslaught.java @@ -56,12 +56,12 @@ class MageHuntersOnslaughtDelayedTriggeredAbility extends DelayedTriggeredAbilit @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.BLOCKER_DECLARED; + return event.getType() == GameEvent.EventType.CREATURE_BLOCKS; } @Override public boolean checkTrigger(GameEvent event, Game game) { - Permanent permanent = game.getPermanent(event.getSourceId()); + Permanent permanent = game.getPermanent(event.getTargetId()); if (permanent == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/s/SorrowsPath.java b/Mage.Sets/src/mage/cards/s/SorrowsPath.java index 7f6deeaffb6..e16319ab0a2 100644 --- a/Mage.Sets/src/mage/cards/s/SorrowsPath.java +++ b/Mage.Sets/src/mage/cards/s/SorrowsPath.java @@ -121,8 +121,8 @@ class SorrowsPathSwitchBlockersEffect extends OneShotEffect { // not blocking (since they're removed from combat) to blocking. It doesn't matter if those abilities triggered when those creatures blocked the first time. Abilities // that trigger whenever one of the attacking creatures becomes blocked will not trigger again, because they never stopped being blocked creatures. Abilities that // trigger whenever a creature blocks one of the attacking creatures will trigger again, though; those kinds of abilities trigger once for each creature that blocks. - reassignBlocker(blocker1, attackers2, game); - reassignBlocker(blocker2, attackers1, game); + reassignBlocker(blocker1, attackers2, game, source); + reassignBlocker(blocker2, attackers1, game, source); Set morSet = new HashSet<>(); attackers1 .stream() @@ -171,17 +171,17 @@ class SorrowsPathSwitchBlockersEffect extends OneShotEffect { return true; } - private void reassignBlocker(Permanent blocker, Set attackers, Game game) { + private void reassignBlocker(Permanent blocker, Set attackers, Game game, Ability source) { for (Permanent attacker : attackers) { CombatGroup group = game.getCombat().findGroup(attacker.getId()); if (group != null) { group.addBlockerToGroup(blocker.getId(), blocker.getControllerId(), game); game.getCombat().addBlockingGroup(blocker.getId(), attacker.getId(), blocker.getControllerId(), game); - // TODO: find an alternate event solution for multi-blockers (as per issue #4285), this will work fine for single blocker creatures though game.fireEvent(new BlockerDeclaredEvent(attacker.getId(), blocker.getId(), blocker.getControllerId())); group.pickBlockerOrder(attacker.getControllerId(), game); } } + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CREATURE_BLOCKS, blocker.getId(), source, null)); CombatGroup blockGroup = findBlockingGroup(blocker, game); // a new blockingGroup is formed, so it's necessary to find it again if (blockGroup != null) { blockGroup.pickAttackerOrder(blocker.getControllerId(), game); diff --git a/Mage/src/main/java/mage/abilities/common/AttacksOrBlocksAttachedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/AttacksOrBlocksAttachedTriggeredAbility.java index 7ef0155e7b1..cb63f7e9fbb 100644 --- a/Mage/src/main/java/mage/abilities/common/AttacksOrBlocksAttachedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/AttacksOrBlocksAttachedTriggeredAbility.java @@ -10,6 +10,8 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; +import java.util.UUID; + public class AttacksOrBlocksAttachedTriggeredAbility extends TriggeredAbilityImpl { private final AttachmentType attachmentType; @@ -33,12 +35,16 @@ public class AttacksOrBlocksAttachedTriggeredAbility extends TriggeredAbilityImp @Override public boolean checkEventType(GameEvent event, Game game) { return event.getType() == GameEvent.EventType.ATTACKER_DECLARED - || event.getType() == GameEvent.EventType.BLOCKER_DECLARED; + || event.getType() == GameEvent.EventType.CREATURE_BLOCKS; } @Override public boolean checkTrigger(GameEvent event, Game game) { Permanent enchantment = getSourcePermanentOrLKI(game); - return enchantment != null && event.getSourceId().equals(enchantment.getAttachedTo()); + if (enchantment == null) { + return false; + } + UUID idToCheck = (event.getType() == GameEvent.EventType.ATTACKER_DECLARED) ? event.getSourceId() : event.getTargetId(); + return idToCheck.equals(enchantment.getAttachedTo()); } } diff --git a/Mage/src/main/java/mage/abilities/common/AttacksOrBlocksTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/AttacksOrBlocksTriggeredAbility.java index cf7a65bd387..7eca53b6969 100644 --- a/Mage/src/main/java/mage/abilities/common/AttacksOrBlocksTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/AttacksOrBlocksTriggeredAbility.java @@ -29,11 +29,11 @@ public class AttacksOrBlocksTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ATTACKER_DECLARED || event.getType() == GameEvent.EventType.BLOCKER_DECLARED; + return event.getType() == GameEvent.EventType.ATTACKER_DECLARED || event.getType() == GameEvent.EventType.CREATURE_BLOCKS; } @Override public boolean checkTrigger(GameEvent event, Game game) { - return event.getSourceId().equals(this.getSourceId()); + return getSourceId().equals((event.getType() == GameEvent.EventType.ATTACKER_DECLARED) ? event.getSourceId() : event.getTargetId()); } } diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetTriggeredAbility.java index de12cb4bc74..a705b110b6b 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetTriggeredAbility.java @@ -25,7 +25,6 @@ public class BecomesTargetTriggeredAbility extends TriggeredAbilityImpl { public BecomesTargetTriggeredAbility(Effect effect, FilterStackObject filter) { this(effect, filter, SetTargetPointer.NONE); - setTriggerPhrase("When {this} becomes the target of " + filter.getMessage() + ", "); } public BecomesTargetTriggeredAbility(Effect effect, FilterStackObject filter, SetTargetPointer setTargetPointer) { @@ -34,13 +33,14 @@ public class BecomesTargetTriggeredAbility extends TriggeredAbilityImpl { public BecomesTargetTriggeredAbility(Effect effect, FilterStackObject filter, SetTargetPointer setTargetPointer, boolean optional) { super(Zone.BATTLEFIELD, effect, optional); - this.filter = filter.copy(); + this.filter = filter; this.setTargetPointer = setTargetPointer; + setTriggerPhrase("When {this} becomes the target of " + filter.getMessage() + ", "); } public BecomesTargetTriggeredAbility(final BecomesTargetTriggeredAbility ability) { super(ability); - this.filter = ability.filter.copy(); + this.filter = ability.filter; this.setTargetPointer = ability.setTargetPointer; } diff --git a/Mage/src/main/java/mage/abilities/common/BlocksCreatureTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BlocksCreatureTriggeredAbility.java index dff1da4b078..c4c4d3c144b 100644 --- a/Mage/src/main/java/mage/abilities/common/BlocksCreatureTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BlocksCreatureTriggeredAbility.java @@ -23,12 +23,12 @@ public class BlocksCreatureTriggeredAbility extends TriggeredAbilityImpl { public BlocksCreatureTriggeredAbility(Effect effect, boolean optional) { this(effect, StaticFilters.FILTER_PERMANENT_CREATURE, optional); - setTriggerPhrase("Whenever {this} blocks " + CardUtil.addArticle(filter.getMessage()) + ", "); } public BlocksCreatureTriggeredAbility(Effect effect, FilterCreaturePermanent filter, boolean optional) { super(Zone.BATTLEFIELD, effect, optional); this.filter = filter; + setTriggerPhrase("Whenever {this} blocks " + CardUtil.addArticle(filter.getMessage()) + ", "); } public BlocksCreatureTriggeredAbility(final BlocksCreatureTriggeredAbility ability) { diff --git a/Mage/src/main/java/mage/abilities/common/BlocksSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BlocksSourceTriggeredAbility.java index 47238414584..4398ee4d809 100644 --- a/Mage/src/main/java/mage/abilities/common/BlocksSourceTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BlocksSourceTriggeredAbility.java @@ -5,7 +5,6 @@ import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; /** * @@ -28,13 +27,12 @@ public class BlocksSourceTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DECLARED_BLOCKERS; + return event.getType() == GameEvent.EventType.CREATURE_BLOCKS; } @Override public boolean checkTrigger(GameEvent event, Game game) { - Permanent permanent = game.getPermanent(getSourceId()); - return permanent != null && permanent.getBlocking() > 0; + return event.getTargetId().equals(getSourceId()); } @Override diff --git a/Mage/src/main/java/mage/game/combat/Combat.java b/Mage/src/main/java/mage/game/combat/Combat.java index af840b41122..fe9e3b11bf4 100644 --- a/Mage/src/main/java/mage/game/combat/Combat.java +++ b/Mage/src/main/java/mage/game/combat/Combat.java @@ -704,6 +704,9 @@ public class Combat implements Serializable, Copyable { for (CombatGroup group : groups) { group.acceptBlockers(game); } + for (UUID blockerId : getBlockers()) { + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CREATURE_BLOCKS, blockerId, null)); + } } public void resumeSelectBlockers(Game game) { diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index be0dfdd2fe2..1fc51a081d8 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -298,6 +298,7 @@ public class GameEvent implements Serializable { */ BLOCKER_DECLARED, CREATURE_BLOCKED, + CREATURE_BLOCKS, BATCH_BLOCK_NONCOMBAT, UNBLOCKED_ATTACKER, SEARCH_LIBRARY, LIBRARY_SEARCHED,