mirror of
https://github.com/magefree/mage.git
synced 2025-12-23 12:02:01 -08:00
fix #11766 (Corrosive Ooze), add test
This commit is contained in:
parent
b6ee7fe410
commit
77b8030642
2 changed files with 248 additions and 86 deletions
|
|
@ -17,14 +17,13 @@ import mage.game.Game;
|
||||||
import mage.game.events.GameEvent;
|
import mage.game.events.GameEvent;
|
||||||
import mage.game.events.ZoneChangeEvent;
|
import mage.game.events.ZoneChangeEvent;
|
||||||
import mage.game.permanent.Permanent;
|
import mage.game.permanent.Permanent;
|
||||||
import mage.players.Player;
|
|
||||||
import mage.util.CardUtil;
|
|
||||||
import mage.watchers.Watcher;
|
import mage.watchers.Watcher;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author rscoates
|
* @author rscoates, LevelX2, xenohedron
|
||||||
*/
|
*/
|
||||||
public final class CorrosiveOoze extends CardImpl {
|
public final class CorrosiveOoze extends CardImpl {
|
||||||
|
|
||||||
|
|
@ -76,43 +75,27 @@ class CorrosiveOozeEffect extends OneShotEffect {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
Player controller = game.getPlayer(source.getControllerId());
|
|
||||||
CorrosiveOozeCombatWatcher watcher = game.getState().getWatcher(CorrosiveOozeCombatWatcher.class);
|
CorrosiveOozeCombatWatcher watcher = game.getState().getWatcher(CorrosiveOozeCombatWatcher.class);
|
||||||
if (controller != null && watcher != null) {
|
if (watcher == null) {
|
||||||
MageObjectReference sourceMor = new MageObjectReference(source.getSourceObject(game), game);
|
return false;
|
||||||
// get equipmentsToDestroy of creatres already left the battlefield
|
|
||||||
List<Permanent> toDestroy = new ArrayList<>();
|
|
||||||
Set<MageObjectReference> toDestroyMor = watcher.getEquipmentsToDestroy(sourceMor);
|
|
||||||
if (toDestroyMor != null) {
|
|
||||||
for (MageObjectReference mor : toDestroyMor) {
|
|
||||||
Permanent attachment = mor.getPermanent(game);
|
|
||||||
if (attachment != null) {
|
|
||||||
toDestroy.add(attachment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// get the related creatures
|
|
||||||
Set<MageObjectReference> relatedCreatures = watcher.getRelatedBlockedCreatures(sourceMor);
|
|
||||||
if (relatedCreatures != null) {
|
|
||||||
for (MageObjectReference relatedCreature : relatedCreatures) {
|
|
||||||
Permanent permanent = relatedCreature.getPermanent(game);
|
|
||||||
if (permanent != null) {
|
|
||||||
for (UUID attachmentId : permanent.getAttachments()) {
|
|
||||||
Permanent attachment = game.getPermanent(attachmentId);
|
|
||||||
if (attachment != null && attachment.hasSubtype(SubType.EQUIPMENT, game)) {
|
|
||||||
toDestroy.add(attachment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (Permanent permanent : toDestroy) {
|
|
||||||
permanent.destroy(source, game, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
MageObjectReference sourceMor = new MageObjectReference(source.getSourceId(), source.getSourceObjectZoneChangeCounter(), game);
|
||||||
|
List<Permanent> equipments = watcher.getEquipmentsToDestroy(sourceMor)
|
||||||
|
.stream()
|
||||||
|
.map(mor -> mor.getPermanent(game))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
watcher.getRelatedBlockedCreatures(sourceMor)
|
||||||
|
.stream()
|
||||||
|
.map(mor -> mor.getPermanent(game))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.flatMap(p -> p.getAttachments().stream())
|
||||||
|
.map(game::getPermanent)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.filter(a -> a.hasSubtype(SubType.EQUIPMENT, game))
|
||||||
|
.forEach(equipments::add);
|
||||||
|
equipments.forEach(p -> p.destroy(source, game));
|
||||||
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -122,7 +105,7 @@ class CorrosiveOozeCombatWatcher extends Watcher {
|
||||||
private final Map<MageObjectReference, Set<MageObjectReference>> oozeBlocksOrBlocked = new HashMap<>();
|
private final Map<MageObjectReference, Set<MageObjectReference>> oozeBlocksOrBlocked = new HashMap<>();
|
||||||
private final Map<MageObjectReference, Set<MageObjectReference>> oozeEquipmentsToDestroy = new HashMap<>();
|
private final Map<MageObjectReference, Set<MageObjectReference>> oozeEquipmentsToDestroy = new HashMap<>();
|
||||||
|
|
||||||
public CorrosiveOozeCombatWatcher() {
|
CorrosiveOozeCombatWatcher() {
|
||||||
super(WatcherScope.GAME);
|
super(WatcherScope.GAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,45 +113,35 @@ class CorrosiveOozeCombatWatcher extends Watcher {
|
||||||
public void watch(GameEvent event, Game game) {
|
public void watch(GameEvent event, Game game) {
|
||||||
if (event.getType() == GameEvent.EventType.BEGIN_COMBAT_STEP_PRE) {
|
if (event.getType() == GameEvent.EventType.BEGIN_COMBAT_STEP_PRE) {
|
||||||
this.oozeBlocksOrBlocked.clear();
|
this.oozeBlocksOrBlocked.clear();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (event.getType() == GameEvent.EventType.BLOCKER_DECLARED) {
|
if (event.getType() == GameEvent.EventType.BLOCKER_DECLARED) {
|
||||||
Permanent attacker = game.getPermanent(event.getTargetId());
|
Permanent attacker = game.getPermanent(event.getTargetId());
|
||||||
Permanent blocker = game.getPermanent(event.getSourceId());
|
Permanent blocker = game.getPermanent(event.getSourceId());
|
||||||
if (attacker != null && CardUtil.haveSameNames(attacker, "Corrosive Ooze", game)) { // To check for name is not working if Ooze is copied but name changed
|
if (attacker == null || blocker == null) {
|
||||||
if (blocker != null && hasAttachedEquipment(game, blocker)) {
|
return;
|
||||||
MageObjectReference oozeMor = new MageObjectReference(attacker, game);
|
|
||||||
Set<MageObjectReference> relatedCreatures = oozeBlocksOrBlocked.getOrDefault(oozeMor, new HashSet<>());
|
|
||||||
relatedCreatures.add(new MageObjectReference(event.getSourceId(), game));
|
|
||||||
oozeBlocksOrBlocked.put(oozeMor, relatedCreatures);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (blocker != null && CardUtil.haveSameNames(blocker, "Corrosive Ooze", game)) {
|
|
||||||
if (attacker != null && hasAttachedEquipment(game, attacker)) {
|
|
||||||
MageObjectReference oozeMor = new MageObjectReference(blocker, game);
|
|
||||||
Set<MageObjectReference> relatedCreatures = oozeBlocksOrBlocked.getOrDefault(oozeMor, new HashSet<>());
|
|
||||||
relatedCreatures.add(new MageObjectReference(event.getTargetId(), game));
|
|
||||||
oozeBlocksOrBlocked.put(oozeMor, relatedCreatures);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
oozeBlocksOrBlocked.computeIfAbsent(new MageObjectReference(attacker, game), k -> new HashSet<>())
|
||||||
|
.add(new MageObjectReference(blocker, game));
|
||||||
|
oozeBlocksOrBlocked.computeIfAbsent(new MageObjectReference(blocker, game), k -> new HashSet<>())
|
||||||
|
.add(new MageObjectReference(attacker, game));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.getType() == GameEvent.EventType.ZONE_CHANGE) {
|
if (event.getType() == GameEvent.EventType.ZONE_CHANGE) {
|
||||||
if (((ZoneChangeEvent) event).getFromZone() == Zone.BATTLEFIELD) {
|
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
|
||||||
if (game.getTurn() != null && TurnPhase.COMBAT == game.getTurnPhaseType()) {
|
if (zEvent.getFromZone() == Zone.BATTLEFIELD) {
|
||||||
// Check if a previous blocked or blocked by creatures is leaving the battlefield
|
// Check if a previous blocked or blocked by creatures is leaving the battlefield
|
||||||
for (Map.Entry<MageObjectReference, Set<MageObjectReference>> entry : oozeBlocksOrBlocked.entrySet()) {
|
for (Map.Entry<MageObjectReference, Set<MageObjectReference>> entry : oozeBlocksOrBlocked.entrySet()) {
|
||||||
for (MageObjectReference mor : entry.getValue()) {
|
for (MageObjectReference mor : entry.getValue()) {
|
||||||
if (mor.refersTo(((ZoneChangeEvent) event).getTarget(), game)) {
|
if (mor.refersTo(zEvent.getTarget(), game)) {
|
||||||
// check for equipments and remember
|
// check for equipments and remember
|
||||||
for (UUID attachmentId : ((ZoneChangeEvent) event).getTarget().getAttachments()) {
|
zEvent.getTarget().getAttachments().stream()
|
||||||
Permanent attachment = game.getPermanent(attachmentId);
|
.map(game::getPermanent)
|
||||||
if (attachment != null && attachment.hasSubtype(SubType.EQUIPMENT, game)) {
|
.filter(Objects::nonNull)
|
||||||
Set<MageObjectReference> toDestroy = oozeEquipmentsToDestroy.getOrDefault(entry.getKey(), new HashSet<>());
|
.filter(a -> a.hasSubtype(SubType.EQUIPMENT, game))
|
||||||
toDestroy.add(new MageObjectReference(attachment, game));
|
.map(a -> new MageObjectReference(a, game))
|
||||||
oozeEquipmentsToDestroy.put(entry.getKey(), toDestroy);
|
.forEach(oozeEquipmentsToDestroy.computeIfAbsent(entry.getKey(), k -> new HashSet<>())::add);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -176,25 +149,18 @@ class CorrosiveOozeCombatWatcher extends Watcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasAttachedEquipment(Game game, Permanent permanent) {
|
@Override
|
||||||
for (UUID attachmentId : permanent.getAttachments()) {
|
public void reset() {
|
||||||
Permanent attachment = game.getPermanent(attachmentId);
|
super.reset();
|
||||||
if (attachment != null && attachment.hasSubtype(SubType.EQUIPMENT, game)) {
|
oozeBlocksOrBlocked.clear();
|
||||||
return true;
|
oozeEquipmentsToDestroy.clear();
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<MageObjectReference> getRelatedBlockedCreatures(MageObjectReference ooze) {
|
Set<MageObjectReference> getRelatedBlockedCreatures(MageObjectReference ooze) {
|
||||||
Set<MageObjectReference> relatedCreatures = this.oozeBlocksOrBlocked.get(ooze);
|
return oozeBlocksOrBlocked.getOrDefault(ooze, Collections.emptySet());
|
||||||
oozeBlocksOrBlocked.remove(ooze); // remove here to get no overlap with creatures leaving meanwhile
|
|
||||||
return relatedCreatures;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<MageObjectReference> getEquipmentsToDestroy(MageObjectReference ooze) {
|
Set<MageObjectReference> getEquipmentsToDestroy(MageObjectReference ooze) {
|
||||||
Set<MageObjectReference> equipmentsToDestroy = this.oozeEquipmentsToDestroy.get(ooze);
|
return oozeEquipmentsToDestroy.getOrDefault(ooze, Collections.emptySet());
|
||||||
oozeEquipmentsToDestroy.remove(ooze); // remove here to get no overlap with creatures leaving meanwhile
|
|
||||||
return equipmentsToDestroy;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,196 @@
|
||||||
|
package org.mage.test.cards.single.dom;
|
||||||
|
|
||||||
|
import mage.constants.PhaseStep;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
||||||
|
public class CorrosiveOozeTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
|
private static final String ooze = "Corrosive Ooze"; // 1G 2/2
|
||||||
|
// Whenever Corrosive Ooze blocks or becomes blocked by an equipped creature,
|
||||||
|
// destroy all Equipment attached to that creature at end of combat.
|
||||||
|
/*
|
||||||
|
* The set of Equipment to be destroyed is determined only as Corrosive Ooze’s delayed triggered ability resolves
|
||||||
|
* at the end of combat. The Equipment will be destroyed even if Corrosive Ooze leaves the battlefield before that time.
|
||||||
|
* --
|
||||||
|
* If the creature Corrosive Ooze blocks or is blocking leaves the battlefield, the Equipment that was attached to
|
||||||
|
* that creature immediately before it left the battlefield will be destroyed as Corrosive Ooze’s delayed triggered
|
||||||
|
* ability resolves at the end of combat.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private static final String sword = "Short Sword"; // Equip 1: +1/+1
|
||||||
|
private static final String wizard = "Fugitive Wizard"; // 1/1
|
||||||
|
private static final String kraken = "Kraken Hatchling"; // 0/4
|
||||||
|
private static final String centaur = "Centaur Courser"; // 3/3
|
||||||
|
private static final String acolyte = "Acolyte of Xathrid"; // 0/1
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTradeAttacking() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, ooze);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, wizard);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, sword);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Wastes");
|
||||||
|
|
||||||
|
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Equip", wizard);
|
||||||
|
|
||||||
|
attack(3, playerA, ooze, playerB);
|
||||||
|
block(3, playerB, wizard, ooze);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(3, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertGraveyardCount(playerA, ooze, 1);
|
||||||
|
assertGraveyardCount(playerB, wizard, 1);
|
||||||
|
assertGraveyardCount(playerB, sword, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTradeBlocking() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, ooze);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, wizard);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, sword);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Wastes");
|
||||||
|
|
||||||
|
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Equip", wizard);
|
||||||
|
|
||||||
|
attack(2, playerB, wizard, playerA);
|
||||||
|
block(2, playerA, ooze, wizard);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(2, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertGraveyardCount(playerA, ooze, 1);
|
||||||
|
assertGraveyardCount(playerB, wizard, 1);
|
||||||
|
assertGraveyardCount(playerB, sword, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBounceAttacking() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, ooze);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, kraken);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, sword);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Wastes");
|
||||||
|
|
||||||
|
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Equip", kraken);
|
||||||
|
|
||||||
|
attack(3, playerA, ooze, playerB);
|
||||||
|
block(3, playerB, kraken, ooze);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(3, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, ooze, 1);
|
||||||
|
assertPermanentCount(playerB, kraken, 1);
|
||||||
|
assertGraveyardCount(playerB, sword, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBounceBlocking() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, ooze);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, kraken);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, sword);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Wastes");
|
||||||
|
|
||||||
|
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Equip", kraken);
|
||||||
|
|
||||||
|
attack(2, playerB, kraken, playerA);
|
||||||
|
block(2, playerA, ooze, kraken);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(2, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, ooze, 1);
|
||||||
|
assertPermanentCount(playerB, kraken, 1);
|
||||||
|
assertGraveyardCount(playerB, sword, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEatenAttacking() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, ooze);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, centaur);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, sword);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Wastes");
|
||||||
|
|
||||||
|
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Equip", centaur);
|
||||||
|
|
||||||
|
attack(3, playerA, ooze, playerB);
|
||||||
|
block(3, playerB, centaur, ooze);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(3, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertGraveyardCount(playerA, ooze, 1);
|
||||||
|
assertPermanentCount(playerB, centaur, 1);
|
||||||
|
assertGraveyardCount(playerB, sword, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEatenBlocking() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, ooze);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, centaur);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, sword);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Wastes");
|
||||||
|
|
||||||
|
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Equip", centaur);
|
||||||
|
|
||||||
|
attack(2, playerB, centaur, playerA);
|
||||||
|
block(2, playerA, ooze, centaur);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(2, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertGraveyardCount(playerA, ooze, 1);
|
||||||
|
assertPermanentCount(playerB, centaur, 1);
|
||||||
|
assertGraveyardCount(playerB, sword, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEatAttacking() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, ooze);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, acolyte);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, sword);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Wastes");
|
||||||
|
|
||||||
|
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Equip", acolyte);
|
||||||
|
|
||||||
|
attack(3, playerA, ooze, playerB);
|
||||||
|
block(3, playerB, acolyte, ooze);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(3, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, ooze, 1);
|
||||||
|
assertGraveyardCount(playerB, acolyte, 1);
|
||||||
|
assertGraveyardCount(playerB, sword, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEatBlocking() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, ooze);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, acolyte);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, sword);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Wastes");
|
||||||
|
|
||||||
|
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Equip", acolyte);
|
||||||
|
|
||||||
|
attack(2, playerB, acolyte, playerA);
|
||||||
|
block(2, playerA, ooze, acolyte);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(2, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, ooze, 1);
|
||||||
|
assertGraveyardCount(playerB, acolyte, 1);
|
||||||
|
assertGraveyardCount(playerB, sword, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue