mirror of
https://github.com/magefree/mage.git
synced 2025-12-23 12:02:01 -08:00
Reworked and improved special mana payment abilities (convoke, delve, assist, improvise):
* now it can be used to calc and find available mana and playable abilities; * now tests and AI can use that abilities; * now it follows mtg's rules and restrictions for mana activation order (rule 601.2f, see #768);
This commit is contained in:
parent
bdaf6454de
commit
c2e7b02e13
9 changed files with 341 additions and 177 deletions
|
|
@ -10,6 +10,7 @@ import mage.abilities.costs.mana.ManaCostsImpl;
|
||||||
import mage.abilities.effects.RequirementEffect;
|
import mage.abilities.effects.RequirementEffect;
|
||||||
import mage.abilities.hint.HintUtils;
|
import mage.abilities.hint.HintUtils;
|
||||||
import mage.abilities.mana.ActivatedManaAbilityImpl;
|
import mage.abilities.mana.ActivatedManaAbilityImpl;
|
||||||
|
import mage.abilities.mana.ManaAbility;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
import mage.cards.Cards;
|
import mage.cards.Cards;
|
||||||
import mage.cards.decks.Deck;
|
import mage.cards.decks.Deck;
|
||||||
|
|
@ -61,6 +62,8 @@ import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_RESET_ALL;
|
||||||
*/
|
*/
|
||||||
public class HumanPlayer extends PlayerImpl {
|
public class HumanPlayer extends PlayerImpl {
|
||||||
|
|
||||||
|
private static final boolean ALLOW_USERS_TO_PUT_NON_PLAYABLE_SPELLS_ON_STACK_WORKAROUND = false; // warning, see workaround's info on usage
|
||||||
|
|
||||||
private transient Boolean responseOpenedForAnswer = false; // can't get response until prepared target (e.g. until send all fire events to all players)
|
private transient Boolean responseOpenedForAnswer = false; // can't get response until prepared target (e.g. until send all fire events to all players)
|
||||||
private final transient PlayerResponse response = new PlayerResponse();
|
private final transient PlayerResponse response = new PlayerResponse();
|
||||||
|
|
||||||
|
|
@ -1039,7 +1042,7 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
|
|
||||||
if (response.getString() != null
|
if (response.getString() != null
|
||||||
&& response.getString().equals("special")) {
|
&& response.getString().equals("special")) {
|
||||||
specialAction(game);
|
activateSpecialAction(game, null);
|
||||||
} else if (response.getUUID() != null) {
|
} else if (response.getUUID() != null) {
|
||||||
boolean result = false;
|
boolean result = false;
|
||||||
MageObject object = game.getObject(response.getUUID());
|
MageObject object = game.getObject(response.getUUID());
|
||||||
|
|
@ -1057,6 +1060,24 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
}
|
}
|
||||||
if (actingPlayer != null) {
|
if (actingPlayer != null) {
|
||||||
useableAbilities = actingPlayer.getPlayableActivatedAbilities(object, zone, game);
|
useableAbilities = actingPlayer.getPlayableActivatedAbilities(object, zone, game);
|
||||||
|
|
||||||
|
// GUI: workaround to enable users to put spells on stack without real available mana
|
||||||
|
// (without highlighting, like it was in old versions before June 2020)
|
||||||
|
// Reason: some gain ability adds cost modification and other things to spells on stack only,
|
||||||
|
// e.g. xmage can't find playable ability before put that spell on stack (wtf example: Chief Engineer,
|
||||||
|
// see ConvokeTest)
|
||||||
|
// TODO: it's a BAD workaround -- users can't see that card/ability is broken and will not report to us, AI can't play that ability too
|
||||||
|
// Enable it on massive broken cards/abilities only or for manual tests
|
||||||
|
if (ALLOW_USERS_TO_PUT_NON_PLAYABLE_SPELLS_ON_STACK_WORKAROUND) {
|
||||||
|
if (object instanceof Card) {
|
||||||
|
for (Ability ability : ((Card) object).getAbilities(game)) {
|
||||||
|
if (ability instanceof SpellAbility && ((SpellAbility) ability).canActivate(actingPlayer.getId(), game).canActivate()
|
||||||
|
|| ability instanceof PlayLandAbility) {
|
||||||
|
useableAbilities.putIfAbsent(ability.getId(), (ActivatedAbility) ability);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (object instanceof Card
|
if (object instanceof Card
|
||||||
|
|
@ -1221,7 +1242,7 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
} else if (response.getString() != null
|
} else if (response.getString() != null
|
||||||
&& response.getString().equals("special")) {
|
&& response.getString().equals("special")) {
|
||||||
if (unpaid instanceof ManaCostsImpl) {
|
if (unpaid instanceof ManaCostsImpl) {
|
||||||
specialManaAction(unpaid, game);
|
activateSpecialAction(game, unpaid);
|
||||||
}
|
}
|
||||||
} else if (response.getManaType() != null) {
|
} else if (response.getManaType() != null) {
|
||||||
// this mana type can be paid once from pool
|
// this mana type can be paid once from pool
|
||||||
|
|
@ -1336,14 +1357,26 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
if (object == null) {
|
if (object == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (AbilityType.SPELL.equals(abilityToCast.getAbilityType())) {
|
|
||||||
|
// GUI: for user's information only - check if mana abilities allows to use here (getUseableManaAbilities already filter it)
|
||||||
|
// Reason: when you use special mana ability then normal mana abilities will be restricted to pay. Users
|
||||||
|
// can't see lands as playable and must know the reason (if they click on land then they get that message)
|
||||||
|
if (abilityToCast.getAbilityType() == AbilityType.SPELL) {
|
||||||
Spell spell = game.getStack().getSpell(abilityToCast.getSourceId());
|
Spell spell = game.getStack().getSpell(abilityToCast.getSourceId());
|
||||||
if (spell != null && !spell.isResolving()
|
boolean haveManaAbilities = object.getAbilities().stream().anyMatch(a -> a instanceof ManaAbility);
|
||||||
&& spell.isDoneActivatingManaAbilities()) {
|
if (spell != null && !spell.isResolving() && haveManaAbilities) {
|
||||||
game.informPlayer(this, "You can no longer use activated mana abilities to pay for the current spell. Cancel and recast the spell and activate mana abilities first.");
|
switch (spell.getCurrentActivatingManaAbilitiesStep()) {
|
||||||
return;
|
// if you used special mana ability like convoke then normal mana abilities will be restricted to use, see Convoke for details
|
||||||
|
case BEFORE:
|
||||||
|
case NORMAL:
|
||||||
|
break;
|
||||||
|
case AFTER:
|
||||||
|
game.informPlayer(this, "You can no longer use activated mana abilities to pay for the current spell (special mana pay already used). Cancel and recast the spell to activate mana abilities first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Zone zone = game.getState().getZone(object.getId());
|
Zone zone = game.getState().getZone(object.getId());
|
||||||
if (zone != null) {
|
if (zone != null) {
|
||||||
LinkedHashMap<UUID, ActivatedManaAbilityImpl> useableAbilities = getUseableManaAbilities(object, zone, game);
|
LinkedHashMap<UUID, ActivatedManaAbilityImpl> useableAbilities = getUseableManaAbilities(object, zone, game);
|
||||||
|
|
@ -1844,7 +1877,13 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
draft.firePickCardEvent(playerId);
|
draft.firePickCardEvent(playerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void specialAction(Game game) {
|
/**
|
||||||
|
* Activate special action (normal or mana)
|
||||||
|
*
|
||||||
|
* @param game
|
||||||
|
* @param unpaidForManaAction - set unpaid for mana actions like convoke
|
||||||
|
*/
|
||||||
|
protected void activateSpecialAction(Game game, ManaCost unpaidForManaAction) {
|
||||||
if (gameInCheckPlayableState(game)) {
|
if (gameInCheckPlayableState(game)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1853,7 +1892,7 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<UUID, SpecialAction> specialActions = game.getState().getSpecialActions().getControlledBy(playerId, false);
|
Map<UUID, SpecialAction> specialActions = game.getState().getSpecialActions().getControlledBy(playerId, unpaidForManaAction != null);
|
||||||
if (!specialActions.isEmpty()) {
|
if (!specialActions.isEmpty()) {
|
||||||
|
|
||||||
updateGameStatePriority("specialAction", game);
|
updateGameStatePriority("specialAction", game);
|
||||||
|
|
@ -1863,39 +1902,13 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
}
|
}
|
||||||
waitForResponse(game);
|
waitForResponse(game);
|
||||||
|
|
||||||
if (response.getUUID() != null) {
|
|
||||||
if (specialActions.containsKey(response.getUUID())) {
|
|
||||||
activateAbility(specialActions.get(response.getUUID()), game);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void specialManaAction(ManaCost unpaid, Game game) {
|
|
||||||
if (gameInCheckPlayableState(game)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!canRespond()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<UUID, SpecialAction> specialActions = game.getState().getSpecialActions().getControlledBy(playerId, true);
|
|
||||||
if (!specialActions.isEmpty()) {
|
|
||||||
updateGameStatePriority("specialAction", game);
|
|
||||||
prepareForResponse(game);
|
|
||||||
if (!isExecutingMacro()) {
|
|
||||||
game.fireGetChoiceEvent(playerId, name, null, new ArrayList<>(specialActions.values()));
|
|
||||||
}
|
|
||||||
waitForResponse(game);
|
|
||||||
|
|
||||||
if (response.getUUID() != null) {
|
if (response.getUUID() != null) {
|
||||||
if (specialActions.containsKey(response.getUUID())) {
|
if (specialActions.containsKey(response.getUUID())) {
|
||||||
SpecialAction specialAction = specialActions.get(response.getUUID());
|
SpecialAction specialAction = specialActions.get(response.getUUID());
|
||||||
if (specialAction != null) {
|
if (unpaidForManaAction != null) {
|
||||||
specialAction.setUnpaidMana(unpaid);
|
specialAction.setUnpaidMana(unpaidForManaAction);
|
||||||
activateAbility(specialActions.get(response.getUUID()), game);
|
|
||||||
}
|
}
|
||||||
|
activateAbility(specialAction, game);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -273,7 +273,7 @@ public class PlayFromNonHandZoneTest extends CardTestPlayerBase {
|
||||||
* the discard was required. In the first log, when it was cast after
|
* the discard was required. In the first log, when it was cast after
|
||||||
* Angelic Purge the discard was not required.
|
* Angelic Purge the discard was not required.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void castFromExileButWithAdditionalCostTest() {
|
public void castFromExileButWithAdditionalCostTest() {
|
||||||
// Ninjutsu {2}{U}{B}
|
// Ninjutsu {2}{U}{B}
|
||||||
|
|
@ -282,7 +282,7 @@ public class PlayFromNonHandZoneTest extends CardTestPlayerBase {
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Fallen Shinobi", 1); // Creature 5/4
|
addCard(Zone.BATTLEFIELD, playerB, "Fallen Shinobi", 1); // Creature 5/4
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");
|
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");
|
||||||
addCard(Zone.HAND, playerB, "Pillarfield Ox");
|
addCard(Zone.HAND, playerB, "Pillarfield Ox");
|
||||||
|
|
||||||
addCard(Zone.LIBRARY, playerB, "Pillarfield Ox"); // Card to draw on turn 2
|
addCard(Zone.LIBRARY, playerB, "Pillarfield Ox"); // Card to draw on turn 2
|
||||||
// As an additional cost to cast Tormenting Voice, discard a card.
|
// As an additional cost to cast Tormenting Voice, discard a card.
|
||||||
// Draw two cards.
|
// Draw two cards.
|
||||||
|
|
@ -290,14 +290,14 @@ public class PlayFromNonHandZoneTest extends CardTestPlayerBase {
|
||||||
// As an additional cost to cast this spell, sacrifice a creature.
|
// As an additional cost to cast this spell, sacrifice a creature.
|
||||||
// Flying, Trample
|
// Flying, Trample
|
||||||
addCard(Zone.LIBRARY, playerA, "Demon of Catastrophes"); // Creature {2}{B}{B} 6/6
|
addCard(Zone.LIBRARY, playerA, "Demon of Catastrophes"); // Creature {2}{B}{B} 6/6
|
||||||
|
|
||||||
skipInitShuffling();
|
skipInitShuffling();
|
||||||
|
|
||||||
attack(2, playerB, "Fallen Shinobi");
|
attack(2, playerB, "Fallen Shinobi");
|
||||||
|
|
||||||
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Tormenting Voice");
|
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Tormenting Voice");
|
||||||
setChoice(playerB, "Pillarfield Ox"); // Discord for Tormenting Voice
|
setChoice(playerB, "Pillarfield Ox"); // Discord for Tormenting Voice
|
||||||
|
|
||||||
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Demon of Catastrophes");
|
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Demon of Catastrophes");
|
||||||
setChoice(playerB, "Silvercoat Lion"); // Sacrifice for Demon
|
setChoice(playerB, "Silvercoat Lion"); // Sacrifice for Demon
|
||||||
|
|
||||||
|
|
@ -309,20 +309,20 @@ public class PlayFromNonHandZoneTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
assertLife(playerA, 15);
|
assertLife(playerA, 15);
|
||||||
assertPermanentCount(playerB, "Fallen Shinobi", 1);
|
assertPermanentCount(playerB, "Fallen Shinobi", 1);
|
||||||
|
|
||||||
assertGraveyardCount(playerA, "Tormenting Voice", 1);
|
assertGraveyardCount(playerA, "Tormenting Voice", 1);
|
||||||
assertGraveyardCount(playerB, "Pillarfield Ox", 1); // Discarded for Tormenting Voice
|
assertGraveyardCount(playerB, "Pillarfield Ox", 1); // Discarded for Tormenting Voice
|
||||||
|
|
||||||
|
|
||||||
assertPermanentCount(playerB, "Demon of Catastrophes", 1);
|
assertPermanentCount(playerB, "Demon of Catastrophes", 1);
|
||||||
assertGraveyardCount(playerB, "Silvercoat Lion", 1); // sacrificed for Demon
|
assertGraveyardCount(playerB, "Silvercoat Lion", 1); // sacrificed for Demon
|
||||||
|
|
||||||
assertHandCount(playerB, "Pillarfield Ox", 1);
|
assertHandCount(playerB, "Pillarfield Ox", 1);
|
||||||
assertHandCount(playerB, 3); // 2 from Tormenting Voice + 1 from Turn 2
|
assertHandCount(playerB, 3); // 2 from Tormenting Voice + 1 from Turn 2
|
||||||
assertExileCount(playerA, 0); // Both exiled cards are cast
|
assertExileCount(playerA, 0); // Both exiled cards are cast
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void castFromExileButWithAdditionalCost2Test() {
|
public void castFromExileButWithAdditionalCost2Test() {
|
||||||
// Ninjutsu {2}{U}{B}
|
// Ninjutsu {2}{U}{B}
|
||||||
|
|
@ -331,7 +331,7 @@ public class PlayFromNonHandZoneTest extends CardTestPlayerBase {
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Fallen Shinobi", 1); // Creature 5/4
|
addCard(Zone.BATTLEFIELD, playerB, "Fallen Shinobi", 1); // Creature 5/4
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");
|
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");
|
||||||
addCard(Zone.HAND, playerB, "Pillarfield Ox");
|
addCard(Zone.HAND, playerB, "Pillarfield Ox");
|
||||||
|
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Amulet of Kroog"); // Just to exile for Angelic Purge
|
addCard(Zone.BATTLEFIELD, playerA, "Amulet of Kroog"); // Just to exile for Angelic Purge
|
||||||
|
|
||||||
// As an additional cost to cast Tormenting Voice, discard a card.
|
// As an additional cost to cast Tormenting Voice, discard a card.
|
||||||
|
|
@ -341,18 +341,18 @@ public class PlayFromNonHandZoneTest extends CardTestPlayerBase {
|
||||||
// As an additional cost to cast Angelic Purge, sacrifice a permanent.
|
// As an additional cost to cast Angelic Purge, sacrifice a permanent.
|
||||||
// Exile target artifact, creature, or enchantment.
|
// Exile target artifact, creature, or enchantment.
|
||||||
addCard(Zone.LIBRARY, playerA, "Angelic Purge"); // Sorcery {2}{W}
|
addCard(Zone.LIBRARY, playerA, "Angelic Purge"); // Sorcery {2}{W}
|
||||||
|
|
||||||
skipInitShuffling();
|
skipInitShuffling();
|
||||||
|
|
||||||
attack(2, playerB, "Fallen Shinobi");
|
attack(2, playerB, "Fallen Shinobi");
|
||||||
|
|
||||||
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Angelic Purge");
|
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Angelic Purge");
|
||||||
setChoice(playerB, "Silvercoat Lion"); // Sacrifice for Purge
|
setChoice(playerB, "Silvercoat Lion"); // Sacrifice for Purge
|
||||||
addTarget(playerB, "Amulet of Kroog"); // Exile with Purge
|
addTarget(playerB, "Amulet of Kroog"); // Exile with Purge
|
||||||
|
|
||||||
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Tormenting Voice");
|
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Tormenting Voice");
|
||||||
setChoice(playerB, "Pillarfield Ox"); // Discord for Tormenting Voice
|
setChoice(playerB, "Pillarfield Ox"); // Discord for Tormenting Voice
|
||||||
|
|
||||||
setStopAt(2, PhaseStep.END_TURN);
|
setStopAt(2, PhaseStep.END_TURN);
|
||||||
|
|
||||||
setStrictChooseMode(true);
|
setStrictChooseMode(true);
|
||||||
|
|
@ -361,27 +361,27 @@ public class PlayFromNonHandZoneTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
assertLife(playerA, 15);
|
assertLife(playerA, 15);
|
||||||
assertPermanentCount(playerB, "Fallen Shinobi", 1);
|
assertPermanentCount(playerB, "Fallen Shinobi", 1);
|
||||||
|
|
||||||
assertGraveyardCount(playerA, "Angelic Purge", 1);
|
|
||||||
assertGraveyardCount(playerB, "Silvercoat Lion", 1); // sacrificed for Purge
|
|
||||||
|
|
||||||
assertGraveyardCount(playerA, "Tormenting Voice", 1);
|
assertGraveyardCount(playerA, "Angelic Purge", 1);
|
||||||
assertGraveyardCount(playerB, "Pillarfield Ox", 1); // Discarded for Tormenting Voice
|
assertGraveyardCount(playerB, "Silvercoat Lion", 1); // sacrificed for Purge
|
||||||
|
|
||||||
|
assertGraveyardCount(playerA, "Tormenting Voice", 1);
|
||||||
|
assertGraveyardCount(playerB, "Pillarfield Ox", 1); // Discarded for Tormenting Voice
|
||||||
|
|
||||||
assertHandCount(playerB, 3); // 2 from Tormenting Voice + 1 from Turn 2 draw
|
assertHandCount(playerB, 3); // 2 from Tormenting Voice + 1 from Turn 2 draw
|
||||||
|
|
||||||
assertExileCount(playerA, 1); // Both exiled cards are cast
|
assertExileCount(playerA, 1); // Both exiled cards are cast
|
||||||
assertExileCount(playerA, "Amulet of Kroog", 1); // Exiled with Purge
|
assertExileCount(playerA, "Amulet of Kroog", 1); // Exiled with Purge
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void castAdventureWithFallenShinobiTest() {
|
public void castAdventureWithFallenShinobiTest() {
|
||||||
// Ninjutsu {2}{U}{B}
|
// Ninjutsu {2}{U}{B}
|
||||||
// Whenever Fallen Shinobi deals combat damage to a player, that player exiles the top two cards
|
// Whenever Fallen Shinobi deals combat damage to a player, that player exiles the top two cards
|
||||||
// of their library. Until end of turn, you may play those cards without paying their mana cost.
|
// of their library. Until end of turn, you may play those cards without paying their mana cost.
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Fallen Shinobi", 1); // Creature 5/4
|
addCard(Zone.BATTLEFIELD, playerB, "Fallen Shinobi", 1); // Creature 5/4
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");
|
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");
|
||||||
|
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Amulet of Kroog"); // Just to exile for Angelic Purge
|
addCard(Zone.BATTLEFIELD, playerA, "Amulet of Kroog"); // Just to exile for Angelic Purge
|
||||||
|
|
||||||
/* Curious Pair {1}{G}
|
/* Curious Pair {1}{G}
|
||||||
|
|
@ -391,23 +391,23 @@ public class PlayFromNonHandZoneTest extends CardTestPlayerBase {
|
||||||
* Treats to Share {G}
|
* Treats to Share {G}
|
||||||
* Sorcery — Adventure
|
* Sorcery — Adventure
|
||||||
* Create a Food token.
|
* Create a Food token.
|
||||||
*/
|
*/
|
||||||
addCard(Zone.LIBRARY, playerA, "Curious Pair");
|
addCard(Zone.LIBRARY, playerA, "Curious Pair");
|
||||||
|
|
||||||
// As an additional cost to cast Angelic Purge, sacrifice a permanent.
|
// As an additional cost to cast Angelic Purge, sacrifice a permanent.
|
||||||
// Exile target artifact, creature, or enchantment.
|
// Exile target artifact, creature, or enchantment.
|
||||||
addCard(Zone.LIBRARY, playerA, "Angelic Purge"); // Sorcery {2}{W}
|
addCard(Zone.LIBRARY, playerA, "Angelic Purge"); // Sorcery {2}{W}
|
||||||
|
|
||||||
skipInitShuffling();
|
skipInitShuffling();
|
||||||
|
|
||||||
attack(2, playerB, "Fallen Shinobi");
|
attack(2, playerB, "Fallen Shinobi");
|
||||||
|
|
||||||
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Angelic Purge");
|
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Angelic Purge");
|
||||||
setChoice(playerB, "Silvercoat Lion"); // Sacrifice for Purge
|
setChoice(playerB, "Silvercoat Lion"); // Sacrifice for Purge
|
||||||
addTarget(playerB, "Amulet of Kroog"); // Exile with Purge
|
addTarget(playerB, "Amulet of Kroog"); // Exile with Purge
|
||||||
|
|
||||||
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Treats to Share");
|
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Treats to Share");
|
||||||
|
|
||||||
setStopAt(2, PhaseStep.END_TURN);
|
setStopAt(2, PhaseStep.END_TURN);
|
||||||
|
|
||||||
setStrictChooseMode(true);
|
setStrictChooseMode(true);
|
||||||
|
|
@ -416,17 +416,46 @@ public class PlayFromNonHandZoneTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
assertLife(playerA, 15);
|
assertLife(playerA, 15);
|
||||||
assertPermanentCount(playerB, "Fallen Shinobi", 1);
|
assertPermanentCount(playerB, "Fallen Shinobi", 1);
|
||||||
|
|
||||||
assertGraveyardCount(playerA, "Angelic Purge", 1);
|
assertGraveyardCount(playerA, "Angelic Purge", 1);
|
||||||
assertGraveyardCount(playerB, "Silvercoat Lion", 1); // sacrificed for Purge
|
assertGraveyardCount(playerB, "Silvercoat Lion", 1); // sacrificed for Purge
|
||||||
|
|
||||||
assertPermanentCount(playerB, "Food", 1);
|
assertPermanentCount(playerB, "Food", 1);
|
||||||
assertExileCount(playerA, "Curious Pair", 1);
|
assertExileCount(playerA, "Curious Pair", 1);
|
||||||
|
|
||||||
assertHandCount(playerB, 1); // 1 from Turn 2 draw
|
assertHandCount(playerB, 1); // 1 from Turn 2 draw
|
||||||
|
|
||||||
assertExileCount(playerA, 2); // Both exiled cards are cast
|
assertExileCount(playerA, 2); // Both exiled cards are cast
|
||||||
assertExileCount(playerA, "Amulet of Kroog", 1); // Exiled with Purge
|
assertExileCount(playerA, "Amulet of Kroog", 1); // Exiled with Purge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_ActivateFromOpponentCreature() {
|
||||||
|
// Players can’t search libraries. Any player may pay {2} for that player to ignore this effect until end of turn.
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Leonin Arbiter", 1);
|
||||||
|
//
|
||||||
|
// {3}{U}{U}
|
||||||
|
// Search target opponent’s library for an artifact card and put that card onto the battlefield under your control. Then that player shuffles their library.
|
||||||
|
addCard(Zone.HAND, playerA, "Acquire", 2);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Island", 5 * 2 + 2);
|
||||||
|
addCard(Zone.LIBRARY, playerB, "Alpha Myr", 1);
|
||||||
|
|
||||||
|
// first cast -- can't search library
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Acquire", playerB);
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
|
||||||
|
// second cast -- unlock library and search
|
||||||
|
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}: Any player may");
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
//
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Acquire", playerB);
|
||||||
|
addTarget(playerA, "Alpha Myr");
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, "Alpha Myr", 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,22 @@
|
||||||
|
|
||||||
|
|
||||||
package mage.abilities;
|
package mage.abilities;
|
||||||
|
|
||||||
|
import mage.abilities.costs.mana.AlternateManaPaymentAbility;
|
||||||
import mage.abilities.costs.mana.ManaCost;
|
import mage.abilities.costs.mana.ManaCost;
|
||||||
|
import mage.abilities.mana.ManaOptions;
|
||||||
import mage.constants.AbilityType;
|
import mage.constants.AbilityType;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.game.stack.Spell;
|
||||||
|
import mage.game.stack.StackObject;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||||
* @author BetaSteward_at_googlemail.com
|
|
||||||
*/
|
*/
|
||||||
public abstract class SpecialAction extends ActivatedAbilityImpl {
|
public abstract class SpecialAction extends ActivatedAbilityImpl {
|
||||||
|
|
||||||
private boolean manaAction;
|
private final AlternateManaPaymentAbility manaAbility; // mana actions generates on every pay cycle, no need to copy it
|
||||||
protected ManaCost unpaidMana;
|
protected ManaCost unpaidMana;
|
||||||
|
|
||||||
public SpecialAction() {
|
public SpecialAction() {
|
||||||
|
|
@ -20,22 +24,23 @@ public abstract class SpecialAction extends ActivatedAbilityImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
public SpecialAction(Zone zone) {
|
public SpecialAction(Zone zone) {
|
||||||
this(zone, false);
|
this(zone, null);
|
||||||
}
|
}
|
||||||
public SpecialAction(Zone zone, boolean manaAction) {
|
|
||||||
|
public SpecialAction(Zone zone, AlternateManaPaymentAbility manaAbility) {
|
||||||
super(AbilityType.SPECIAL_ACTION, zone);
|
super(AbilityType.SPECIAL_ACTION, zone);
|
||||||
this.usesStack = false;
|
this.usesStack = false;
|
||||||
this.manaAction = manaAction;
|
this.manaAbility = manaAbility;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SpecialAction(final SpecialAction action) {
|
public SpecialAction(final SpecialAction action) {
|
||||||
super(action);
|
super(action);
|
||||||
this.manaAction = action.manaAction;
|
|
||||||
this.unpaidMana = action.unpaidMana;
|
this.unpaidMana = action.unpaidMana;
|
||||||
|
this.manaAbility = action.manaAbility;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isManaAction() {
|
public boolean isManaAction() {
|
||||||
return manaAction;
|
return manaAbility != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUnpaidMana(ManaCost manaCost) {
|
public void setUnpaidMana(ManaCost manaCost) {
|
||||||
|
|
@ -45,4 +50,29 @@ public abstract class SpecialAction extends ActivatedAbilityImpl {
|
||||||
public ManaCost getUnpaidMana() {
|
public ManaCost getUnpaidMana() {
|
||||||
return unpaidMana;
|
return unpaidMana;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ManaOptions getManaOptions(Ability source, Game game, ManaCost unpaid) {
|
||||||
|
if (manaAbility != null) {
|
||||||
|
return manaAbility.getManaOptions(source, game, unpaid);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActivationStatus canActivate(UUID playerId, Game game) {
|
||||||
|
if (isManaAction()) {
|
||||||
|
// limit play mana abilities by steps
|
||||||
|
int currentStepOrder = 0;
|
||||||
|
if (!game.getStack().isEmpty()) {
|
||||||
|
StackObject stackObject = game.getStack().getFirst();
|
||||||
|
if (stackObject instanceof Spell) {
|
||||||
|
currentStepOrder = ((Spell) stackObject).getCurrentActivatingManaAbilitiesStep().getStepOrder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentStepOrder > manaAbility.useOnActivationManaAbilityStep().getStepOrder()) {
|
||||||
|
return ActivationStatus.getFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.canActivate(playerId, game);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package mage.abilities.costs.mana;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some special AlternateManaPaymentAbility must be restricted to pay before or after mana abilities.
|
||||||
|
* Game logic: if you use special mana ability then normal mana abilities must be restricted and vice versa,
|
||||||
|
* see Convoke for more info and rules
|
||||||
|
*
|
||||||
|
* @author JayDi85
|
||||||
|
*/
|
||||||
|
|
||||||
|
public enum ActivationManaAbilityStep {
|
||||||
|
BEFORE(0), // assist
|
||||||
|
NORMAL(1), // all activated mana abilities
|
||||||
|
AFTER(2); // convoke, delve, improvise
|
||||||
|
|
||||||
|
private final int stepOrder;
|
||||||
|
|
||||||
|
ActivationManaAbilityStep(int stepOrder) {
|
||||||
|
this.stepOrder = stepOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStepOrder() {
|
||||||
|
return stepOrder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
|
|
||||||
package mage.abilities.costs.mana;
|
package mage.abilities.costs.mana;
|
||||||
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.mana.ManaOptions;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for abilities that allow the player to pay mana costs of a spell in alternate ways.
|
* Interface for abilities that allow the player to pay mana costs of a spell in alternate ways.
|
||||||
* For the payment SpecialActions are used.
|
* For the payment SpecialActions are used.
|
||||||
*
|
* <p>
|
||||||
* Example of such an alternate payment ability: {@link mage.abilities.keyword.DelveAbility}
|
* Example of such an alternate payment ability: {@link mage.abilities.keyword.DelveAbility}
|
||||||
*
|
*
|
||||||
* @author LevelX2
|
* @author LevelX2, JayDi85
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
|
||||||
public interface AlternateManaPaymentAbility {
|
public interface AlternateManaPaymentAbility {
|
||||||
/**
|
/**
|
||||||
* Adds the special action to the state, that allows the user to do the alternate mana payment
|
* Adds the special action to the state, that allows the user to do the alternate mana payment
|
||||||
|
|
@ -22,4 +21,21 @@ public interface AlternateManaPaymentAbility {
|
||||||
* @param unpaid unapaid mana costs of the spell
|
* @param unpaid unapaid mana costs of the spell
|
||||||
*/
|
*/
|
||||||
void addSpecialAction(Ability source, Game game, ManaCost unpaid);
|
void addSpecialAction(Ability source, Game game, ManaCost unpaid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All possible mana payments that can make that ability (uses to find playable abilities)
|
||||||
|
*
|
||||||
|
* @param source
|
||||||
|
* @param game
|
||||||
|
* @param unpaid
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
ManaOptions getManaOptions(Ability source, Game game, ManaCost unpaid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mana payment step where you can use it
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
ActivationManaAbilityStep useOnActivationManaAbilityStep();
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
package mage.abilities.effects;
|
package mage.abilities.effects;
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import mage.MageObjectReference;
|
import mage.MageObjectReference;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.CompoundAbility;
|
import mage.abilities.CompoundAbility;
|
||||||
import mage.abilities.MageSingleton;
|
import mage.abilities.MageSingleton;
|
||||||
|
import mage.abilities.costs.mana.ActivationManaAbilityStep;
|
||||||
import mage.abilities.dynamicvalue.DynamicValue;
|
import mage.abilities.dynamicvalue.DynamicValue;
|
||||||
import mage.abilities.dynamicvalue.common.DomainValue;
|
import mage.abilities.dynamicvalue.common.DomainValue;
|
||||||
import mage.abilities.dynamicvalue.common.SignInversionDynamicValue;
|
import mage.abilities.dynamicvalue.common.SignInversionDynamicValue;
|
||||||
|
|
@ -20,6 +20,8 @@ import mage.game.stack.StackObject;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
import mage.target.targetpointer.TargetPointer;
|
import mage.target.targetpointer.TargetPointer;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author BetaSteward_at_googlemail.com, JayDi85
|
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||||
*/
|
*/
|
||||||
|
|
@ -416,7 +418,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
|
||||||
StackObject stackObject = game.getStack().getFirst();
|
StackObject stackObject = game.getStack().getFirst();
|
||||||
return !(stackObject instanceof Spell)
|
return !(stackObject instanceof Spell)
|
||||||
|| !Zone.LIBRARY.equals(((Spell) stackObject).getFromZone())
|
|| !Zone.LIBRARY.equals(((Spell) stackObject).getFromZone())
|
||||||
|| ((Spell) stackObject).isDoneActivatingManaAbilities();
|
|| ((Spell) stackObject).getCurrentActivatingManaAbilitiesStep() == ActivationManaAbilityStep.AFTER; // mana payment finished
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ import mage.constants.AsThoughEffectType;
|
||||||
import mage.constants.TimingRule;
|
import mage.constants.TimingRule;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
import mage.game.stack.Spell;
|
||||||
|
import mage.game.stack.StackObject;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -54,10 +56,24 @@ public abstract class ActivatedManaAbilityImpl extends ActivatedAbilityImpl impl
|
||||||
&& null == game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.ACTIVATE_AS_INSTANT, this, controllerId, game)) {
|
&& null == game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.ACTIVATE_AS_INSTANT, this, controllerId, game)) {
|
||||||
return ActivationStatus.getFalse();
|
return ActivationStatus.getFalse();
|
||||||
}
|
}
|
||||||
// check if player is in the process of playing spell costs and they are no longer allowed to use activated mana abilities (e.g. because they started to use improvise)
|
|
||||||
|
// check if player is in the process of playing spell costs and they are no longer allowed to use
|
||||||
|
// activated mana abilities (e.g. because they started to use improvise or convoke)
|
||||||
|
if (!game.getStack().isEmpty()) {
|
||||||
|
StackObject stackObject = game.getStack().getFirst();
|
||||||
|
if (stackObject instanceof Spell) {
|
||||||
|
switch (((Spell) stackObject).getCurrentActivatingManaAbilitiesStep()) {
|
||||||
|
case BEFORE:
|
||||||
|
case NORMAL:
|
||||||
|
break;
|
||||||
|
case AFTER:
|
||||||
|
return ActivationStatus.getFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//20091005 - 605.3a
|
//20091005 - 605.3a
|
||||||
return new ActivationStatus(costs.canPay(this, sourceId, controllerId, game), null);
|
return new ActivationStatus(costs.canPay(this, sourceId, controllerId, game), null);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package mage.game.stack;
|
package mage.game.stack;
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import mage.MageInt;
|
import mage.MageInt;
|
||||||
import mage.MageObject;
|
import mage.MageObject;
|
||||||
import mage.Mana;
|
import mage.Mana;
|
||||||
|
|
@ -11,6 +10,7 @@ import mage.abilities.Mode;
|
||||||
import mage.abilities.SpellAbility;
|
import mage.abilities.SpellAbility;
|
||||||
import mage.abilities.costs.AlternativeSourceCosts;
|
import mage.abilities.costs.AlternativeSourceCosts;
|
||||||
import mage.abilities.costs.Cost;
|
import mage.abilities.costs.Cost;
|
||||||
|
import mage.abilities.costs.mana.ActivationManaAbilityStep;
|
||||||
import mage.abilities.costs.mana.ManaCost;
|
import mage.abilities.costs.mana.ManaCost;
|
||||||
import mage.abilities.costs.mana.ManaCosts;
|
import mage.abilities.costs.mana.ManaCosts;
|
||||||
import mage.abilities.keyword.BestowAbility;
|
import mage.abilities.keyword.BestowAbility;
|
||||||
|
|
@ -32,6 +32,11 @@ import mage.players.Player;
|
||||||
import mage.util.GameLog;
|
import mage.util.GameLog;
|
||||||
import mage.util.SubTypeList;
|
import mage.util.SubTypeList;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author BetaSteward_at_googlemail.com
|
* @author BetaSteward_at_googlemail.com
|
||||||
*/
|
*/
|
||||||
|
|
@ -62,7 +67,7 @@ public class Spell extends StackObjImpl implements Card {
|
||||||
private boolean resolving = false;
|
private boolean resolving = false;
|
||||||
private UUID commandedBy = null; // for Word of Command
|
private UUID commandedBy = null; // for Word of Command
|
||||||
|
|
||||||
private boolean doneActivatingManaAbilities; // if this is true, the player is no longer allowed to pay the spell costs with activating of mana abilies
|
private ActivationManaAbilityStep currentActivatingManaAbilitiesStep = ActivationManaAbilityStep.BEFORE;
|
||||||
|
|
||||||
public Spell(Card card, SpellAbility ability, UUID controllerId, Zone fromZone) {
|
public Spell(Card card, SpellAbility ability, UUID controllerId, Zone fromZone) {
|
||||||
this.card = card;
|
this.card = card;
|
||||||
|
|
@ -118,12 +123,12 @@ public class Spell extends StackObjImpl implements Card {
|
||||||
this.resolving = spell.resolving;
|
this.resolving = spell.resolving;
|
||||||
this.commandedBy = spell.commandedBy;
|
this.commandedBy = spell.commandedBy;
|
||||||
|
|
||||||
this.doneActivatingManaAbilities = spell.doneActivatingManaAbilities;
|
this.currentActivatingManaAbilitiesStep = spell.currentActivatingManaAbilitiesStep;
|
||||||
this.targetChanged = spell.targetChanged;
|
this.targetChanged = spell.targetChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean activate(Game game, boolean noMana) {
|
public boolean activate(Game game, boolean noMana) {
|
||||||
setDoneActivatingManaAbilities(false); // used for e.g. improvise
|
setCurrentActivatingManaAbilitiesStep(ActivationManaAbilityStep.BEFORE); // mana payment step started, can use any mana abilities, see AlternateManaPaymentAbility
|
||||||
|
|
||||||
if (!ability.activate(game, noMana)) {
|
if (!ability.activate(game, noMana)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -147,7 +152,7 @@ public class Spell extends StackObjImpl implements Card {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setDoneActivatingManaAbilities(true); // can be activated again maybe during the resolution of the spell (e.g. Metallic Rebuke)
|
setCurrentActivatingManaAbilitiesStep(ActivationManaAbilityStep.NORMAL);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -401,12 +406,12 @@ public class Spell extends StackObjImpl implements Card {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDoneActivatingManaAbilities() {
|
public ActivationManaAbilityStep getCurrentActivatingManaAbilitiesStep() {
|
||||||
return doneActivatingManaAbilities;
|
return this.currentActivatingManaAbilitiesStep;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDoneActivatingManaAbilities(boolean doneActivatingManaAbilities) {
|
public void setCurrentActivatingManaAbilitiesStep(ActivationManaAbilityStep currentActivatingManaAbilitiesStep) {
|
||||||
this.doneActivatingManaAbilities = doneActivatingManaAbilities;
|
this.currentActivatingManaAbilitiesStep = currentActivatingManaAbilitiesStep;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
package mage.players;
|
package mage.players;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import mage.ConditionalMana;
|
import mage.ConditionalMana;
|
||||||
import mage.MageObject;
|
import mage.MageObject;
|
||||||
import mage.MageObjectReference;
|
import mage.MageObjectReference;
|
||||||
|
|
@ -15,6 +12,7 @@ import mage.abilities.common.PlayLandAsCommanderAbility;
|
||||||
import mage.abilities.common.WhileSearchingPlayFromLibraryAbility;
|
import mage.abilities.common.WhileSearchingPlayFromLibraryAbility;
|
||||||
import mage.abilities.common.delayed.AtTheEndOfTurnStepPostDelayedTriggeredAbility;
|
import mage.abilities.common.delayed.AtTheEndOfTurnStepPostDelayedTriggeredAbility;
|
||||||
import mage.abilities.costs.*;
|
import mage.abilities.costs.*;
|
||||||
|
import mage.abilities.costs.mana.AlternateManaPaymentAbility;
|
||||||
import mage.abilities.costs.mana.ManaCost;
|
import mage.abilities.costs.mana.ManaCost;
|
||||||
import mage.abilities.costs.mana.ManaCosts;
|
import mage.abilities.costs.mana.ManaCosts;
|
||||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||||
|
|
@ -68,6 +66,10 @@ import mage.util.GameLog;
|
||||||
import mage.util.RandomUtil;
|
import mage.util.RandomUtil;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
public abstract class PlayerImpl implements Player, Serializable {
|
public abstract class PlayerImpl implements Player, Serializable {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(PlayerImpl.class);
|
private static final Logger logger = Logger.getLogger(PlayerImpl.class);
|
||||||
|
|
@ -611,9 +613,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
&& this.hasOpponent(sourceControllerId, game)
|
&& this.hasOpponent(sourceControllerId, game)
|
||||||
&& game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) == null
|
&& game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) == null
|
||||||
&& abilities.stream()
|
&& abilities.stream()
|
||||||
.filter(HexproofBaseAbility.class::isInstance)
|
.filter(HexproofBaseAbility.class::isInstance)
|
||||||
.map(HexproofBaseAbility.class::cast)
|
.map(HexproofBaseAbility.class::cast)
|
||||||
.anyMatch(ability -> ability.checkObject(source, game))) {
|
.anyMatch(ability -> ability.checkObject(source, game))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -653,7 +655,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
game.informPlayers(getLogName() + " discards down to "
|
game.informPlayers(getLogName() + " discards down to "
|
||||||
+ this.maxHandSize
|
+ this.maxHandSize
|
||||||
+ (this.maxHandSize == 1
|
+ (this.maxHandSize == 1
|
||||||
? " hand card" : " hand cards"));
|
? " hand card" : " hand cards"));
|
||||||
}
|
}
|
||||||
discard(hand.size() - this.maxHandSize, false, null, game);
|
discard(hand.size() - this.maxHandSize, false, null, game);
|
||||||
}
|
}
|
||||||
|
|
@ -802,7 +804,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
}
|
}
|
||||||
GameEvent gameEvent = GameEvent.getEvent(GameEvent.EventType.DISCARD_CARD,
|
GameEvent gameEvent = GameEvent.getEvent(GameEvent.EventType.DISCARD_CARD,
|
||||||
card.getId(), source == null
|
card.getId(), source == null
|
||||||
? null : source.getSourceId(), playerId);
|
? null : source.getSourceId(), playerId);
|
||||||
gameEvent.setFlag(source != null); // event from effect or from cost (source == null)
|
gameEvent.setFlag(source != null); // event from effect or from cost (source == null)
|
||||||
if (game.replaceEvent(gameEvent, source)) {
|
if (game.replaceEvent(gameEvent, source)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1811,9 +1813,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Permanent> getPermanentsThatCanBeUntapped(Game game,
|
private List<Permanent> getPermanentsThatCanBeUntapped(Game game,
|
||||||
List<Permanent> canBeUntapped,
|
List<Permanent> canBeUntapped,
|
||||||
RestrictionUntapNotMoreThanEffect handledEffect,
|
RestrictionUntapNotMoreThanEffect handledEffect,
|
||||||
Map<Entry<RestrictionUntapNotMoreThanEffect, Set<Ability>>, Integer> notMoreThanEffectsUsage) {
|
Map<Entry<RestrictionUntapNotMoreThanEffect, Set<Ability>>, Integer> notMoreThanEffectsUsage) {
|
||||||
List<Permanent> leftForUntap = new ArrayList<>();
|
List<Permanent> leftForUntap = new ArrayList<>();
|
||||||
// select permanents that can still be untapped
|
// select permanents that can still be untapped
|
||||||
for (Permanent permanent : canBeUntapped) {
|
for (Permanent permanent : canBeUntapped) {
|
||||||
|
|
@ -2522,7 +2524,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId,
|
public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId,
|
||||||
boolean triggerEvents) {
|
boolean triggerEvents) {
|
||||||
//20091005 - 701.14c
|
//20091005 - 701.14c
|
||||||
Library searchedLibrary = null;
|
Library searchedLibrary = null;
|
||||||
String searchInfo = null;
|
String searchInfo = null;
|
||||||
|
|
@ -2724,7 +2726,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
/**
|
/**
|
||||||
* @param game
|
* @param game
|
||||||
* @param appliedEffects
|
* @param appliedEffects
|
||||||
* @param numSides Number of sides the dice has
|
* @param numSides Number of sides the dice has
|
||||||
* @return the number that the player rolled
|
* @return the number that the player rolled
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -2761,16 +2763,16 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
/**
|
/**
|
||||||
* @param game
|
* @param game
|
||||||
* @param appliedEffects
|
* @param appliedEffects
|
||||||
* @param numberChaosSides The number of chaos sides the planar die
|
* @param numberChaosSides The number of chaos sides the planar die
|
||||||
* currently has (normally 1 but can be 5)
|
* currently has (normally 1 but can be 5)
|
||||||
* @param numberPlanarSides The number of chaos sides the planar die
|
* @param numberPlanarSides The number of chaos sides the planar die
|
||||||
* currently has (normally 1)
|
* currently has (normally 1)
|
||||||
* @return the outcome that the player rolled. Either ChaosRoll, PlanarRoll
|
* @return the outcome that the player rolled. Either ChaosRoll, PlanarRoll
|
||||||
* or NilRoll
|
* or NilRoll
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public PlanarDieRoll rollPlanarDie(Game game, List<UUID> appliedEffects, int numberChaosSides,
|
public PlanarDieRoll rollPlanarDie(Game game, List<UUID> appliedEffects, int numberChaosSides,
|
||||||
int numberPlanarSides) {
|
int numberPlanarSides) {
|
||||||
int result = RandomUtil.nextInt(9) + 1;
|
int result = RandomUtil.nextInt(9) + 1;
|
||||||
PlanarDieRoll roll = PlanarDieRoll.NIL_ROLL;
|
PlanarDieRoll roll = PlanarDieRoll.NIL_ROLL;
|
||||||
if (numberChaosSides + numberPlanarSides > 9) {
|
if (numberChaosSides + numberPlanarSides > 9) {
|
||||||
|
|
@ -2927,7 +2929,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ability
|
* @param ability
|
||||||
* @param available if null, it won't be checked if enough mana is available
|
* @param available if null, it won't be checked if enough mana is available
|
||||||
* @param sourceObject
|
* @param sourceObject
|
||||||
* @param game
|
* @param game
|
||||||
* @return
|
* @return
|
||||||
|
|
@ -3076,7 +3078,6 @@ 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) {
|
||||||
// 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
|
||||||
if (((ActivatedManaAbilityImpl) ability).canActivate(this.getId(), game).canActivate()) {
|
if (((ActivatedManaAbilityImpl) ability).canActivate(this.getId(), game).canActivate()) {
|
||||||
|
|
@ -3085,8 +3086,11 @@ 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, manaAvailable, 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) {
|
||||||
// activated ability
|
// all other activated ability
|
||||||
if (canPlay((ActivatedAbility) ability, manaAvailable, card, game)) {
|
if (canPlay((ActivatedAbility) ability, manaAvailable, card, game)) {
|
||||||
return (ActivatedAbility) ability;
|
return (ActivatedAbility) ability;
|
||||||
}
|
}
|
||||||
|
|
@ -3119,6 +3123,20 @@ 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()
|
||||||
|
|
@ -3221,37 +3239,45 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
|
|
||||||
boolean canActivateAsHandZone = permittingObject != null
|
boolean canActivateAsHandZone = permittingObject != null
|
||||||
|| (fromZone == Zone.GRAVEYARD && canPlayCardsFromGraveyard());
|
|| (fromZone == Zone.GRAVEYARD && canPlayCardsFromGraveyard());
|
||||||
|
boolean possibleToPlay = false;
|
||||||
|
|
||||||
// as affected controller
|
// spell/hand abilities (play from all zones)
|
||||||
UUID savedControllerId = ability.getControllerId();
|
// need permitingObject or canPlayCardsFromGraveyard
|
||||||
ability.setControllerId(this.getId());
|
if (canActivateAsHandZone
|
||||||
try {
|
&& ability.getZone().match(Zone.HAND)
|
||||||
boolean possibleToPlay = false;
|
&& (isPlaySpell || isPlayLand)) {
|
||||||
|
possibleToPlay = true;
|
||||||
|
}
|
||||||
|
|
||||||
// spell/hand abilities (play from all zones)
|
// zone's abilities (play from specific zone)
|
||||||
// need permitingObject or canPlayCardsFromGraveyard
|
// no need in permitingObject
|
||||||
if (canActivateAsHandZone
|
if (fromZone != Zone.ALL && ability.getZone().match(fromZone)) {
|
||||||
&& ability.getZone().match(Zone.HAND)
|
possibleToPlay = true;
|
||||||
&& (isPlaySpell || isPlayLand)) {
|
}
|
||||||
possibleToPlay = true;
|
|
||||||
|
if (!possibleToPlay) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// direct mode (with original controller)
|
||||||
|
ActivatedAbility playAbility = findActivatedAbilityFromPlayable(card, availableMana, ability, game);
|
||||||
|
if (playAbility != null && !output.contains(playAbility)) {
|
||||||
|
output.add(playAbility);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// from non hand mode (with affected controller)
|
||||||
|
if (canActivateAsHandZone) {
|
||||||
|
UUID savedControllerId = ability.getControllerId();
|
||||||
|
ability.setControllerId(this.getId());
|
||||||
|
try {
|
||||||
|
playAbility = findActivatedAbilityFromPlayable(card, availableMana, ability, game);
|
||||||
|
if (playAbility != null && !output.contains(playAbility)) {
|
||||||
|
output.add(playAbility);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
ability.setControllerId(savedControllerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// zone's abilities (play from specific zone)
|
|
||||||
// no need in permitingObject
|
|
||||||
if (fromZone != Zone.ALL && ability.getZone().match(fromZone)) {
|
|
||||||
possibleToPlay = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!possibleToPlay) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ActivatedAbility playAbility = findActivatedAbilityFromPlayable(card, availableMana, ability, game);
|
|
||||||
if (playAbility != null && !output.contains(playAbility)) {
|
|
||||||
output.add(playAbility);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
ability.setControllerId(savedControllerId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3270,8 +3296,10 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
boolean previousState = game.inCheckPlayableState();
|
boolean previousState = game.inCheckPlayableState();
|
||||||
game.setCheckPlayableState(true);
|
game.setCheckPlayableState(true);
|
||||||
try {
|
try {
|
||||||
|
// basic mana
|
||||||
ManaOptions availableMana = getManaAvailable(game);
|
ManaOptions availableMana = getManaAvailable(game);
|
||||||
availableMana.addMana(manaPool.getMana());
|
availableMana.addMana(manaPool.getMana());
|
||||||
|
// conditional mana
|
||||||
for (ConditionalMana conditionalMana : manaPool.getConditionalMana()) {
|
for (ConditionalMana conditionalMana : manaPool.getConditionalMana()) {
|
||||||
availableMana.addMana(conditionalMana);
|
availableMana.addMana(conditionalMana);
|
||||||
}
|
}
|
||||||
|
|
@ -3364,7 +3392,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
|
|
||||||
// activated abilities from battlefield objects
|
// activated abilities from battlefield objects
|
||||||
if (fromAll || fromZone == Zone.BATTLEFIELD) {
|
if (fromAll || fromZone == Zone.BATTLEFIELD) {
|
||||||
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(playerId)) {
|
for (Permanent permanent : game.getBattlefield().getAllActivePermanents()) {
|
||||||
boolean canUseActivated = permanent.canUseActivatedAbilities(game);
|
boolean canUseActivated = permanent.canUseActivatedAbilities(game);
|
||||||
List<ActivatedAbility> battlePlayable = new ArrayList<>();
|
List<ActivatedAbility> battlePlayable = new ArrayList<>();
|
||||||
getPlayableFromCardAll(game, Zone.BATTLEFIELD, permanent, availableMana, battlePlayable);
|
getPlayableFromCardAll(game, Zone.BATTLEFIELD, permanent, availableMana, battlePlayable);
|
||||||
|
|
@ -3624,7 +3652,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canPaySacrificeCost(Permanent permanent, UUID sourceId,
|
public boolean canPaySacrificeCost(Permanent permanent, UUID sourceId,
|
||||||
UUID controllerId, Game game
|
UUID controllerId, Game game
|
||||||
) {
|
) {
|
||||||
return sacrificeCostFilter == null || !sacrificeCostFilter.match(permanent, sourceId, controllerId, game);
|
return sacrificeCostFilter == null || !sacrificeCostFilter.match(permanent, sourceId, controllerId, game);
|
||||||
}
|
}
|
||||||
|
|
@ -3777,8 +3805,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean moveCards(Card card, Zone toZone,
|
public boolean moveCards(Card card, Zone toZone,
|
||||||
Ability source, Game game,
|
Ability source, Game game,
|
||||||
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
|
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
|
||||||
) {
|
) {
|
||||||
Set<Card> cardList = new HashSet<>();
|
Set<Card> cardList = new HashSet<>();
|
||||||
if (card != null) {
|
if (card != null) {
|
||||||
|
|
@ -3789,22 +3817,22 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean moveCards(Cards cards, Zone toZone,
|
public boolean moveCards(Cards cards, Zone toZone,
|
||||||
Ability source, Game game
|
Ability source, Game game
|
||||||
) {
|
) {
|
||||||
return moveCards(cards.getCards(game), toZone, source, game);
|
return moveCards(cards.getCards(game), toZone, source, game);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean moveCards(Set<Card> cards, Zone toZone,
|
public boolean moveCards(Set<Card> cards, Zone toZone,
|
||||||
Ability source, Game game
|
Ability source, Game game
|
||||||
) {
|
) {
|
||||||
return moveCards(cards, toZone, source, game, false, false, false, null);
|
return moveCards(cards, toZone, source, game, false, false, false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean moveCards(Set<Card> cards, Zone toZone,
|
public boolean moveCards(Set<Card> cards, Zone toZone,
|
||||||
Ability source, Game game,
|
Ability source, Game game,
|
||||||
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
|
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
|
||||||
) {
|
) {
|
||||||
if (cards.isEmpty()) {
|
if (cards.isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -3906,8 +3934,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean moveCardsToExile(Card card, Ability source,
|
public boolean moveCardsToExile(Card card, Ability source,
|
||||||
Game game, boolean withName, UUID exileId,
|
Game game, boolean withName, UUID exileId,
|
||||||
String exileZoneName
|
String exileZoneName
|
||||||
) {
|
) {
|
||||||
Set<Card> cards = new HashSet<>();
|
Set<Card> cards = new HashSet<>();
|
||||||
cards.add(card);
|
cards.add(card);
|
||||||
|
|
@ -3916,8 +3944,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean moveCardsToExile(Set<Card> cards, Ability source,
|
public boolean moveCardsToExile(Set<Card> cards, Ability source,
|
||||||
Game game, boolean withName, UUID exileId,
|
Game game, boolean withName, UUID exileId,
|
||||||
String exileZoneName
|
String exileZoneName
|
||||||
) {
|
) {
|
||||||
if (cards.isEmpty()) {
|
if (cards.isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -3933,14 +3961,14 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean moveCardToHandWithInfo(Card card, UUID sourceId,
|
public boolean moveCardToHandWithInfo(Card card, UUID sourceId,
|
||||||
Game game
|
Game game
|
||||||
) {
|
) {
|
||||||
return this.moveCardToHandWithInfo(card, sourceId, game, true);
|
return this.moveCardToHandWithInfo(card, sourceId, game, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean moveCardToHandWithInfo(Card card, UUID sourceId,
|
public boolean moveCardToHandWithInfo(Card card, UUID sourceId,
|
||||||
Game game, boolean withName
|
Game game, boolean withName
|
||||||
) {
|
) {
|
||||||
boolean result = false;
|
boolean result = false;
|
||||||
Zone fromZone = game.getState().getZone(card.getId());
|
Zone fromZone = game.getState().getZone(card.getId());
|
||||||
|
|
@ -3965,7 +3993,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<Card> moveCardsToGraveyardWithInfo(Set<Card> allCards, Ability source,
|
public Set<Card> moveCardsToGraveyardWithInfo(Set<Card> allCards, Ability source,
|
||||||
Game game, Zone fromZone
|
Game game, Zone fromZone
|
||||||
) {
|
) {
|
||||||
UUID sourceId = source == null ? null : source.getSourceId();
|
UUID sourceId = source == null ? null : source.getSourceId();
|
||||||
Set<Card> movedCards = new LinkedHashSet<>();
|
Set<Card> movedCards = new LinkedHashSet<>();
|
||||||
|
|
@ -3973,7 +4001,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
// identify cards from one owner
|
// identify cards from one owner
|
||||||
Cards cards = new CardsImpl();
|
Cards cards = new CardsImpl();
|
||||||
UUID ownerId = null;
|
UUID ownerId = null;
|
||||||
for (Iterator<Card> it = allCards.iterator(); it.hasNext();) {
|
for (Iterator<Card> it = allCards.iterator(); it.hasNext(); ) {
|
||||||
Card card = it.next();
|
Card card = it.next();
|
||||||
if (cards.isEmpty()) {
|
if (cards.isEmpty()) {
|
||||||
ownerId = card.getOwnerId();
|
ownerId = card.getOwnerId();
|
||||||
|
|
@ -4036,7 +4064,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean moveCardToGraveyardWithInfo(Card card, UUID sourceId,
|
public boolean moveCardToGraveyardWithInfo(Card card, UUID sourceId,
|
||||||
Game game, Zone fromZone
|
Game game, Zone fromZone
|
||||||
) {
|
) {
|
||||||
if (card == null) {
|
if (card == null) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -4065,8 +4093,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean moveCardToLibraryWithInfo(Card card, UUID sourceId,
|
public boolean moveCardToLibraryWithInfo(Card card, UUID sourceId,
|
||||||
Game game, Zone fromZone,
|
Game game, Zone fromZone,
|
||||||
boolean toTop, boolean withName
|
boolean toTop, boolean withName
|
||||||
) {
|
) {
|
||||||
if (card == null) {
|
if (card == null) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -4131,7 +4159,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean moveCardToExileWithInfo(Card card, UUID exileId, String exileName, UUID sourceId,
|
public boolean moveCardToExileWithInfo(Card card, UUID exileId, String exileName, UUID sourceId,
|
||||||
Game game, Zone fromZone, boolean withName) {
|
Game game, Zone fromZone, boolean withName) {
|
||||||
if (card == null) {
|
if (card == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -4154,7 +4182,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
game.informPlayers(this.getLogName() + " moves " + (withName ? card.getLogName()
|
game.informPlayers(this.getLogName() + " moves " + (withName ? card.getLogName()
|
||||||
+ (card.isCopy() ? " (Copy)" : "") : "a card face down") + ' '
|
+ (card.isCopy() ? " (Copy)" : "") : "a card face down") + ' '
|
||||||
+ (fromZone != null ? "from " + fromZone.toString().toLowerCase(Locale.ENGLISH)
|
+ (fromZone != null ? "from " + fromZone.toString().toLowerCase(Locale.ENGLISH)
|
||||||
+ ' ' : "") + "to the exile zone");
|
+ ' ' : "") + "to the exile zone");
|
||||||
|
|
||||||
}
|
}
|
||||||
result = true;
|
result = true;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue