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

This commit is contained in:
Alex W. Jackson 2022-09-08 21:41:15 -04:00
parent 1c688a0345
commit d5e56f523d
16 changed files with 50 additions and 39 deletions

View file

@ -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) {

View file

@ -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;
}

View file

@ -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;

View file

@ -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<Permanent> 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<List<Permanent>> allPiles = masterMap.get(playerId);
Set<UUID> blockerIds = new HashSet<>();
for (List<Permanent> 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;

View file

@ -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.";
}
}
}

View file

@ -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) {

View file

@ -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;

View file

@ -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;
}

View file

@ -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<MageObjectReference> morSet = new HashSet<>();
attackers1
.stream()
@ -171,17 +171,17 @@ class SorrowsPathSwitchBlockersEffect extends OneShotEffect {
return true;
}
private void reassignBlocker(Permanent blocker, Set<Permanent> attackers, Game game) {
private void reassignBlocker(Permanent blocker, Set<Permanent> 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);

View file

@ -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());
}
}

View file

@ -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());
}
}

View file

@ -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;
}

View file

@ -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) {

View file

@ -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

View file

@ -704,6 +704,9 @@ public class Combat implements Serializable, Copyable<Combat> {
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) {

View file

@ -298,6 +298,7 @@ public class GameEvent implements Serializable {
*/
BLOCKER_DECLARED,
CREATURE_BLOCKED,
CREATURE_BLOCKS,
BATCH_BLOCK_NONCOMBAT,
UNBLOCKED_ATTACKER,
SEARCH_LIBRARY, LIBRARY_SEARCHED,