mirror of
https://github.com/correl/mage.git
synced 2024-11-14 19:19:32 +00: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.hint.HintUtils;
|
||||
import mage.abilities.mana.ActivatedManaAbilityImpl;
|
||||
import mage.abilities.mana.ManaAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.Cards;
|
||||
import mage.cards.decks.Deck;
|
||||
|
@ -61,6 +62,8 @@ import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_RESET_ALL;
|
|||
*/
|
||||
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 final transient PlayerResponse response = new PlayerResponse();
|
||||
|
||||
|
@ -1039,7 +1042,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
|
||||
if (response.getString() != null
|
||||
&& response.getString().equals("special")) {
|
||||
specialAction(game);
|
||||
activateSpecialAction(game, null);
|
||||
} else if (response.getUUID() != null) {
|
||||
boolean result = false;
|
||||
MageObject object = game.getObject(response.getUUID());
|
||||
|
@ -1057,6 +1060,24 @@ public class HumanPlayer extends PlayerImpl {
|
|||
}
|
||||
if (actingPlayer != null) {
|
||||
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
|
||||
|
@ -1221,7 +1242,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
} else if (response.getString() != null
|
||||
&& response.getString().equals("special")) {
|
||||
if (unpaid instanceof ManaCostsImpl) {
|
||||
specialManaAction(unpaid, game);
|
||||
activateSpecialAction(game, unpaid);
|
||||
}
|
||||
} else if (response.getManaType() != null) {
|
||||
// this mana type can be paid once from pool
|
||||
|
@ -1336,14 +1357,26 @@ public class HumanPlayer extends PlayerImpl {
|
|||
if (object == null) {
|
||||
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());
|
||||
if (spell != null && !spell.isResolving()
|
||||
&& spell.isDoneActivatingManaAbilities()) {
|
||||
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.");
|
||||
return;
|
||||
boolean haveManaAbilities = object.getAbilities().stream().anyMatch(a -> a instanceof ManaAbility);
|
||||
if (spell != null && !spell.isResolving() && haveManaAbilities) {
|
||||
switch (spell.getCurrentActivatingManaAbilitiesStep()) {
|
||||
// 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());
|
||||
if (zone != null) {
|
||||
LinkedHashMap<UUID, ActivatedManaAbilityImpl> useableAbilities = getUseableManaAbilities(object, zone, game);
|
||||
|
@ -1844,7 +1877,13 @@ public class HumanPlayer extends PlayerImpl {
|
|||
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)) {
|
||||
return;
|
||||
}
|
||||
|
@ -1853,7 +1892,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
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()) {
|
||||
|
||||
updateGameStatePriority("specialAction", game);
|
||||
|
@ -1863,39 +1902,13 @@ public class HumanPlayer extends PlayerImpl {
|
|||
}
|
||||
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 (specialActions.containsKey(response.getUUID())) {
|
||||
SpecialAction specialAction = specialActions.get(response.getUUID());
|
||||
if (specialAction != null) {
|
||||
specialAction.setUnpaidMana(unpaid);
|
||||
activateAbility(specialActions.get(response.getUUID()), game);
|
||||
if (unpaidForManaAction != null) {
|
||||
specialAction.setUnpaidMana(unpaidForManaAction);
|
||||
}
|
||||
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
|
||||
* Angelic Purge the discard was not required.
|
||||
*/
|
||||
|
||||
|
||||
@Test
|
||||
public void castFromExileButWithAdditionalCostTest() {
|
||||
// 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, "Silvercoat Lion");
|
||||
addCard(Zone.HAND, playerB, "Pillarfield Ox");
|
||||
|
||||
|
||||
addCard(Zone.LIBRARY, playerB, "Pillarfield Ox"); // Card to draw on turn 2
|
||||
// As an additional cost to cast Tormenting Voice, discard a card.
|
||||
// Draw two cards.
|
||||
|
@ -290,14 +290,14 @@ public class PlayFromNonHandZoneTest extends CardTestPlayerBase {
|
|||
// As an additional cost to cast this spell, sacrifice a creature.
|
||||
// Flying, Trample
|
||||
addCard(Zone.LIBRARY, playerA, "Demon of Catastrophes"); // Creature {2}{B}{B} 6/6
|
||||
|
||||
|
||||
skipInitShuffling();
|
||||
|
||||
attack(2, playerB, "Fallen Shinobi");
|
||||
|
||||
|
||||
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Tormenting Voice");
|
||||
setChoice(playerB, "Pillarfield Ox"); // Discord for Tormenting Voice
|
||||
|
||||
|
||||
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Demon of Catastrophes");
|
||||
setChoice(playerB, "Silvercoat Lion"); // Sacrifice for Demon
|
||||
|
||||
|
@ -309,20 +309,20 @@ public class PlayFromNonHandZoneTest extends CardTestPlayerBase {
|
|||
|
||||
assertLife(playerA, 15);
|
||||
assertPermanentCount(playerB, "Fallen Shinobi", 1);
|
||||
|
||||
assertGraveyardCount(playerA, "Tormenting Voice", 1);
|
||||
assertGraveyardCount(playerB, "Pillarfield Ox", 1); // Discarded for Tormenting Voice
|
||||
|
||||
|
||||
|
||||
assertGraveyardCount(playerA, "Tormenting Voice", 1);
|
||||
assertGraveyardCount(playerB, "Pillarfield Ox", 1); // Discarded for Tormenting Voice
|
||||
|
||||
|
||||
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, 3); // 2 from Tormenting Voice + 1 from Turn 2
|
||||
assertExileCount(playerA, 0); // Both exiled cards are cast
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void castFromExileButWithAdditionalCost2Test() {
|
||||
// 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, "Silvercoat Lion");
|
||||
addCard(Zone.HAND, playerB, "Pillarfield Ox");
|
||||
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Amulet of Kroog"); // Just to exile for Angelic Purge
|
||||
|
||||
// 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.
|
||||
// Exile target artifact, creature, or enchantment.
|
||||
addCard(Zone.LIBRARY, playerA, "Angelic Purge"); // Sorcery {2}{W}
|
||||
|
||||
|
||||
skipInitShuffling();
|
||||
|
||||
attack(2, playerB, "Fallen Shinobi");
|
||||
|
||||
|
||||
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Angelic Purge");
|
||||
setChoice(playerB, "Silvercoat Lion"); // Sacrifice for Purge
|
||||
addTarget(playerB, "Amulet of Kroog"); // Exile with Purge
|
||||
|
||||
|
||||
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Tormenting Voice");
|
||||
setChoice(playerB, "Pillarfield Ox"); // Discord for Tormenting Voice
|
||||
|
||||
|
||||
setStopAt(2, PhaseStep.END_TURN);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
|
@ -361,27 +361,27 @@ public class PlayFromNonHandZoneTest extends CardTestPlayerBase {
|
|||
|
||||
assertLife(playerA, 15);
|
||||
assertPermanentCount(playerB, "Fallen Shinobi", 1);
|
||||
|
||||
assertGraveyardCount(playerA, "Angelic Purge", 1);
|
||||
assertGraveyardCount(playerB, "Silvercoat Lion", 1); // sacrificed for Purge
|
||||
|
||||
assertGraveyardCount(playerA, "Tormenting Voice", 1);
|
||||
assertGraveyardCount(playerB, "Pillarfield Ox", 1); // Discarded for Tormenting Voice
|
||||
|
||||
assertGraveyardCount(playerA, "Angelic Purge", 1);
|
||||
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
|
||||
|
||||
|
||||
assertExileCount(playerA, 1); // Both exiled cards are cast
|
||||
assertExileCount(playerA, "Amulet of Kroog", 1); // Exiled with Purge
|
||||
}
|
||||
|
||||
@Test
|
||||
public void castAdventureWithFallenShinobiTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void castAdventureWithFallenShinobiTest() {
|
||||
// Ninjutsu {2}{U}{B}
|
||||
// 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.
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Fallen Shinobi", 1); // Creature 5/4
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");
|
||||
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Amulet of Kroog"); // Just to exile for Angelic Purge
|
||||
|
||||
/* Curious Pair {1}{G}
|
||||
|
@ -391,23 +391,23 @@ public class PlayFromNonHandZoneTest extends CardTestPlayerBase {
|
|||
* Treats to Share {G}
|
||||
* Sorcery — Adventure
|
||||
* 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.
|
||||
// Exile target artifact, creature, or enchantment.
|
||||
addCard(Zone.LIBRARY, playerA, "Angelic Purge"); // Sorcery {2}{W}
|
||||
|
||||
|
||||
skipInitShuffling();
|
||||
|
||||
attack(2, playerB, "Fallen Shinobi");
|
||||
|
||||
|
||||
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Angelic Purge");
|
||||
setChoice(playerB, "Silvercoat Lion"); // Sacrifice for Purge
|
||||
addTarget(playerB, "Amulet of Kroog"); // Exile with Purge
|
||||
|
||||
|
||||
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Treats to Share");
|
||||
|
||||
|
||||
setStopAt(2, PhaseStep.END_TURN);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
|
@ -416,17 +416,46 @@ public class PlayFromNonHandZoneTest extends CardTestPlayerBase {
|
|||
|
||||
assertLife(playerA, 15);
|
||||
assertPermanentCount(playerB, "Fallen Shinobi", 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);
|
||||
assertExileCount(playerA, "Curious Pair", 1);
|
||||
|
||||
|
||||
assertHandCount(playerB, 1); // 1 from Turn 2 draw
|
||||
|
||||
|
||||
assertExileCount(playerA, 2); // Both exiled cards are cast
|
||||
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;
|
||||
|
||||
import mage.abilities.costs.mana.AlternateManaPaymentAbility;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.mana.ManaOptions;
|
||||
import mage.constants.AbilityType;
|
||||
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
|
||||
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||
*/
|
||||
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;
|
||||
|
||||
public SpecialAction() {
|
||||
|
@ -20,22 +24,23 @@ public abstract class SpecialAction extends ActivatedAbilityImpl {
|
|||
}
|
||||
|
||||
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);
|
||||
this.usesStack = false;
|
||||
this.manaAction = manaAction;
|
||||
this.manaAbility = manaAbility;
|
||||
}
|
||||
|
||||
public SpecialAction(final SpecialAction action) {
|
||||
super(action);
|
||||
this.manaAction = action.manaAction;
|
||||
this.unpaidMana = action.unpaidMana;
|
||||
this.manaAbility = action.manaAbility;
|
||||
}
|
||||
|
||||
public boolean isManaAction() {
|
||||
return manaAction;
|
||||
return manaAbility != null;
|
||||
}
|
||||
|
||||
public void setUnpaidMana(ManaCost manaCost) {
|
||||
|
@ -45,4 +50,29 @@ public abstract class SpecialAction extends ActivatedAbilityImpl {
|
|||
public ManaCost getUnpaidMana() {
|
||||
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;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.mana.ManaOptions;
|
||||
import mage.game.Game;
|
||||
|
||||
/**
|
||||
* Interface for abilities that allow the player to pay mana costs of a spell in alternate ways.
|
||||
* For the payment SpecialActions are used.
|
||||
*
|
||||
* <p>
|
||||
* Example of such an alternate payment ability: {@link mage.abilities.keyword.DelveAbility}
|
||||
*
|
||||
* @author LevelX2
|
||||
* @author LevelX2, JayDi85
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface AlternateManaPaymentAbility {
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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;
|
||||
|
||||
import java.util.*;
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.CompoundAbility;
|
||||
import mage.abilities.MageSingleton;
|
||||
import mage.abilities.costs.mana.ActivationManaAbilityStep;
|
||||
import mage.abilities.dynamicvalue.DynamicValue;
|
||||
import mage.abilities.dynamicvalue.common.DomainValue;
|
||||
import mage.abilities.dynamicvalue.common.SignInversionDynamicValue;
|
||||
|
@ -20,6 +20,8 @@ import mage.game.stack.StackObject;
|
|||
import mage.players.Player;
|
||||
import mage.target.targetpointer.TargetPointer;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||
*/
|
||||
|
@ -416,7 +418,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
|
|||
StackObject stackObject = game.getStack().getFirst();
|
||||
return !(stackObject instanceof Spell)
|
||||
|| !Zone.LIBRARY.equals(((Spell) stackObject).getFromZone())
|
||||
|| ((Spell) stackObject).isDoneActivatingManaAbilities();
|
||||
|| ((Spell) stackObject).getCurrentActivatingManaAbilitiesStep() == ActivationManaAbilityStep.AFTER; // mana payment finished
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import mage.constants.AsThoughEffectType;
|
|||
import mage.constants.TimingRule;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.game.stack.StackObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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)) {
|
||||
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
|
||||
return new ActivationStatus(costs.canPay(this, sourceId, controllerId, game), null);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package mage.game.stack;
|
||||
|
||||
import java.util.*;
|
||||
import mage.MageInt;
|
||||
import mage.MageObject;
|
||||
import mage.Mana;
|
||||
|
@ -11,6 +10,7 @@ import mage.abilities.Mode;
|
|||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.costs.AlternativeSourceCosts;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.mana.ActivationManaAbilityStep;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
import mage.abilities.keyword.BestowAbility;
|
||||
|
@ -32,6 +32,11 @@ import mage.players.Player;
|
|||
import mage.util.GameLog;
|
||||
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
|
||||
*/
|
||||
|
@ -62,7 +67,7 @@ public class Spell extends StackObjImpl implements Card {
|
|||
private boolean resolving = false;
|
||||
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) {
|
||||
this.card = card;
|
||||
|
@ -118,12 +123,12 @@ public class Spell extends StackObjImpl implements Card {
|
|||
this.resolving = spell.resolving;
|
||||
this.commandedBy = spell.commandedBy;
|
||||
|
||||
this.doneActivatingManaAbilities = spell.doneActivatingManaAbilities;
|
||||
this.currentActivatingManaAbilitiesStep = spell.currentActivatingManaAbilitiesStep;
|
||||
this.targetChanged = spell.targetChanged;
|
||||
}
|
||||
|
||||
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)) {
|
||||
return false;
|
||||
|
@ -147,7 +152,7 @@ public class Spell extends StackObjImpl implements Card {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
setDoneActivatingManaAbilities(true); // can be activated again maybe during the resolution of the spell (e.g. Metallic Rebuke)
|
||||
setCurrentActivatingManaAbilitiesStep(ActivationManaAbilityStep.NORMAL);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -401,12 +406,12 @@ public class Spell extends StackObjImpl implements Card {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean isDoneActivatingManaAbilities() {
|
||||
return doneActivatingManaAbilities;
|
||||
public ActivationManaAbilityStep getCurrentActivatingManaAbilitiesStep() {
|
||||
return this.currentActivatingManaAbilitiesStep;
|
||||
}
|
||||
|
||||
public void setDoneActivatingManaAbilities(boolean doneActivatingManaAbilities) {
|
||||
this.doneActivatingManaAbilities = doneActivatingManaAbilities;
|
||||
public void setCurrentActivatingManaAbilitiesStep(ActivationManaAbilityStep currentActivatingManaAbilitiesStep) {
|
||||
this.currentActivatingManaAbilitiesStep = currentActivatingManaAbilitiesStep;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
package mage.players;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import mage.ConditionalMana;
|
||||
import mage.MageObject;
|
||||
import mage.MageObjectReference;
|
||||
|
@ -15,6 +12,7 @@ import mage.abilities.common.PlayLandAsCommanderAbility;
|
|||
import mage.abilities.common.WhileSearchingPlayFromLibraryAbility;
|
||||
import mage.abilities.common.delayed.AtTheEndOfTurnStepPostDelayedTriggeredAbility;
|
||||
import mage.abilities.costs.*;
|
||||
import mage.abilities.costs.mana.AlternateManaPaymentAbility;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
|
@ -68,6 +66,10 @@ import mage.util.GameLog;
|
|||
import mage.util.RandomUtil;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public abstract class PlayerImpl implements Player, Serializable {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(PlayerImpl.class);
|
||||
|
@ -611,9 +613,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
&& this.hasOpponent(sourceControllerId, game)
|
||||
&& game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) == null
|
||||
&& abilities.stream()
|
||||
.filter(HexproofBaseAbility.class::isInstance)
|
||||
.map(HexproofBaseAbility.class::cast)
|
||||
.anyMatch(ability -> ability.checkObject(source, game))) {
|
||||
.filter(HexproofBaseAbility.class::isInstance)
|
||||
.map(HexproofBaseAbility.class::cast)
|
||||
.anyMatch(ability -> ability.checkObject(source, game))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -653,7 +655,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
game.informPlayers(getLogName() + " discards down to "
|
||||
+ this.maxHandSize
|
||||
+ (this.maxHandSize == 1
|
||||
? " hand card" : " hand cards"));
|
||||
? " hand card" : " hand cards"));
|
||||
}
|
||||
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,
|
||||
card.getId(), source == null
|
||||
? null : source.getSourceId(), playerId);
|
||||
? null : source.getSourceId(), playerId);
|
||||
gameEvent.setFlag(source != null); // event from effect or from cost (source == null)
|
||||
if (game.replaceEvent(gameEvent, source)) {
|
||||
return false;
|
||||
|
@ -1811,9 +1813,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
private List<Permanent> getPermanentsThatCanBeUntapped(Game game,
|
||||
List<Permanent> canBeUntapped,
|
||||
RestrictionUntapNotMoreThanEffect handledEffect,
|
||||
Map<Entry<RestrictionUntapNotMoreThanEffect, Set<Ability>>, Integer> notMoreThanEffectsUsage) {
|
||||
List<Permanent> canBeUntapped,
|
||||
RestrictionUntapNotMoreThanEffect handledEffect,
|
||||
Map<Entry<RestrictionUntapNotMoreThanEffect, Set<Ability>>, Integer> notMoreThanEffectsUsage) {
|
||||
List<Permanent> leftForUntap = new ArrayList<>();
|
||||
// select permanents that can still be untapped
|
||||
for (Permanent permanent : canBeUntapped) {
|
||||
|
@ -2522,7 +2524,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId,
|
||||
boolean triggerEvents) {
|
||||
boolean triggerEvents) {
|
||||
//20091005 - 701.14c
|
||||
Library searchedLibrary = null;
|
||||
String searchInfo = null;
|
||||
|
@ -2724,7 +2726,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
/**
|
||||
* @param game
|
||||
* @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
|
||||
*/
|
||||
@Override
|
||||
|
@ -2761,16 +2763,16 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
/**
|
||||
* @param game
|
||||
* @param appliedEffects
|
||||
* @param numberChaosSides The number of chaos sides the planar die
|
||||
* currently has (normally 1 but can be 5)
|
||||
* @param numberChaosSides The number of chaos sides the planar die
|
||||
* currently has (normally 1 but can be 5)
|
||||
* @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
|
||||
* or NilRoll
|
||||
*/
|
||||
@Override
|
||||
public PlanarDieRoll rollPlanarDie(Game game, List<UUID> appliedEffects, int numberChaosSides,
|
||||
int numberPlanarSides) {
|
||||
int numberPlanarSides) {
|
||||
int result = RandomUtil.nextInt(9) + 1;
|
||||
PlanarDieRoll roll = PlanarDieRoll.NIL_ROLL;
|
||||
if (numberChaosSides + numberPlanarSides > 9) {
|
||||
|
@ -2927,7 +2929,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
/**
|
||||
* @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 game
|
||||
* @return
|
||||
|
@ -3076,7 +3078,6 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
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)
|
||||
|
||||
if (ability instanceof ActivatedManaAbilityImpl) {
|
||||
// mana ability
|
||||
if (((ActivatedManaAbilityImpl) ability).canActivate(this.getId(), game).canActivate()) {
|
||||
|
@ -3085,8 +3086,11 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
} else if (ability instanceof AlternativeSourceCosts) {
|
||||
// alternative cost must be replaced by real play ability
|
||||
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) {
|
||||
// activated ability
|
||||
// all other activated ability
|
||||
if (canPlay((ActivatedAbility) ability, manaAvailable, card, game)) {
|
||||
return (ActivatedAbility) ability;
|
||||
}
|
||||
|
@ -3119,6 +3123,20 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
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) {
|
||||
if (sourceObject != null && !(sourceObject instanceof Permanent)) {
|
||||
Ability sourceAbility = sourceObject.getAbilities().stream()
|
||||
|
@ -3221,37 +3239,45 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
boolean canActivateAsHandZone = permittingObject != null
|
||||
|| (fromZone == Zone.GRAVEYARD && canPlayCardsFromGraveyard());
|
||||
boolean possibleToPlay = false;
|
||||
|
||||
// as affected controller
|
||||
UUID savedControllerId = ability.getControllerId();
|
||||
ability.setControllerId(this.getId());
|
||||
try {
|
||||
boolean possibleToPlay = false;
|
||||
// spell/hand abilities (play from all zones)
|
||||
// need permitingObject or canPlayCardsFromGraveyard
|
||||
if (canActivateAsHandZone
|
||||
&& ability.getZone().match(Zone.HAND)
|
||||
&& (isPlaySpell || isPlayLand)) {
|
||||
possibleToPlay = true;
|
||||
}
|
||||
|
||||
// spell/hand abilities (play from all zones)
|
||||
// need permitingObject or canPlayCardsFromGraveyard
|
||||
if (canActivateAsHandZone
|
||||
&& ability.getZone().match(Zone.HAND)
|
||||
&& (isPlaySpell || isPlayLand)) {
|
||||
possibleToPlay = true;
|
||||
// zone's abilities (play from specific zone)
|
||||
// no need in permitingObject
|
||||
if (fromZone != Zone.ALL && ability.getZone().match(fromZone)) {
|
||||
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();
|
||||
game.setCheckPlayableState(true);
|
||||
try {
|
||||
// basic mana
|
||||
ManaOptions availableMana = getManaAvailable(game);
|
||||
availableMana.addMana(manaPool.getMana());
|
||||
// conditional mana
|
||||
for (ConditionalMana conditionalMana : manaPool.getConditionalMana()) {
|
||||
availableMana.addMana(conditionalMana);
|
||||
}
|
||||
|
@ -3364,7 +3392,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
// activated abilities from battlefield objects
|
||||
if (fromAll || fromZone == Zone.BATTLEFIELD) {
|
||||
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(playerId)) {
|
||||
for (Permanent permanent : game.getBattlefield().getAllActivePermanents()) {
|
||||
boolean canUseActivated = permanent.canUseActivatedAbilities(game);
|
||||
List<ActivatedAbility> battlePlayable = new ArrayList<>();
|
||||
getPlayableFromCardAll(game, Zone.BATTLEFIELD, permanent, availableMana, battlePlayable);
|
||||
|
@ -3624,7 +3652,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean canPaySacrificeCost(Permanent permanent, UUID sourceId,
|
||||
UUID controllerId, Game game
|
||||
UUID controllerId, Game game
|
||||
) {
|
||||
return sacrificeCostFilter == null || !sacrificeCostFilter.match(permanent, sourceId, controllerId, game);
|
||||
}
|
||||
|
@ -3777,8 +3805,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCards(Card card, Zone toZone,
|
||||
Ability source, Game game,
|
||||
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
|
||||
Ability source, Game game,
|
||||
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
|
||||
) {
|
||||
Set<Card> cardList = new HashSet<>();
|
||||
if (card != null) {
|
||||
|
@ -3789,22 +3817,22 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCards(Cards cards, Zone toZone,
|
||||
Ability source, Game game
|
||||
Ability source, Game game
|
||||
) {
|
||||
return moveCards(cards.getCards(game), toZone, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveCards(Set<Card> cards, Zone toZone,
|
||||
Ability source, Game game,
|
||||
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
|
||||
Ability source, Game game,
|
||||
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
|
||||
) {
|
||||
if (cards.isEmpty()) {
|
||||
return true;
|
||||
|
@ -3906,8 +3934,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCardsToExile(Card card, Ability source,
|
||||
Game game, boolean withName, UUID exileId,
|
||||
String exileZoneName
|
||||
Game game, boolean withName, UUID exileId,
|
||||
String exileZoneName
|
||||
) {
|
||||
Set<Card> cards = new HashSet<>();
|
||||
cards.add(card);
|
||||
|
@ -3916,8 +3944,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCardsToExile(Set<Card> cards, Ability source,
|
||||
Game game, boolean withName, UUID exileId,
|
||||
String exileZoneName
|
||||
Game game, boolean withName, UUID exileId,
|
||||
String exileZoneName
|
||||
) {
|
||||
if (cards.isEmpty()) {
|
||||
return true;
|
||||
|
@ -3933,14 +3961,14 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCardToHandWithInfo(Card card, UUID sourceId,
|
||||
Game game
|
||||
Game game
|
||||
) {
|
||||
return this.moveCardToHandWithInfo(card, sourceId, game, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveCardToHandWithInfo(Card card, UUID sourceId,
|
||||
Game game, boolean withName
|
||||
Game game, boolean withName
|
||||
) {
|
||||
boolean result = false;
|
||||
Zone fromZone = game.getState().getZone(card.getId());
|
||||
|
@ -3965,7 +3993,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public Set<Card> moveCardsToGraveyardWithInfo(Set<Card> allCards, Ability source,
|
||||
Game game, Zone fromZone
|
||||
Game game, Zone fromZone
|
||||
) {
|
||||
UUID sourceId = source == null ? null : source.getSourceId();
|
||||
Set<Card> movedCards = new LinkedHashSet<>();
|
||||
|
@ -3973,7 +4001,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
// identify cards from one owner
|
||||
Cards cards = new CardsImpl();
|
||||
UUID ownerId = null;
|
||||
for (Iterator<Card> it = allCards.iterator(); it.hasNext();) {
|
||||
for (Iterator<Card> it = allCards.iterator(); it.hasNext(); ) {
|
||||
Card card = it.next();
|
||||
if (cards.isEmpty()) {
|
||||
ownerId = card.getOwnerId();
|
||||
|
@ -4036,7 +4064,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCardToGraveyardWithInfo(Card card, UUID sourceId,
|
||||
Game game, Zone fromZone
|
||||
Game game, Zone fromZone
|
||||
) {
|
||||
if (card == null) {
|
||||
return false;
|
||||
|
@ -4065,8 +4093,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCardToLibraryWithInfo(Card card, UUID sourceId,
|
||||
Game game, Zone fromZone,
|
||||
boolean toTop, boolean withName
|
||||
Game game, Zone fromZone,
|
||||
boolean toTop, boolean withName
|
||||
) {
|
||||
if (card == null) {
|
||||
return false;
|
||||
|
@ -4131,7 +4159,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
|
@ -4154,7 +4182,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
game.informPlayers(this.getLogName() + " moves " + (withName ? card.getLogName()
|
||||
+ (card.isCopy() ? " (Copy)" : "") : "a card face down") + ' '
|
||||
+ (fromZone != null ? "from " + fromZone.toString().toLowerCase(Locale.ENGLISH)
|
||||
+ ' ' : "") + "to the exile zone");
|
||||
+ ' ' : "") + "to the exile zone");
|
||||
|
||||
}
|
||||
result = true;
|
||||
|
|
Loading…
Reference in a new issue