mirror of
https://github.com/correl/mage.git
synced 2024-12-25 03:00:15 +00:00
* Fixed spell's target still legal handling check as it begins to resolve (fixes problem of Sublime Epiphany #6646).
This commit is contained in:
parent
ae165e5197
commit
41abefa8e4
2 changed files with 112 additions and 24 deletions
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package org.mage.test.cards.modal;
|
||||||
|
|
||||||
|
import mage.constants.PhaseStep;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import mage.game.permanent.Permanent;
|
||||||
|
import mage.game.permanent.PermanentToken;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author LevelX2
|
||||||
|
*/
|
||||||
|
public class OneOrMoreTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sublime Epiphany can bounce and
|
||||||
|
* copy the same creature. This is because legality of targets is checked
|
||||||
|
* only as the spell begins to resolve, not in between modes, and because
|
||||||
|
* the games can use last known info of the legal target.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSubtleStrikeFirstMode() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Island", 6);
|
||||||
|
// Choose one or more —
|
||||||
|
// 1 • Counter target spell
|
||||||
|
// 2 • Counter target activated or triggered ability.
|
||||||
|
// 3 • Return target nonland permanent to its owner's hand.
|
||||||
|
// 4 • Create a token that's a copy of target creature you control.
|
||||||
|
// 5 • Target player draws a card.
|
||||||
|
addCard(Zone.HAND, playerA, "Sublime Epiphany"); // Instant {4}{U}{U}
|
||||||
|
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion");
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sublime Epiphany", "Silvercoat Lion");
|
||||||
|
setModeChoice(playerA, "3");
|
||||||
|
setModeChoice(playerA, "4");
|
||||||
|
addTarget(playerA, "Silvercoat Lion");
|
||||||
|
setModeChoice(playerA, "5");
|
||||||
|
addTarget(playerA, playerB);
|
||||||
|
setModeChoice(playerA, null);
|
||||||
|
|
||||||
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertHandCount(playerB, 1);
|
||||||
|
assertHandCount(playerA, "Silvercoat Lion", 1);
|
||||||
|
|
||||||
|
assertPowerToughness(playerA, "Silvercoat Lion", 2, 2);
|
||||||
|
|
||||||
|
Permanent perm = getPermanent("Silvercoat Lion");
|
||||||
|
Assert.assertTrue("Silvercoat Lion has to be a Token", perm instanceof PermanentToken);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,6 +33,7 @@ import mage.util.GameLog;
|
||||||
import mage.util.SubTypeList;
|
import mage.util.SubTypeList;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
@ -219,15 +220,14 @@ public class Spell extends StackObjImpl implements Card {
|
||||||
// resolve if legal parts
|
// resolve if legal parts
|
||||||
if (notTargeted || legalParts) {
|
if (notTargeted || legalParts) {
|
||||||
for (SpellAbility spellAbility : this.spellAbilities) {
|
for (SpellAbility spellAbility : this.spellAbilities) {
|
||||||
if (spellAbilityHasLegalParts(spellAbility, game)) {
|
// legality of targets is checked only as the spell begins to resolve, not in between modes (spliced spells handeled correctly?)
|
||||||
|
if (spellAbilityCheckTargetsAndDeactivateModes(spellAbility, game)) {
|
||||||
for (UUID modeId : spellAbility.getModes().getSelectedModes()) {
|
for (UUID modeId : spellAbility.getModes().getSelectedModes()) {
|
||||||
spellAbility.getModes().setActiveMode(modeId);
|
spellAbility.getModes().setActiveMode(modeId);
|
||||||
if (spellAbility.getTargets().stillLegal(spellAbility, game)) {
|
if (spellAbility.getSpellAbilityType() != SpellAbilityType.SPLICE) {
|
||||||
if (spellAbility.getSpellAbilityType() != SpellAbilityType.SPLICE) {
|
updateOptionalCosts(index);
|
||||||
updateOptionalCosts(index);
|
|
||||||
}
|
|
||||||
result |= spellAbility.resolve(game);
|
|
||||||
}
|
}
|
||||||
|
result |= spellAbility.resolve(game);
|
||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
@ -318,6 +318,31 @@ public class Spell extends StackObjImpl implements Card {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legality of the targets of all modes are only checked as the spell begins to resolve
|
||||||
|
* A mode without any legal target (if it has targets at all) won't resolve.
|
||||||
|
* So modes with targets without legal targets are unselected.
|
||||||
|
*
|
||||||
|
* @param spellAbility
|
||||||
|
* @param game
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private boolean spellAbilityCheckTargetsAndDeactivateModes(SpellAbility spellAbility, Game game) {
|
||||||
|
boolean legalModes = false;
|
||||||
|
for (Iterator<UUID> iterator = spellAbility.getModes().getSelectedModes().iterator(); iterator.hasNext();) {
|
||||||
|
UUID nextSelectedModeId = iterator.next();
|
||||||
|
Mode mode = spellAbility.getModes().get(nextSelectedModeId);
|
||||||
|
if (!mode.getTargets().isEmpty()) {
|
||||||
|
if (!mode.getTargets().stillLegal(spellAbility, game)) {
|
||||||
|
iterator.remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
legalModes = true;
|
||||||
|
}
|
||||||
|
return legalModes;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean spellAbilityHasLegalParts(SpellAbility spellAbility, Game game) {
|
private boolean spellAbilityHasLegalParts(SpellAbility spellAbility, Game game) {
|
||||||
if (spellAbility.getModes().getSelectedModes().size() > 1) {
|
if (spellAbility.getModes().getSelectedModes().size() > 1) {
|
||||||
boolean targetedMode = false;
|
boolean targetedMode = false;
|
||||||
|
@ -606,7 +631,9 @@ public class Spell extends StackObjImpl implements Card {
|
||||||
spellAbilities.add(spellAbility);
|
spellAbilities.add(spellAbility);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void addAbility(Ability ability) {
|
public void addAbility(Ability ability) {
|
||||||
|
throw new UnsupportedOperationException("Not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -709,7 +736,7 @@ public class Spell extends StackObjImpl implements Card {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Spell copySpell(UUID newController) {
|
public Spell copySpell(UUID newController) {
|
||||||
Spell copy = new Spell(this.card, this.ability.copySpell(), this.controllerId, this.fromZone);
|
Spell spellCopy = new Spell(this.card, this.ability.copySpell(), this.controllerId, this.fromZone);
|
||||||
boolean firstDone = false;
|
boolean firstDone = false;
|
||||||
for (SpellAbility spellAbility : this.getSpellAbilities()) {
|
for (SpellAbility spellAbility : this.getSpellAbilities()) {
|
||||||
if (!firstDone) {
|
if (!firstDone) {
|
||||||
|
@ -718,11 +745,11 @@ public class Spell extends StackObjImpl implements Card {
|
||||||
}
|
}
|
||||||
SpellAbility newAbility = spellAbility.copy(); // e.g. spliced spell
|
SpellAbility newAbility = spellAbility.copy(); // e.g. spliced spell
|
||||||
newAbility.newId();
|
newAbility.newId();
|
||||||
copy.addSpellAbility(newAbility);
|
spellCopy.addSpellAbility(newAbility);
|
||||||
}
|
}
|
||||||
copy.setCopy(true, this);
|
spellCopy.setCopy(true, this);
|
||||||
copy.setControllerId(newController);
|
spellCopy.setControllerId(newController);
|
||||||
return copy;
|
return spellCopy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -947,12 +974,12 @@ public class Spell extends StackObjImpl implements Card {
|
||||||
}
|
}
|
||||||
if (isTransformable()) {
|
if (isTransformable()) {
|
||||||
Card secondCard = getSecondCardFace();
|
Card secondCard = getSecondCardFace();
|
||||||
ObjectColor color = secondCard.getColor(null);
|
ObjectColor objectColor = secondCard.getColor(null);
|
||||||
mana.setBlack(mana.isBlack() || color.isBlack());
|
mana.setBlack(mana.isBlack() || objectColor.isBlack());
|
||||||
mana.setGreen(mana.isGreen() || color.isGreen());
|
mana.setGreen(mana.isGreen() || objectColor.isGreen());
|
||||||
mana.setRed(mana.isRed() || color.isRed());
|
mana.setRed(mana.isRed() || objectColor.isRed());
|
||||||
mana.setBlue(mana.isBlue() || color.isBlue());
|
mana.setBlue(mana.isBlue() || objectColor.isBlue());
|
||||||
mana.setWhite(mana.isWhite() || color.isWhite());
|
mana.setWhite(mana.isWhite() || objectColor.isWhite());
|
||||||
for (String rule : secondCard.getRules()) {
|
for (String rule : secondCard.getRules()) {
|
||||||
rule = rule.replaceAll("(?i)<i.*?</i>", ""); // Ignoring reminder text in italic
|
rule = rule.replaceAll("(?i)<i.*?</i>", ""); // Ignoring reminder text in italic
|
||||||
if (!mana.isBlack() && rule.matches(regexBlack)) {
|
if (!mana.isBlack() && rule.matches(regexBlack)) {
|
||||||
|
@ -1006,21 +1033,21 @@ public class Spell extends StackObjImpl implements Card {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StackObject createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets, int amount) {
|
public StackObject createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets, int amount) {
|
||||||
Spell copy = null;
|
Spell spellCopy = null;
|
||||||
GameEvent gameEvent = GameEvent.getEvent(EventType.COPY_STACKOBJECT, this.getId(), source.getSourceId(), newControllerId, amount);
|
GameEvent gameEvent = GameEvent.getEvent(EventType.COPY_STACKOBJECT, this.getId(), source.getSourceId(), newControllerId, amount);
|
||||||
if (game.replaceEvent(gameEvent)) {
|
if (game.replaceEvent(gameEvent)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < gameEvent.getAmount(); i++) {
|
for (int i = 0; i < gameEvent.getAmount(); i++) {
|
||||||
copy = this.copySpell(newControllerId);
|
spellCopy = this.copySpell(newControllerId);
|
||||||
game.getState().setZone(copy.getId(), Zone.STACK); // required for targeting ex: Nivmagus Elemental
|
game.getState().setZone(spellCopy.getId(), Zone.STACK); // required for targeting ex: Nivmagus Elemental
|
||||||
game.getStack().push(copy);
|
game.getStack().push(spellCopy);
|
||||||
if (chooseNewTargets) {
|
if (chooseNewTargets) {
|
||||||
copy.chooseNewTargets(game, newControllerId);
|
spellCopy.chooseNewTargets(game, newControllerId);
|
||||||
}
|
}
|
||||||
game.fireEvent(new GameEvent(EventType.COPIED_STACKOBJECT, copy.getId(), this.getId(), newControllerId));
|
game.fireEvent(new GameEvent(EventType.COPIED_STACKOBJECT, spellCopy.getId(), this.getId(), newControllerId));
|
||||||
}
|
}
|
||||||
return copy;
|
return spellCopy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Reference in a new issue