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.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.");
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -429,4 +429,33 @@ public class PlayFromNonHandZoneTest extends CardTestPlayerBase {
|
|||
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);
|
||||
|
|
@ -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,11 +3239,6 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
boolean canActivateAsHandZone = permittingObject != null
|
||||
|| (fromZone == Zone.GRAVEYARD && canPlayCardsFromGraveyard());
|
||||
|
||||
// as affected controller
|
||||
UUID savedControllerId = ability.getControllerId();
|
||||
ability.setControllerId(this.getId());
|
||||
try {
|
||||
boolean possibleToPlay = false;
|
||||
|
||||
// spell/hand abilities (play from all zones)
|
||||
|
|
@ -3246,7 +3259,19 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
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);
|
||||
}
|
||||
|
|
@ -3255,6 +3280,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ActivatedAbility> getPlayable(Game game, boolean hidden) {
|
||||
|
|
@ -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);
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue