mirror of
https://github.com/magefree/mage.git
synced 2026-01-10 21:02:08 -08:00
fix Cathedral Membrane, add test
use game default BlockedAttackerWatcher
This commit is contained in:
parent
b29ece2125
commit
7883d90cc8
8 changed files with 152 additions and 77 deletions
|
|
@ -1,21 +1,22 @@
|
|||
|
||||
package mage.cards.c;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.ZoneChangeTriggeredAbility;
|
||||
import mage.abilities.common.DiesSourceTriggeredAbility;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.keyword.DefenderAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.TurnPhase;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.watchers.Watcher;
|
||||
import mage.watchers.common.BlockedAttackerWatcher;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
|
@ -35,8 +36,7 @@ public final class CathedralMembrane extends CardImpl {
|
|||
this.addAbility(DefenderAbility.getInstance());
|
||||
|
||||
// When Cathedral Membrane dies during combat, it deals 6 damage to each creature it blocked this combat.
|
||||
this.addAbility(new CathedralMembraneAbility(), new CathedralMembraneWatcher());
|
||||
|
||||
this.addAbility(new CathedralMembraneAbility());
|
||||
}
|
||||
|
||||
private CathedralMembrane(final CathedralMembrane card) {
|
||||
|
|
@ -49,10 +49,11 @@ public final class CathedralMembrane extends CardImpl {
|
|||
}
|
||||
}
|
||||
|
||||
class CathedralMembraneAbility extends ZoneChangeTriggeredAbility {
|
||||
class CathedralMembraneAbility extends DiesSourceTriggeredAbility {
|
||||
|
||||
CathedralMembraneAbility() {
|
||||
super(Zone.BATTLEFIELD, Zone.GRAVEYARD, new CathedralMembraneEffect(), "When {this} dies during combat, ", false);
|
||||
super(new CathedralMembraneEffect());
|
||||
setTriggerPhrase("When {this} dies during combat, ");
|
||||
}
|
||||
|
||||
private CathedralMembraneAbility(CathedralMembraneAbility ability) {
|
||||
|
|
@ -66,12 +67,7 @@ class CathedralMembraneAbility extends ZoneChangeTriggeredAbility {
|
|||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
if (super.checkTrigger(event, game)) {
|
||||
if (game.getTurnPhaseType() == TurnPhase.COMBAT) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return game.getTurnPhaseType() == TurnPhase.COMBAT && super.checkTrigger(event, game);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -94,41 +90,13 @@ class CathedralMembraneEffect extends OneShotEffect {
|
|||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
CathedralMembraneWatcher watcher = game.getState().getWatcher(CathedralMembraneWatcher.class, source.getSourceId());
|
||||
if (watcher != null) {
|
||||
for (UUID uuid : watcher.getBlockedCreatures()) {
|
||||
Permanent permanent = game.getPermanent(uuid);
|
||||
if (permanent != null) {
|
||||
permanent.damage(6, source.getSourceId(), source, game, false, true);
|
||||
}
|
||||
Permanent permanent = source.getSourcePermanentOrLKI(game);
|
||||
BlockedAttackerWatcher watcher = game.getState().getWatcher(BlockedAttackerWatcher.class);
|
||||
if (watcher != null && permanent != null) {
|
||||
for (Permanent p : watcher.getBlockedCreatures(new MageObjectReference(permanent, game), game)) {
|
||||
p.damage(6, source.getSourceId(), source, game, false, true);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class CathedralMembraneWatcher extends Watcher {
|
||||
|
||||
private final Set<UUID> blockedCreatures = new HashSet<>();
|
||||
|
||||
CathedralMembraneWatcher() {
|
||||
super(WatcherScope.CARD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void watch(GameEvent event, Game game) {
|
||||
if (event.getType() == GameEvent.EventType.BLOCKER_DECLARED && event.getSourceId().equals(sourceId)) {
|
||||
blockedCreatures.add(event.getTargetId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
super.reset();
|
||||
blockedCreatures.clear();
|
||||
}
|
||||
|
||||
Set<UUID> getBlockedCreatures() {
|
||||
return blockedCreatures;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ class GazeOfTheGorgonEffect extends OneShotEffect {
|
|||
List<Permanent> toDestroy = new ArrayList<>();
|
||||
for (Permanent creature : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, source.getControllerId(), source, game)) {
|
||||
if (!creature.getId().equals(targetCreature.getSourceId())) {
|
||||
if (watcher.creatureHasBlockedAttacker(new MageObjectReference(creature, game), targetCreature, game) || watcher.creatureHasBlockedAttacker(targetCreature, new MageObjectReference(creature, game), game)) {
|
||||
if (watcher.creatureHasBlockedAttacker(new MageObjectReference(creature, game), targetCreature) || watcher.creatureHasBlockedAttacker(targetCreature, new MageObjectReference(creature, game))) {
|
||||
toDestroy.add(creature);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import mage.filter.common.FilterCreaturePermanent;
|
|||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
import mage.watchers.common.BlockedAttackerWatcher;
|
||||
|
||||
|
|
@ -82,7 +81,7 @@ class GlyphOfDelusionSecondTarget extends TargetPermanent {
|
|||
if (targetSource != null) {
|
||||
for (Permanent creature : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, source, game)) {
|
||||
if (!targets.containsKey(creature.getId()) && creature.canBeTargetedBy(targetSource, sourceControllerId, source, game)) {
|
||||
if (watcher.creatureHasBlockedAttacker(new MageObjectReference(creature, game), new MageObjectReference(firstTarget, game), game)) {
|
||||
if (watcher.creatureHasBlockedAttacker(new MageObjectReference(creature, game), new MageObjectReference(firstTarget, game))) {
|
||||
possibleTargets.add(creature.getId());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import mage.game.Game;
|
|||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
import mage.watchers.common.BlockedAttackerWatcher;
|
||||
|
||||
/**
|
||||
|
|
@ -110,7 +109,7 @@ class GlyphOfDoomEffect extends OneShotEffect {
|
|||
List<Permanent> toDestroy = new ArrayList<>();
|
||||
for (Permanent creature : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, source.getControllerId(), source, game)) {
|
||||
if (!creature.getId().equals(targetCreature.getSourceId())) {
|
||||
if (watcher.creatureHasBlockedAttacker(new MageObjectReference(creature, game), targetCreature, game)) {
|
||||
if (watcher.creatureHasBlockedAttacker(new MageObjectReference(creature, game), targetCreature)) {
|
||||
toDestroy.add(creature);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import mage.players.Player;
|
|||
import mage.target.Target;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetCardInGraveyard;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
import mage.watchers.common.BlockedAttackerWatcher;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
|
@ -87,7 +86,7 @@ class GlyphOfReincarnationEffect extends OneShotEffect {
|
|||
Map<UUID, Player> destroyed = new HashMap<>();
|
||||
for (Permanent creature : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, source.getControllerId(), source, game)) {
|
||||
if (!creature.getId().equals(targetWall.getId())) {
|
||||
if (watcher.creatureHasBlockedAttacker(new MageObjectReference(creature, game), new MageObjectReference(targetWall, game), game)) {
|
||||
if (watcher.creatureHasBlockedAttacker(new MageObjectReference(creature, game), new MageObjectReference(targetWall, game))) {
|
||||
if (creature.destroy(source, game, true)
|
||||
&& game.getState().getZone(creature.getId()) == Zone.GRAVEYARD) { // If a commander is replaced to command zone, the creature does not die
|
||||
Player permController = game.getPlayer(creature.getControllerId());
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ class VenomousBreathEffect extends OneShotEffect {
|
|||
List<Permanent> toDestroy = new ArrayList<>();
|
||||
for (Permanent creature : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, source.getControllerId(), source, game)) {
|
||||
if (!creature.getId().equals(targetCreature.getSourceId())) {
|
||||
if (watcher.creatureHasBlockedAttacker(new MageObjectReference(creature, game), targetCreature, game) || watcher.creatureHasBlockedAttacker(targetCreature, new MageObjectReference(creature, game), game)) {
|
||||
if (watcher.creatureHasBlockedAttacker(new MageObjectReference(creature, game), targetCreature) || watcher.creatureHasBlockedAttacker(targetCreature, new MageObjectReference(creature, game))) {
|
||||
toDestroy.add(creature);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
package org.mage.test.cards.watchers;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
* @author xenohedron
|
||||
*/
|
||||
public class CathedralMembraneTest extends CardTestPlayerBase {
|
||||
|
||||
// see issue #13774
|
||||
|
||||
private static final String cathedralMembrane = "Cathedral Membrane"; // 0/6
|
||||
// Defender
|
||||
// When this creature dies during combat, it deals 6 damage to each creature it blocked this combat.
|
||||
|
||||
private static final String wurm = "Autochthon Wurm"; // 9/14 convoke trample
|
||||
private static final String gigantosaurus = "Gigantosaurus"; // 10/10
|
||||
|
||||
private static final String moraug = "Moraug, Fury of Akoum"; // 6/6
|
||||
// Each creature you control gets +1/+0 for each time it has attacked this turn.
|
||||
// Landfall — Whenever a land you control enters, if it's your main phase,
|
||||
// there's an additional combat phase after this phase. At the beginning of that combat, untap all creatures you control.
|
||||
|
||||
private static final String recovery = "Miraculous Recovery"; // 4W instant
|
||||
// Return target creature card from your graveyard to the battlefield. Put a +1/+1 counter on it.
|
||||
|
||||
@Test
|
||||
public void testMembraneTrigger() {
|
||||
addCard(Zone.HAND, playerA, "Mountain");
|
||||
addCard(Zone.BATTLEFIELD, playerA, wurm);
|
||||
addCard(Zone.BATTLEFIELD, playerA, gigantosaurus);
|
||||
addCard(Zone.BATTLEFIELD, playerA, moraug);
|
||||
addCard(Zone.HAND, playerB, cathedralMembrane);
|
||||
addCard(Zone.HAND, playerB, recovery);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Plains", 6);
|
||||
|
||||
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, cathedralMembrane);
|
||||
setChoice(playerB, true); // pay life
|
||||
|
||||
attack(3, playerA, wurm, playerB);
|
||||
|
||||
block(3, playerB, cathedralMembrane, wurm);
|
||||
setChoiceAmount(playerA, 6); // assign trample damage
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(3, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertTapped(wurm, true);
|
||||
assertTapped(gigantosaurus, false);
|
||||
assertTapped(moraug, false);
|
||||
assertGraveyardCount(playerB, cathedralMembrane, 1);
|
||||
assertLife(playerA, 20);
|
||||
assertLife(playerB, 20 - 2 - 4);
|
||||
assertDamageReceived(playerA, wurm, 6);
|
||||
assertDamageReceived(playerA, gigantosaurus, 0);
|
||||
assertDamageReceived(playerA, moraug, 0);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMembraneTriggerAgain() {
|
||||
addCard(Zone.HAND, playerA, "Mountain");
|
||||
addCard(Zone.BATTLEFIELD, playerA, wurm);
|
||||
addCard(Zone.BATTLEFIELD, playerA, gigantosaurus);
|
||||
addCard(Zone.BATTLEFIELD, playerA, moraug);
|
||||
addCard(Zone.HAND, playerB, cathedralMembrane);
|
||||
addCard(Zone.HAND, playerB, recovery);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Plains", 6);
|
||||
|
||||
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, cathedralMembrane);
|
||||
setChoice(playerB, true); // pay life
|
||||
|
||||
attack(3, playerA, wurm, playerB);
|
||||
|
||||
block(3, playerB, cathedralMembrane, wurm);
|
||||
setChoiceAmount(playerA, 6); // assign trample damage
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(3, PhaseStep.END_COMBAT);
|
||||
execute(); // separate execute needed to separate commands from second combat phase
|
||||
|
||||
playLand(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Mountain");
|
||||
castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerB, recovery, cathedralMembrane);
|
||||
|
||||
attack(3, playerA, wurm, playerB);
|
||||
attack(3, playerA, gigantosaurus, playerB);
|
||||
|
||||
block(3, playerB, cathedralMembrane, gigantosaurus);
|
||||
|
||||
setStopAt(3, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertTapped(wurm, true);
|
||||
assertTapped(gigantosaurus, true);
|
||||
assertTapped(moraug, false);
|
||||
assertGraveyardCount(playerB, cathedralMembrane, 1);
|
||||
assertLife(playerA, 20);
|
||||
assertLife(playerB, 20 - 2 - 4 - 11);
|
||||
assertDamageReceived(playerA, wurm, 6);
|
||||
assertDamageReceived(playerA, gigantosaurus, 1 + 6);
|
||||
assertDamageReceived(playerA, moraug, 0);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
package mage.watchers.common;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import mage.MageObjectReference;
|
||||
import mage.constants.WatcherScope;
|
||||
import mage.game.Game;
|
||||
|
|
@ -17,7 +16,9 @@ import mage.watchers.Watcher;
|
|||
*/
|
||||
public class BlockedAttackerWatcher extends Watcher {
|
||||
|
||||
private final Map<MageObjectReference, Set<MageObjectReference>> blockData = new HashMap<>();
|
||||
// key: blocking creatures
|
||||
// value: set of creatures blocked
|
||||
private final Map<MageObjectReference, Set<MageObjectReference>> blockerMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Game default watcher
|
||||
|
|
@ -29,31 +30,31 @@ public class BlockedAttackerWatcher extends Watcher {
|
|||
@Override
|
||||
public void watch(GameEvent event, Game game) {
|
||||
if (event.getType() == GameEvent.EventType.BLOCKER_DECLARED) {
|
||||
MageObjectReference blocker = new MageObjectReference(event.getSourceId(), game);
|
||||
Set<MageObjectReference> blockedAttackers = blockData.get(blocker);
|
||||
if (blockedAttackers != null) {
|
||||
blockedAttackers.add(new MageObjectReference(event.getTargetId(), game));
|
||||
} else {
|
||||
blockedAttackers = new HashSet<>();
|
||||
blockedAttackers.add(new MageObjectReference(event.getTargetId(), game));
|
||||
blockData.put(blocker, blockedAttackers);
|
||||
}
|
||||
blockerMap.computeIfAbsent(new MageObjectReference(event.getSourceId(), game), k -> new HashSet<>())
|
||||
.add(new MageObjectReference(event.getTargetId(), game));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
super.reset();
|
||||
blockData.clear();
|
||||
blockerMap.clear();
|
||||
}
|
||||
|
||||
public boolean creatureHasBlockedAttacker(Permanent attacker, Permanent blocker, Game game) {
|
||||
Set<MageObjectReference> blockedAttackers = blockData.get(new MageObjectReference(blocker, game));
|
||||
return blockedAttackers != null && blockedAttackers.contains(new MageObjectReference(attacker, game));
|
||||
return blockerMap.getOrDefault(new MageObjectReference(blocker, game), Collections.emptySet())
|
||||
.contains(new MageObjectReference(attacker, game));
|
||||
}
|
||||
|
||||
public boolean creatureHasBlockedAttacker(MageObjectReference attacker, MageObjectReference blocker, Game game) {
|
||||
Set<MageObjectReference> blockedAttackers = blockData.get(blocker);
|
||||
return blockedAttackers != null && blockedAttackers.contains(attacker);
|
||||
public boolean creatureHasBlockedAttacker(MageObjectReference attacker, MageObjectReference blocker) {
|
||||
return blockerMap.getOrDefault(blocker, Collections.emptySet()).contains(attacker);
|
||||
}
|
||||
|
||||
public Set<Permanent> getBlockedCreatures(MageObjectReference blocker, Game game) {
|
||||
return blockerMap.getOrDefault(blocker, Collections.emptySet())
|
||||
.stream()
|
||||
.map(m -> m.getPermanent(game))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue