mirror of
https://github.com/correl/mage.git
synced 2024-12-26 03:00:11 +00:00
* Special mana payments like convoke/delve - fixed that it can't be used to cast card from graveyard (example: Hogaak, Arisen Necropolis, see #6680);
This commit is contained in:
parent
6754636f86
commit
cd624b2158
4 changed files with 129 additions and 80 deletions
|
@ -295,4 +295,52 @@ public class ConvokeTest extends CardTestPlayerBaseWithAIHelps {
|
||||||
execute();
|
execute();
|
||||||
assertAllCommandsUsed();
|
assertAllCommandsUsed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_Other_CastFromGraveayrd_Convoke() {
|
||||||
|
// https://github.com/magefree/mage/issues/6680
|
||||||
|
|
||||||
|
// {5}{B/G}{B/G}
|
||||||
|
// You can't spend mana to cast this spell.
|
||||||
|
// Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature's color.)
|
||||||
|
// Delve (Each card you exile from your graveyard while casting this spell pays for {1}.)
|
||||||
|
// You may cast Hogaak, Arisen Necropolis from your graveyard.
|
||||||
|
addCard(Zone.GRAVEYARD, playerA, "Hogaak, Arisen Necropolis", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 7);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Hogaak, Arisen Necropolis");
|
||||||
|
addTarget(playerA, "Balduvian Bears", 7); // convoke pay
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, "Hogaak, Arisen Necropolis", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_Other_CastFromGraveayrd_ConvokeAndDelve() {
|
||||||
|
// https://github.com/magefree/mage/issues/6680
|
||||||
|
|
||||||
|
// {5}{B/G}{B/G}
|
||||||
|
// You can't spend mana to cast this spell.
|
||||||
|
// Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature's color.)
|
||||||
|
// Delve (Each card you exile from your graveyard while casting this spell pays for {1}.)
|
||||||
|
// You may cast Hogaak, Arisen Necropolis from your graveyard.
|
||||||
|
addCard(Zone.GRAVEYARD, playerA, "Hogaak, Arisen Necropolis", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 2); // convoke (you can't pay normal mana here)
|
||||||
|
addCard(Zone.GRAVEYARD, playerA, "Balduvian Bears", 5); // delve
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Hogaak, Arisen Necropolis");
|
||||||
|
addTarget(playerA, "Balduvian Bears", 2); // convoke pay
|
||||||
|
setChoice(playerA, "Balduvian Bears", 5); // delve pay
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, "Hogaak, Arisen Necropolis", 1);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1711,7 +1711,7 @@ public class TestPlayer implements Player {
|
||||||
printAbilities(game, computerPlayer.getPlayable(game, true));
|
printAbilities(game, computerPlayer.getPlayable(game, true));
|
||||||
printEnd();
|
printEnd();
|
||||||
}
|
}
|
||||||
Assert.fail("Missing " + choiceType + " def for"
|
Assert.fail("Missing " + choiceType.toUpperCase(Locale.ENGLISH) + " def for"
|
||||||
+ " turn " + game.getTurnNum()
|
+ " turn " + game.getTurnNum()
|
||||||
+ ", step " + (game.getStep() != null ? game.getStep().getType().name() : "not started")
|
+ ", step " + (game.getStep() != null ? game.getStep().getType().name() : "not started")
|
||||||
+ ", " + this.getName()
|
+ ", " + this.getName()
|
||||||
|
|
|
@ -1735,8 +1735,14 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
||||||
*/
|
*/
|
||||||
// TODO: mode options doesn't work here (see BrutalExpulsionTest)
|
// TODO: mode options doesn't work here (see BrutalExpulsionTest)
|
||||||
public void addTarget(TestPlayer player, String target) {
|
public void addTarget(TestPlayer player, String target) {
|
||||||
|
addTarget(player, target, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTarget(TestPlayer player, String target, int timesToChoose) {
|
||||||
|
for (int i = 0; i < timesToChoose; i++) {
|
||||||
player.addTarget(target);
|
player.addTarget(target);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a player as target
|
* Sets a player as target
|
||||||
|
@ -1745,8 +1751,14 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
||||||
* @param targetPlayer
|
* @param targetPlayer
|
||||||
*/
|
*/
|
||||||
public void addTarget(TestPlayer player, TestPlayer targetPlayer) {
|
public void addTarget(TestPlayer player, TestPlayer targetPlayer) {
|
||||||
|
addTarget(player, targetPlayer, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTarget(TestPlayer player, TestPlayer targetPlayer, int timesToChoose) {
|
||||||
|
for (int i = 0; i < timesToChoose; i++) {
|
||||||
player.addTarget("targetPlayer=" + targetPlayer.getName());
|
player.addTarget("targetPlayer=" + targetPlayer.getName());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param player
|
* @param player
|
||||||
|
|
|
@ -69,6 +69,7 @@ import org.apache.log4j.Logger;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public abstract class PlayerImpl implements Player, Serializable {
|
public abstract class PlayerImpl implements Player, Serializable {
|
||||||
|
|
||||||
|
@ -3105,6 +3106,19 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ActivatedAbility findActivatedAbilityFromPlayable(Card card, ManaOptions manaAvailable, Ability ability, Game game) {
|
protected ActivatedAbility findActivatedAbilityFromPlayable(Card card, ManaOptions manaAvailable, Ability ability, Game game) {
|
||||||
|
|
||||||
|
// special mana to pay spell cost
|
||||||
|
ManaOptions manaFull = manaAvailable.copy();
|
||||||
|
if (ability instanceof SpellAbility) {
|
||||||
|
for (AlternateManaPaymentAbility altAbility : card.getAbilities(game).stream()
|
||||||
|
.filter(a -> a instanceof AlternateManaPaymentAbility)
|
||||||
|
.map(a -> (AlternateManaPaymentAbility) a)
|
||||||
|
.collect(Collectors.toList())) {
|
||||||
|
ManaOptions manaSpecial = altAbility.getManaOptions(ability, game, ability.getManaCostsToPay());
|
||||||
|
manaFull.addMana(manaSpecial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// replace alternative abilities by real play abilities (e.g. morph/facedown static ability by play land)
|
// replace alternative abilities by real play abilities (e.g. morph/facedown static ability by play land)
|
||||||
if (ability instanceof ActivatedManaAbilityImpl) {
|
if (ability instanceof ActivatedManaAbilityImpl) {
|
||||||
// mana ability
|
// mana ability
|
||||||
|
@ -3113,13 +3127,10 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
}
|
}
|
||||||
} else if (ability instanceof AlternativeSourceCosts) {
|
} else if (ability instanceof AlternativeSourceCosts) {
|
||||||
// alternative cost must be replaced by real play ability
|
// alternative cost must be replaced by real play ability
|
||||||
return findActivatedAbilityFromAlternativeSourceCost(card, manaAvailable, ability, game);
|
return findActivatedAbilityFromAlternativeSourceCost(card, manaFull, ability, game);
|
||||||
} else if (ability instanceof AlternateManaPaymentAbility) {
|
|
||||||
// alternative mana pay like convoke (tap creature to pay)
|
|
||||||
return findActivatedAbilityFromAlternateManaPaymentAbility(card, manaAvailable, (AlternateManaPaymentAbility) ability, game);
|
|
||||||
} else if (ability instanceof ActivatedAbility) {
|
} else if (ability instanceof ActivatedAbility) {
|
||||||
// all other activated ability
|
// all other activated ability
|
||||||
if (canPlay((ActivatedAbility) ability, manaAvailable, card, game)) {
|
if (canPlay((ActivatedAbility) ability, manaFull, card, game)) {
|
||||||
return (ActivatedAbility) ability;
|
return (ActivatedAbility) ability;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3151,20 +3162,6 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ActivatedAbility findActivatedAbilityFromAlternateManaPaymentAbility(Card card, ManaOptions manaAvailable, AlternateManaPaymentAbility ability, Game game) {
|
|
||||||
// alternative mana payment allows to pay mana for spell ability
|
|
||||||
SpellAbility spellAbility = card.getSpellAbility();
|
|
||||||
if (spellAbility != null) {
|
|
||||||
ManaOptions manaSpecial = ability.getManaOptions(spellAbility, game, spellAbility.getManaCostsToPay());
|
|
||||||
ManaOptions manaFull = manaAvailable.copy();
|
|
||||||
manaFull.addMana(manaSpecial);
|
|
||||||
if (canPlay(spellAbility, manaFull, card, game)) {
|
|
||||||
return spellAbility;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean canLandPlayAlternateSourceCostsAbility(Card sourceObject, ManaOptions available, Ability ability, Game game) {
|
protected boolean canLandPlayAlternateSourceCostsAbility(Card sourceObject, ManaOptions available, Ability ability, Game game) {
|
||||||
if (sourceObject != null && !(sourceObject instanceof Permanent)) {
|
if (sourceObject != null && !(sourceObject instanceof Permanent)) {
|
||||||
Ability sourceAbility = sourceObject.getAbilities().stream()
|
Ability sourceAbility = sourceObject.getAbilities().stream()
|
||||||
|
@ -3197,15 +3194,6 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Abilities<ActivatedAbility> getActivatedOnly(Abilities<Ability> list) {
|
|
||||||
Abilities<ActivatedAbility> res = new AbilitiesImpl<>();
|
|
||||||
list.stream()
|
|
||||||
.filter(a -> a instanceof ActivatedAbility)
|
|
||||||
.map(a -> (ActivatedAbility) a)
|
|
||||||
.forEach(res::add);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void getPlayableFromCardAll(Game game, Zone fromZone, Card card, ManaOptions availableMana, List<ActivatedAbility> output) {
|
private void getPlayableFromCardAll(Game game, Zone fromZone, Card card, ManaOptions availableMana, List<ActivatedAbility> output) {
|
||||||
if (fromZone == null || card == null) {
|
if (fromZone == null || card == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -3214,24 +3202,25 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
// BASIC abilities
|
// BASIC abilities
|
||||||
if (card instanceof SplitCard) {
|
if (card instanceof SplitCard) {
|
||||||
SplitCard splitCard = (SplitCard) card;
|
SplitCard splitCard = (SplitCard) card;
|
||||||
getPlayableFromCardSingle(game, fromZone, splitCard.getLeftHalfCard(), getActivatedOnly(splitCard.getLeftHalfCard().getAbilities(game)), availableMana, output);
|
getPlayableFromCardSingle(game, fromZone, splitCard.getLeftHalfCard(), splitCard.getLeftHalfCard().getAbilities(game), availableMana, output);
|
||||||
getPlayableFromCardSingle(game, fromZone, splitCard.getRightHalfCard(), getActivatedOnly(splitCard.getRightHalfCard().getAbilities(game)), availableMana, output);
|
getPlayableFromCardSingle(game, fromZone, splitCard.getRightHalfCard(), splitCard.getRightHalfCard().getAbilities(game), availableMana, output);
|
||||||
getPlayableFromCardSingle(game, fromZone, splitCard, getActivatedOnly(splitCard.getSharedAbilities(game)), availableMana, output);
|
getPlayableFromCardSingle(game, fromZone, splitCard, splitCard.getSharedAbilities(game), availableMana, output);
|
||||||
} else if (card instanceof AdventureCard) {
|
} else if (card instanceof AdventureCard) {
|
||||||
// adventure must use different card characteristics for different spells (main or adventure)
|
// adventure must use different card characteristics for different spells (main or adventure)
|
||||||
AdventureCard adventureCard = (AdventureCard) card;
|
AdventureCard adventureCard = (AdventureCard) card;
|
||||||
getPlayableFromCardSingle(game, fromZone, adventureCard.getSpellCard(), getActivatedOnly(adventureCard.getSpellCard().getAbilities(game)), availableMana, output);
|
getPlayableFromCardSingle(game, fromZone, adventureCard.getSpellCard(), adventureCard.getSpellCard().getAbilities(game), availableMana, output);
|
||||||
getPlayableFromCardSingle(game, fromZone, adventureCard, getActivatedOnly(adventureCard.getSharedAbilities(game)), availableMana, output);
|
getPlayableFromCardSingle(game, fromZone, adventureCard, adventureCard.getSharedAbilities(game), availableMana, output);
|
||||||
} else {
|
} else {
|
||||||
getPlayableFromCardSingle(game, fromZone, card, getActivatedOnly(card.getAbilities(game)), availableMana, output);
|
getPlayableFromCardSingle(game, fromZone, card, card.getAbilities(game), availableMana, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DYNAMIC ADDED abilities are adds in getAbilities(game)
|
// DYNAMIC ADDED abilities are adds in getAbilities(game)
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getPlayableFromCardSingle(Game game, Zone fromZone, Card card, Abilities<ActivatedAbility> candidateAbilities, ManaOptions availableMana, List<ActivatedAbility> output) {
|
private void getPlayableFromCardSingle(Game game, Zone fromZone, Card card, Abilities<Ability> candidateAbilities, ManaOptions availableMana, List<ActivatedAbility> output) {
|
||||||
// check "can play" condition as affected controller (BUT play from not own hand zone must be checked as original controller)
|
// check "can play" condition as affected controller (BUT play from not own hand zone must be checked as original controller)
|
||||||
for (ActivatedAbility ability : candidateAbilities.getActivatedAbilities(Zone.ALL)) {
|
// must check all abilities, not activated only
|
||||||
|
for (Ability ability : candidateAbilities) {
|
||||||
boolean isPlaySpell = (ability instanceof SpellAbility);
|
boolean isPlaySpell = (ability instanceof SpellAbility);
|
||||||
boolean isPlayLand = (ability instanceof PlayLandAbility);
|
boolean isPlayLand = (ability instanceof PlayLandAbility);
|
||||||
|
|
||||||
|
@ -3295,7 +3284,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
// from non hand mode (with affected controller)
|
// from non hand mode (with affected controller)
|
||||||
if (canActivateAsHandZone) {
|
if (canActivateAsHandZone && ability.getControllerId() != this.getId()) {
|
||||||
UUID savedControllerId = ability.getControllerId();
|
UUID savedControllerId = ability.getControllerId();
|
||||||
ability.setControllerId(this.getId());
|
ability.setControllerId(this.getId());
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Reference in a new issue