mirror of
https://github.com/magefree/mage.git
synced 2026-01-26 21:29:17 -08:00
implement [MH3] Primal Prayers ; use choice panel for cast mode choice ; allow some restricted "as thought as it had flash" to work only on matching alternative cast. (#12420)
This commit is contained in:
parent
503e842b51
commit
c0eab28626
67 changed files with 1105 additions and 596 deletions
|
|
@ -69,7 +69,10 @@ public enum MageIdentifier {
|
|||
XandersPactAlternateCast,
|
||||
TheTombOfAclazotzWatcher,
|
||||
MeTheImmortalAlternateCast,
|
||||
WithoutPayingManaCostAlternateCast;
|
||||
WithoutPayingManaCostAlternateCast,
|
||||
AlurenAlternateCast,
|
||||
OfferingAlternateCast,
|
||||
PrimalPrayersAlternateCast;
|
||||
|
||||
/**
|
||||
* Additional text if there is need to differentiate two very similar effects
|
||||
|
|
|
|||
|
|
@ -25,10 +25,7 @@ import mage.target.targetadjustment.TargetAdjuster;
|
|||
import mage.watchers.Watcher;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Practically everything in the game is started from an Ability. This interface
|
||||
|
|
@ -280,12 +277,17 @@ public interface Ability extends Controllable, Serializable {
|
|||
/**
|
||||
* Activates this ability prompting the controller to pay any mandatory
|
||||
*
|
||||
* @param game A reference the {@link Game} for which this ability should be
|
||||
* activated within.
|
||||
* @param noMana Whether or not {@link ManaCosts} have to be paid.
|
||||
* @param game A reference the {@link Game} for which this ability should be
|
||||
* activated within.
|
||||
* @param allowedIdentifiers Restrict alternative/regular cost depending (if contain MageIdentifier.Default, there is no restriction)
|
||||
* @param noMana Whether or not {@link ManaCosts} have to be paid.
|
||||
* @return True if this ability was successfully activated.
|
||||
*/
|
||||
boolean activate(Game game, boolean noMana);
|
||||
boolean activate(Game game, Set<MageIdentifier> allowedIdentifiers, boolean noMana);
|
||||
|
||||
default boolean activate(Game game, boolean noMana) {
|
||||
return activate(game, new HashSet<>(Arrays.asList(MageIdentifier.Default)), noMana);
|
||||
}
|
||||
|
||||
boolean isActivated();
|
||||
|
||||
|
|
@ -472,7 +474,7 @@ public interface Ability extends Controllable, Serializable {
|
|||
*/
|
||||
String getGameLogMessage(Game game);
|
||||
|
||||
boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, boolean noMana, Player controller, Game game);
|
||||
boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, Set<MageIdentifier> allowedIdentifiers, boolean noMana, Player controller, Game game);
|
||||
|
||||
/**
|
||||
* Finds the source object regardless of its zcc. Can be LKI from battlefield in some cases.
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ import mage.abilities.hint.Hint;
|
|||
import mage.abilities.icon.CardIcon;
|
||||
import mage.abilities.mana.ActivatedManaAbilityImpl;
|
||||
import mage.cards.Card;
|
||||
import mage.choices.Choice;
|
||||
import mage.choices.ChoiceHintType;
|
||||
import mage.choices.ChoiceImpl;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.game.command.Dungeon;
|
||||
|
|
@ -255,7 +258,7 @@ public abstract class AbilityImpl implements Ability {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean activate(Game game, boolean noMana) {
|
||||
public boolean activate(Game game, Set<MageIdentifier> allowedIdentifiers, boolean noMana) {
|
||||
Player controller = game.getPlayer(this.getControllerId());
|
||||
if (controller == null) {
|
||||
return false;
|
||||
|
|
@ -302,7 +305,9 @@ public abstract class AbilityImpl implements Ability {
|
|||
// or her intentions to pay any or all of those costs (see rule 601.2e).
|
||||
// A player can't apply two alternative methods of casting or two alternative costs to a single spell.
|
||||
if (isMainPartAbility) {
|
||||
activateAlternateOrAdditionalCosts(sourceObject, noMana, controller, game);
|
||||
if (!activateAlternateOrAdditionalCosts(sourceObject, allowedIdentifiers, noMana, controller, game)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 117.6. Some mana costs contain no mana symbols. This represents an unpayable cost. An ability can
|
||||
|
|
@ -451,8 +456,11 @@ public abstract class AbilityImpl implements Ability {
|
|||
return activated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if choices for the activation were made (can be to activate with the regular cost)
|
||||
*/
|
||||
@Override
|
||||
public boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, boolean noMana, Player controller, Game game) {
|
||||
public boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, Set<MageIdentifier> allowedIdentifiers, boolean noMana, Player controller, Game game) {
|
||||
boolean canUseAlternativeCost = true;
|
||||
boolean canUseAdditionalCost = true;
|
||||
|
||||
|
|
@ -494,48 +502,104 @@ public abstract class AbilityImpl implements Ability {
|
|||
canUseAlternativeCost = false;
|
||||
}
|
||||
|
||||
boolean alternativeCostUsed = false;
|
||||
if (sourceObject != null && !(sourceObject instanceof Permanent)) {
|
||||
// it's important to apply alternative cost first
|
||||
// example: Omniscience gives free mana as alternative, but Entwine ability adds {2} as additional
|
||||
Abilities<Ability> abilities = CardUtil.getAbilities(sourceObject, game);
|
||||
// TODO: Why the check for permanent?
|
||||
if (sourceObject == null || sourceObject instanceof Permanent) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 1. ALTERNATIVE COSTS
|
||||
for (Ability ability : abilities) {
|
||||
// if cast for noMana no Alternative costs are allowed
|
||||
if (canUseAlternativeCost && !noMana && ability instanceof AlternativeSourceCosts) {
|
||||
AlternativeSourceCosts alternativeSpellCosts = (AlternativeSourceCosts) ability;
|
||||
if (alternativeSpellCosts.isAvailable(this, game)) {
|
||||
if (alternativeSpellCosts.askToActivateAlternativeCosts(this, game)) {
|
||||
// only one alternative costs may be activated
|
||||
alternativeCostUsed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// controller specific alternate spell costs
|
||||
if (canUseAlternativeCost && !noMana && !alternativeCostUsed) {
|
||||
for (AlternativeSourceCosts alternativeSourceCosts : controller.getAlternativeSourceCosts()) {
|
||||
if (alternativeSourceCosts.isAvailable(this, game)) {
|
||||
if (alternativeSourceCosts.askToActivateAlternativeCosts(this, game)) {
|
||||
// only one alternative costs may be activated
|
||||
alternativeCostUsed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// it's important to apply alternative cost first
|
||||
// example: Omniscience gives free mana as alternative, but Entwine ability adds {2} as additional
|
||||
Abilities<Ability> abilities = CardUtil.getAbilities(sourceObject, game);
|
||||
|
||||
// 2. ADDITIONAL COST
|
||||
for (Ability ability : abilities) {
|
||||
if (canUseAdditionalCost && ability instanceof OptionalAdditionalSourceCosts) {
|
||||
((OptionalAdditionalSourceCosts) ability).addOptionalAdditionalCosts(this, game);
|
||||
// 1. ALTERNATIVE COSTS
|
||||
// Collect all possible alternatives costs:
|
||||
List<AlternativeSourceCosts> possibleAlternatives = new ArrayList<>();
|
||||
for (Ability ability : abilities) {
|
||||
// if cast for noMana no Alternative costs are allowed
|
||||
if (canUseAlternativeCost && !noMana && ability instanceof AlternativeSourceCosts) {
|
||||
AlternativeSourceCosts alternativeSpellCosts = (AlternativeSourceCosts) ability;
|
||||
if (alternativeSpellCosts.isAvailable(this, game)
|
||||
&& alternativeSpellCosts.canActivateAlternativeCostsNow(this, game)
|
||||
&& (allowedIdentifiers.contains(MageIdentifier.Default) || allowedIdentifiers.contains(ability.getIdentifier()))) {
|
||||
possibleAlternatives.add(alternativeSpellCosts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return alternativeCostUsed;
|
||||
// controller specific alternate spell costs
|
||||
if (canUseAlternativeCost && !noMana) {
|
||||
for (AlternativeSourceCosts alternativeSourceCosts : controller.getAlternativeSourceCosts()) {
|
||||
if (alternativeSourceCosts.isAvailable(this, game)
|
||||
&& alternativeSourceCosts.canActivateAlternativeCostsNow(this, game)
|
||||
&& (allowedIdentifiers.contains(MageIdentifier.Default) || allowedIdentifiers.contains(alternativeSourceCosts.getIdentifier()))) {
|
||||
possibleAlternatives.add(alternativeSourceCosts);
|
||||
}
|
||||
}
|
||||
}
|
||||
Player player = game.getPlayer(getControllerId());
|
||||
if (player == null) {
|
||||
// No controller to activate.
|
||||
return false;
|
||||
}
|
||||
Choice choice = new ChoiceImpl(false); // not required, cancelling will cancel the cast (as you could do once in the pay mana mode).
|
||||
choice.setSubMessage("for casting " + CardUtil.getSourceLogName(game, "", this, "", ""));
|
||||
AlternativeSourceCosts alternativeChosen = null;
|
||||
if (!possibleAlternatives.isEmpty()) {
|
||||
// At least one alternative cost is available.
|
||||
// We open a menu for the player to choose up to one.
|
||||
boolean mustChooseAlternative = !(allowedIdentifiers.contains(MageIdentifier.Default) || allowedIdentifiers.contains(getIdentifier()));
|
||||
choice.setMessage(
|
||||
mustChooseAlternative
|
||||
? "Choose an alternative cost"
|
||||
: "You may choose an alternative cost"
|
||||
);
|
||||
Map<String, Integer> sort = new LinkedHashMap<>();
|
||||
int i;
|
||||
for (i = 0; i < possibleAlternatives.size(); i++) {
|
||||
String key = Integer.toString(i + 1);
|
||||
sort.put(key, i);
|
||||
AlternativeSourceCosts alternative = possibleAlternatives.get(i);
|
||||
MageObject object = alternative.getSourceObject(game);
|
||||
choice.withItem(
|
||||
key,
|
||||
possibleAlternatives.get(i).getAlternativeCostText(this, game),
|
||||
i,
|
||||
object != null ? ChoiceHintType.GAME_OBJECT : null,
|
||||
object != null ? object.getId().toString() : null
|
||||
);
|
||||
}
|
||||
if (!mustChooseAlternative) {
|
||||
// add the non-alternative cast as the last option.
|
||||
String key = Integer.toString(i + 1);
|
||||
sort.put(key, i);
|
||||
choice.withItem(
|
||||
key,
|
||||
"Cast with no alternative cost: " + this.getManaCosts().getText(),
|
||||
i,
|
||||
ChoiceHintType.GAME_OBJECT,
|
||||
sourceObject.getId().toString()
|
||||
);
|
||||
}
|
||||
if (!player.choose(Outcome.Benefit, choice, game)) {
|
||||
return false;
|
||||
}
|
||||
String choiceKey = choice.getChoiceKey();
|
||||
if (sort.containsKey(choiceKey)) {
|
||||
int choiceNumber = sort.get(choiceKey);
|
||||
if (choiceNumber < possibleAlternatives.size()) {
|
||||
alternativeChosen = possibleAlternatives.get(choiceNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (alternativeChosen != null) {
|
||||
alternativeChosen.activateAlternativeCosts(this, game);
|
||||
}
|
||||
// 2. ADDITIONAL COST
|
||||
for (Ability ability : abilities) {
|
||||
if (canUseAdditionalCost && ability instanceof OptionalAdditionalSourceCosts) {
|
||||
((OptionalAdditionalSourceCosts) ability).addOptionalAdditionalCosts(this, game);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package mage.abilities;
|
||||
|
||||
import mage.ApprovingObject;
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.abilities.costs.Cost;
|
||||
|
|
@ -215,8 +216,8 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean activate(Game game, boolean noMana) {
|
||||
if (!hasMoreActivationsThisTurn(game) || !super.activate(game, noMana)) {
|
||||
public boolean activate(Game game, Set<MageIdentifier> allowedIdentifiers, boolean noMana) {
|
||||
if (!hasMoreActivationsThisTurn(game) || !super.activate(game, allowedIdentifiers, noMana)) {
|
||||
return false;
|
||||
}
|
||||
ActivationInfo activationInfo = getActivationInfo(game);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import mage.players.Player;
|
|||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
|
|
@ -79,10 +80,20 @@ public class SpellAbility extends ActivatedAbilityImpl {
|
|||
* can be casted that are affected by the CastAsInstant effect.
|
||||
* (i.e. Vizier of the Menagerie and issue #5816)
|
||||
*/
|
||||
public boolean spellCanBeActivatedRegularlyNow(UUID playerId, Game game) {
|
||||
|
||||
private static final Set<MageIdentifier> activationSetAllowAll = new HashSet();
|
||||
|
||||
static {
|
||||
activationSetAllowAll.add(MageIdentifier.Default);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the set of cast method MageIdentifer that are allowed to be cast at this time (if MageIdentifier.Default is in it, there is no restriction)
|
||||
*/
|
||||
public Set<MageIdentifier> spellCanBeActivatedNow(UUID playerId, Game game) {
|
||||
MageObject object = game.getObject(sourceId);
|
||||
if (object == null) {
|
||||
return false;
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
// forced to cast (can be part id or main id)
|
||||
|
|
@ -93,23 +104,48 @@ public class SpellAbility extends ActivatedAbilityImpl {
|
|||
}
|
||||
for (UUID idToCheck : idsToCheck) {
|
||||
if (game.getState().getValue("PlayFromNotOwnHandZone" + idToCheck) != null) {
|
||||
return (Boolean) game.getState().getValue("PlayFromNotOwnHandZone" + idToCheck); // card like Chandra, Torch of Defiance +1 loyal ability)
|
||||
if ((Boolean) game.getState().getValue("PlayFromNotOwnHandZone" + idToCheck)) {
|
||||
return activationSetAllowAll;
|
||||
} else {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST_AS_INSTANT, this, playerId, game).isEmpty() // check this first to allow Offering in main phase
|
||||
|| timing == TimingRule.INSTANT
|
||||
// OfferingAbility is doing side effects in its asThough computation.
|
||||
// so we call it before the timing check.
|
||||
// TODO: maybe Offering could be reworked with the MageIdentifier solution of linking
|
||||
// CAST_AS_INSTANT with alternative cast methods?
|
||||
Set<ApprovingObject> asInstantApprovers = game.getContinuousEffects()
|
||||
.asThough(sourceId, AsThoughEffectType.CAST_AS_INSTANT, this, playerId, game);
|
||||
|
||||
if (timing == TimingRule.INSTANT
|
||||
|| object.isInstant(game)
|
||||
|| object.hasAbility(FlashAbility.getInstance(), game)
|
||||
|| game.canPlaySorcery(playerId);
|
||||
|| game.canPlaySorcery(playerId)) {
|
||||
return activationSetAllowAll;
|
||||
}
|
||||
|
||||
// In case there is a need for as AsThoughEffectType.CAST_AS_INSTANT, we do collect the MageIdentifer of the approving objects
|
||||
// to match later on with the real method to cast. When only non-Default MageIdentifier are used, only some of the alternative
|
||||
// cast are possible to activate.
|
||||
Set<MageIdentifier> setOfIdentifier = new HashSet<>();
|
||||
setOfIdentifier.addAll(
|
||||
asInstantApprovers
|
||||
.stream()
|
||||
.map(ApprovingObject::getApprovingAbility)
|
||||
.map(Ability::getIdentifier)
|
||||
.collect(Collectors.toSet())
|
||||
);
|
||||
return setOfIdentifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActivationStatus canActivate(UUID playerId, Game game) {
|
||||
// spells can be cast from non hand zones, so must use custom check
|
||||
// no super.canActivate() call
|
||||
|
||||
if (this.spellCanBeActivatedRegularlyNow(playerId, game)) {
|
||||
Set<MageIdentifier> allowedIdentifiers = this.spellCanBeActivatedNow(playerId, game);
|
||||
if (!allowedIdentifiers.isEmpty()) {
|
||||
if (spellAbilityType == SpellAbilityType.SPLIT
|
||||
|| spellAbilityType == SpellAbilityType.SPLIT_AFTERMATH) {
|
||||
return ActivationStatus.getFalse();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package mage.abilities.common;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageObject;
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
|
|
@ -96,8 +95,10 @@ class CastFromGraveyardOnceEffect extends AsThoughEffectImpl {
|
|||
if (spellAbility.getManaCosts().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return spellAbility.spellCanBeActivatedRegularlyNow(playerId, game)
|
||||
&& filter.match(cardToCheck, playerId, source, game);
|
||||
Set<MageIdentifier> allowedToBeCastNow = spellAbility.spellCanBeActivatedNow(playerId, game);
|
||||
if (allowedToBeCastNow.contains(MageIdentifier.Default)) {
|
||||
return filter.match(cardToCheck, playerId, source, game);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.abilities.common;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
|
|
@ -10,6 +11,7 @@ import mage.constants.*;
|
|||
import mage.game.Game;
|
||||
import mage.game.stack.Spell;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
|
@ -48,6 +50,7 @@ public class SpellTransformedAbility extends SpellAbility {
|
|||
this.setSpellAbilityCastMode(SpellAbilityCastMode.TRANSFORMED);
|
||||
//when casting this way, the card must have the TransformAbility from elsewhere
|
||||
}
|
||||
|
||||
protected SpellTransformedAbility(final SpellTransformedAbility ability) {
|
||||
super(ability);
|
||||
this.manaCost = ability.manaCost;
|
||||
|
|
@ -59,8 +62,8 @@ public class SpellTransformedAbility extends SpellAbility {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean activate(Game game, boolean noMana) {
|
||||
if (super.activate(game, noMana)) {
|
||||
public boolean activate(Game game, Set<MageIdentifier> allowedIdentifiers, boolean noMana) {
|
||||
if (super.activate(game, allowedIdentifiers, noMana)) {
|
||||
game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + getSourceId(), Boolean.TRUE);
|
||||
// TODO: must be removed after transform cards (one side) migrated to MDF engine (multiple sides)
|
||||
TransformedEffect effect = new TransformedEffect();
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import mage.abilities.condition.Condition;
|
|||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.AbilityType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.game.Game;
|
||||
|
|
@ -50,13 +49,13 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
|||
}
|
||||
|
||||
/**
|
||||
* @param cost alternate cost to pay
|
||||
* @param cost alternate cost to pay
|
||||
* @param condition only if the condition is true it's possible to use the
|
||||
* alternate costs
|
||||
* @param rule if != null used as rule text
|
||||
* @param filter filters the cards this alternate cost can be applied to
|
||||
* @param onlyMana if true only the mana costs are replaced by this costs,
|
||||
* other costs stay untouched
|
||||
* alternate costs
|
||||
* @param rule if != null used as rule text
|
||||
* @param filter filters the cards this alternate cost can be applied to
|
||||
* @param onlyMana if true only the mana costs are replaced by this costs,
|
||||
* other costs stay untouched
|
||||
*/
|
||||
public AlternativeCostSourceAbility(Cost cost, Condition condition, String rule, FilterCard filter, boolean onlyMana) {
|
||||
super(Zone.ALL, null);
|
||||
|
|
@ -115,69 +114,88 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean askToActivateAlternativeCosts(Ability ability, Game game) {
|
||||
if (ability != null && AbilityType.SPELL == ability.getAbilityType()) {
|
||||
if (filter != null) {
|
||||
Card card = game.getCard(ability.getSourceId());
|
||||
if (!filter.match(card, ability.getControllerId(), ability, game)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Player player = game.getPlayer(ability.getControllerId());
|
||||
if (player != null) {
|
||||
Costs<AlternativeCost> alternativeCostsToCheck;
|
||||
if (dynamicCost != null) {
|
||||
alternativeCostsToCheck = new CostsImpl<>();
|
||||
alternativeCostsToCheck.add(convertToAlternativeCost(dynamicCost.getCost(ability, game)));
|
||||
} else {
|
||||
alternativeCostsToCheck = this.alternateCosts;
|
||||
}
|
||||
|
||||
String costChoiceText;
|
||||
if (dynamicCost != null) {
|
||||
costChoiceText = dynamicCost.getText(ability, game);
|
||||
} else {
|
||||
costChoiceText = alternativeCostsToCheck.isEmpty() ? "Cast without paying its mana cost?" : "Pay alternative costs? (" + alternativeCostsToCheck.getText() + ')';
|
||||
}
|
||||
if (alternativeCostsToCheck.canPay(ability, ability, ability.getControllerId(), game)
|
||||
&& player.chooseUse(Outcome.Benefit, costChoiceText, this, game)) {
|
||||
if (ability instanceof SpellAbility) {
|
||||
ability.getManaCostsToPay().removeIf(VariableCost.class::isInstance);
|
||||
CardUtil.reduceCost((SpellAbility) ability, ability.getManaCosts());
|
||||
|
||||
} else {
|
||||
ability.clearManaCostsToPay();
|
||||
}
|
||||
if (!onlyMana) {
|
||||
ability.clearCosts();
|
||||
}
|
||||
for (AlternativeCost alternateCost : alternativeCostsToCheck) {
|
||||
alternateCost.activate();
|
||||
for (Iterator it = ((Costs) alternateCost).iterator(); it.hasNext();) {
|
||||
Cost costDetailed = (Cost) it.next();
|
||||
if (costDetailed instanceof ManaCost) {
|
||||
ability.addManaCostsToPay((ManaCost) costDetailed.copy());
|
||||
} else if (costDetailed != null) {
|
||||
ability.addCost(costDetailed.copy());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Those cost have been paid, we want to store them.
|
||||
if (dynamicCost != null) {
|
||||
rememberDynamicCost(game, ability, alternativeCostsToCheck);
|
||||
}
|
||||
|
||||
// save activated status
|
||||
doActivate(game, ability);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
public boolean canActivateAlternativeCostsNow(Ability ability, Game game) {
|
||||
if (ability == null || !AbilityType.SPELL.equals(ability.getAbilityType())) {
|
||||
return isActivated(ability, game);
|
||||
}
|
||||
if (filter != null) {
|
||||
Card card = game.getCard(ability.getSourceId());
|
||||
if (!filter.match(card, ability.getControllerId(), ability, game)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return isActivated(ability, game);
|
||||
Player player = game.getPlayer(ability.getControllerId());
|
||||
if (player == null) {
|
||||
return false;
|
||||
}
|
||||
Costs<AlternativeCost> alternativeCostsToCheck;
|
||||
if (dynamicCost != null) {
|
||||
alternativeCostsToCheck = new CostsImpl<>();
|
||||
alternativeCostsToCheck.add(convertToAlternativeCost(dynamicCost.getCost(ability, game)));
|
||||
} else {
|
||||
alternativeCostsToCheck = this.alternateCosts;
|
||||
}
|
||||
|
||||
return alternativeCostsToCheck.canPay(ability, ability, ability.getControllerId(), game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAlternativeCostText(Ability ability, Game game) {
|
||||
if (dynamicCost != null) {
|
||||
return "Cast with alternative cost: " + dynamicCost.getText(ability, game) + CardUtil.getSourceLogName(game, this);
|
||||
} else {
|
||||
Costs<AlternativeCost> alternativeCostsToCheck;
|
||||
if (dynamicCost != null) {
|
||||
alternativeCostsToCheck = new CostsImpl<>();
|
||||
alternativeCostsToCheck.add(convertToAlternativeCost(dynamicCost.getCost(ability, game)));
|
||||
} else {
|
||||
alternativeCostsToCheck = this.alternateCosts;
|
||||
}
|
||||
return alternativeCostsToCheck.isEmpty()
|
||||
? "Cast without paying its mana cost" + CardUtil.getSourceLogName(game, this)
|
||||
: "Cast with alternative cost: " + alternativeCostsToCheck.getText() + CardUtil.getSourceLogName(game, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean activateAlternativeCosts(Ability ability, Game game) {
|
||||
Costs<AlternativeCost> alternativeCostsToCheck;
|
||||
if (dynamicCost != null) {
|
||||
alternativeCostsToCheck = new CostsImpl<>();
|
||||
alternativeCostsToCheck.add(convertToAlternativeCost(dynamicCost.getCost(ability, game)));
|
||||
} else {
|
||||
alternativeCostsToCheck = this.alternateCosts;
|
||||
}
|
||||
if (ability instanceof SpellAbility) {
|
||||
ability.getManaCostsToPay().removeIf(VariableCost.class::isInstance);
|
||||
CardUtil.reduceCost((SpellAbility) ability, ability.getManaCosts());
|
||||
|
||||
} else {
|
||||
ability.clearManaCostsToPay();
|
||||
}
|
||||
if (!onlyMana) {
|
||||
ability.clearCosts();
|
||||
}
|
||||
for (AlternativeCost alternateCost : alternativeCostsToCheck) {
|
||||
alternateCost.activate();
|
||||
for (Iterator it = ((Costs) alternateCost).iterator(); it.hasNext(); ) {
|
||||
Cost costDetailed = (Cost) it.next();
|
||||
if (costDetailed instanceof ManaCost) {
|
||||
ability.addManaCostsToPay((ManaCost) costDetailed.copy());
|
||||
} else if (costDetailed != null) {
|
||||
ability.addCost(costDetailed.copy());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Those cost have been paid, we want to store them.
|
||||
if (dynamicCost != null) {
|
||||
rememberDynamicCost(game, ability, alternativeCostsToCheck);
|
||||
}
|
||||
|
||||
// save activated status
|
||||
doActivate(game, ability);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void doActivate(Game game, Ability ability) {
|
||||
|
|
@ -217,8 +235,8 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
|||
* @param game
|
||||
* @param source
|
||||
* @param alternativeCostOriginalId you must save originalId on card's
|
||||
* creation
|
||||
* @param searchPrevZCC true on battlefield, false on stack
|
||||
* creation
|
||||
* @param searchPrevZCC true on battlefield, false on stack
|
||||
* @return
|
||||
*/
|
||||
public static boolean getActivatedStatus(Game game, Ability source, UUID alternativeCostOriginalId, boolean searchPrevZCC) {
|
||||
|
|
@ -280,8 +298,8 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
|||
sb.append(CardUtil.concatWithAnd(alternateCosts
|
||||
.stream()
|
||||
.map(cost -> cost.getCost() instanceof ManaCost
|
||||
? "pay " + cost.getText(true)
|
||||
: cost.getText(true))
|
||||
? "pay " + cost.getText(true)
|
||||
: cost.getText(true))
|
||||
.map(CardUtil::getTextWithFirstCharLowerCase)
|
||||
.collect(Collectors.toList())));
|
||||
if (condition == null || alternateCosts.size() == 1) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.abilities.costs;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.abilities.Ability;
|
||||
import mage.game.Game;
|
||||
|
||||
|
|
@ -10,16 +11,34 @@ import mage.game.Game;
|
|||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public interface AlternativeSourceCosts {
|
||||
public interface AlternativeSourceCosts extends Ability {
|
||||
|
||||
/**
|
||||
* Ask the player if they want to use the alternative costs
|
||||
* If non-Default, allow to link this cost to permission abilities
|
||||
*/
|
||||
MageIdentifier getIdentifier();
|
||||
|
||||
/**
|
||||
* Check that the alternative ability can be used for the ability.
|
||||
*
|
||||
* @param ability ability the alternative cost is activated for
|
||||
* @param game
|
||||
* @return
|
||||
*/
|
||||
boolean askToActivateAlternativeCosts(Ability ability, Game game);
|
||||
boolean canActivateAlternativeCostsNow(Ability ability, Game game);
|
||||
|
||||
String getAlternativeCostText(Ability ability, Game game);
|
||||
|
||||
/**
|
||||
* activate a specific alternative cost.
|
||||
* A check to canActivateAlternativeCostsNow should be made before-end
|
||||
* to check if it is valid.
|
||||
*
|
||||
* @param ability ability the alternative cost is activated for
|
||||
* @param game
|
||||
* @return
|
||||
*/
|
||||
boolean activateAlternativeCosts(Ability ability, Game game);
|
||||
|
||||
/**
|
||||
* Is the alternative spell cost currently available
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
package mage.abilities.costs;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.StaticAbility;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.AbilityType;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
|
|
@ -21,8 +20,9 @@ public abstract class AlternativeSourceCostsImpl extends StaticAbility implement
|
|||
protected final AlternativeCost alternativeCost;
|
||||
protected final String reminderText;
|
||||
protected final String activationKey;
|
||||
protected static String getActivationKey(String name){
|
||||
return name+"ActivationKey";
|
||||
|
||||
protected static String getActivationKey(String name) {
|
||||
return name + "ActivationKey";
|
||||
}
|
||||
|
||||
protected AlternativeSourceCostsImpl(String name, String reminderText, String manaString) {
|
||||
|
|
@ -44,24 +44,24 @@ public abstract class AlternativeSourceCostsImpl extends StaticAbility implement
|
|||
this.activationKey = ability.activationKey;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean askToActivateAlternativeCosts(Ability ability, Game game) {
|
||||
if (ability instanceof SpellAbility) {
|
||||
handleActivatingAlternativeCosts(ability, game);
|
||||
public boolean canActivateAlternativeCostsNow(Ability ability, Game game) {
|
||||
if (ability == null || !AbilityType.SPELL.equals(ability.getAbilityType())) {
|
||||
return isActivated(ability, game);
|
||||
}
|
||||
return isActivated(ability, game);
|
||||
Player player = game.getPlayer(ability.getControllerId());
|
||||
return player != null && alternativeCost.canPay(ability, this, player.getId(), game);
|
||||
}
|
||||
|
||||
protected boolean handleActivatingAlternativeCosts(Ability ability, Game game) {
|
||||
Player player = game.getPlayer(ability.getControllerId());
|
||||
if (player == null) {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public String getAlternativeCostText(Ability ability, Game game) {
|
||||
return "Cast with " + this.name + " alternative cost: " + alternativeCost.getText(true) + CardUtil.getSourceLogName(game, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean activateAlternativeCosts(Ability ability, Game game) {
|
||||
this.resetCost();
|
||||
if (!alternativeCost.canPay(ability, this, player.getId(), game)
|
||||
|| !player.chooseUse(Outcome.Benefit, "Cast this for its " + this.name + " cost? (" + alternativeCost.getText(true) + ')', ability, game)) {
|
||||
return false;
|
||||
}
|
||||
ability.setCostsTag(activationKey, null);
|
||||
alternativeCost.activate();
|
||||
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@ import mage.filter.StaticFilters;
|
|||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class CastFromHandWithoutPayingManaCostEffect extends ContinuousEffectImpl {
|
||||
|
||||
private final AlternativeCostSourceAbility alternativeCastingCostAbility;
|
||||
|
|
@ -54,18 +52,13 @@ public class CastFromHandWithoutPayingManaCostEffect extends ContinuousEffectImp
|
|||
return new CastFromHandWithoutPayingManaCostEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Ability source, Game game, UUID activePlayerId) {
|
||||
super.init(source, game, activePlayerId);
|
||||
alternativeCastingCostAbility.setSourceId(source.getSourceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
alternativeCastingCostAbility.setSourceId(source.getSourceId());
|
||||
controller.getAlternativeSourceCosts().add(alternativeCastingCostAbility);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.common.DiesSourceTriggeredAbility;
|
||||
|
|
@ -19,6 +20,8 @@ import mage.constants.TimingRule;
|
|||
import mage.game.Game;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
|
|
@ -73,8 +76,8 @@ public class BlitzAbility extends SpellAbility {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean activate(Game game, boolean noMana) {
|
||||
if (!super.activate(game, noMana)) {
|
||||
public boolean activate(Game game, Set<MageIdentifier> allowedIdentifiers, boolean noMana) {
|
||||
if (!super.activate(game, allowedIdentifiers, noMana)) {
|
||||
return false;
|
||||
}
|
||||
this.setCostsTag(BLITZ_ACTIVATION_VALUE_KEY, null);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.ApprovingObject;
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageObjectReference;
|
||||
import mage.Mana;
|
||||
import mage.abilities.SpellAbility;
|
||||
|
|
@ -20,6 +21,7 @@ import mage.players.Player;
|
|||
import mage.target.common.TargetSacrifice;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
|
@ -101,7 +103,7 @@ public class EmergeAbility extends SpellAbility {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean activate(Game game, boolean noMana) {
|
||||
public boolean activate(Game game, Set<MageIdentifier> allowedIdentifiers, boolean noMana) {
|
||||
Player controller = game.getPlayer(this.getControllerId());
|
||||
if (controller != null) {
|
||||
TargetSacrifice target = new TargetSacrifice(filter);
|
||||
|
|
@ -110,7 +112,7 @@ public class EmergeAbility extends SpellAbility {
|
|||
Permanent creature = game.getPermanent(target.getFirstTarget());
|
||||
if (creature != null) {
|
||||
CardUtil.reduceCost(this, creature.getManaValue());
|
||||
if (super.activate(game, false)) {
|
||||
if (super.activate(game, allowedIdentifiers, false)) {
|
||||
MageObjectReference mor = new MageObjectReference(creature, game);
|
||||
if (creature.sacrifice(this, game)) {
|
||||
this.setCostsTag(EMERGE_ACTIVATION_CREATURE_REFERENCE, mor); //Can access with LKI afterwards
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.Costs;
|
||||
|
|
@ -15,6 +16,7 @@ import mage.game.Game;
|
|||
import mage.target.common.TargetCardInYourGraveyard;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
|
@ -97,8 +99,8 @@ public class EscapeAbility extends SpellAbility {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean activate(Game game, boolean noMana) {
|
||||
if (super.activate(game, noMana)) {
|
||||
public boolean activate(Game game, Set<MageIdentifier> allowedIdentifiers, boolean noMana) {
|
||||
if (super.activate(game, allowedIdentifiers, noMana)) {
|
||||
game.getState().setValue(CASTED_WITH_ESCAPE_KEY + getSourceId().toString() + (getSourceObjectZoneChangeCounter() + 1), Boolean.TRUE);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpecialAction;
|
||||
|
|
@ -17,6 +18,7 @@ import mage.players.Player;
|
|||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
|
@ -78,7 +80,8 @@ public class PlotAbility extends SpecialAction {
|
|||
// Not Allowed from other zones
|
||||
return ActivationStatus.getFalse();
|
||||
}
|
||||
if (!card.getSpellAbility().spellCanBeActivatedRegularlyNow(playerId, game)) {
|
||||
Set<MageIdentifier> allowedToBeCastNow = card.getSpellAbility().spellCanBeActivatedNow(playerId, game);
|
||||
if (!allowedToBeCastNow.contains(MageIdentifier.Default) && !allowedToBeCastNow.contains(card.getSpellAbility().getIdentifier())) {
|
||||
return ActivationStatus.getFalse();
|
||||
}
|
||||
return super.canActivate(playerId, game);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.ApprovingObject;
|
||||
import mage.MageIdentifier;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.dynamicvalue.common.OpponentsLostLifeCount;
|
||||
|
|
@ -10,6 +11,7 @@ import mage.constants.SpellAbilityType;
|
|||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
|
@ -53,9 +55,9 @@ public class SpectacleAbility extends SpellAbility {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean activate(Game game, boolean noMana) {
|
||||
if (super.activate(game, noMana)) {
|
||||
this.setCostsTag(SPECTACLE_ACTIVATION_VALUE_KEY,null);
|
||||
public boolean activate(Game game, Set<MageIdentifier> allowedIdentifiers, boolean noMana) {
|
||||
if (super.activate(game, allowedIdentifiers, noMana)) {
|
||||
this.setCostsTag(SPECTACLE_ACTIVATION_VALUE_KEY, null);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.ApprovingObject;
|
||||
import mage.MageIdentifier;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.cards.Card;
|
||||
|
|
@ -10,6 +11,7 @@ import mage.game.Game;
|
|||
import mage.players.Player;
|
||||
import mage.watchers.common.CastSpellLastTurnWatcher;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
|
@ -63,8 +65,8 @@ public class SurgeAbility extends SpellAbility {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean activate(Game game, boolean noMana) {
|
||||
if (super.activate(game, noMana)) {
|
||||
public boolean activate(Game game, Set<MageIdentifier> allowedIdentifiers, boolean noMana) {
|
||||
if (super.activate(game, allowedIdentifiers, noMana)) {
|
||||
this.setCostsTag(SURGE_ACTIVATION_VALUE_KEY, null);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.ApprovingObject;
|
||||
import mage.MageIdentifier;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpecialAction;
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
|
|
@ -27,6 +28,7 @@ import mage.util.CardUtil;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
|
@ -216,7 +218,8 @@ public class SuspendAbility extends SpecialAction {
|
|||
if (card == null) {
|
||||
return ActivationStatus.getFalse();
|
||||
}
|
||||
if (!card.getSpellAbility().spellCanBeActivatedRegularlyNow(playerId, game)) {
|
||||
Set<MageIdentifier> allowedToBeCastNow = card.getSpellAbility().spellCanBeActivatedNow(playerId, game);
|
||||
if (!allowedToBeCastNow.contains(MageIdentifier.Default) && !allowedToBeCastNow.contains(card.getSpellAbility().getIdentifier())) {
|
||||
return ActivationStatus.getFalse();
|
||||
}
|
||||
return super.canActivate(playerId, game);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import mage.abilities.costs.Cost;
|
|||
import mage.abilities.effects.mana.AddConditionalColorlessManaEffect;
|
||||
import mage.abilities.effects.mana.BasicManaEffect;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
|
||||
public class ActivateIfConditionManaAbility extends ActivatedManaAbilityImpl {
|
||||
|
||||
|
|
@ -26,11 +25,6 @@ public class ActivateIfConditionManaAbility extends ActivatedManaAbilityImpl {
|
|||
super(ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean activate(Game game, boolean noMana) {
|
||||
return super.activate(game, noMana);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return super.getRule() + " Activate only if " + condition.toString() + '.';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
package mage.abilities.mana;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.Mana;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.effects.mana.AddManaOfAnyColorEffect;
|
||||
|
|
@ -12,6 +13,7 @@ import mage.util.CardUtil;
|
|||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author LevelX2, Susucr
|
||||
|
|
@ -50,9 +52,9 @@ public class LimitedTimesPerTurnActivatedManaAbility extends ActivatedManaAbilit
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean activate(Game game, boolean noMana) {
|
||||
public boolean activate(Game game, Set<MageIdentifier> allowedIdentifiers, boolean noMana) {
|
||||
if (canActivate(this.controllerId, game).canActivate()) {
|
||||
return super.activate(game, noMana);
|
||||
return super.activate(game, allowedIdentifiers, noMana);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -311,7 +311,10 @@ public class ChoiceImpl implements Choice {
|
|||
// no key answer found, try to match by text starting with
|
||||
for (String needChoice : answers) {
|
||||
for (Map.Entry<String, String> currentChoice : this.getKeyChoices().entrySet()) {
|
||||
if (currentChoice.getValue().startsWith(needChoice)) {
|
||||
String choiceValue = currentChoice.getValue();
|
||||
// Clean any html part (for easier unit test matching)
|
||||
String cleanedChoiceValue = choiceValue.replaceAll("<[^<>]*>", "");
|
||||
if (choiceValue.startsWith(needChoice) || cleanedChoiceValue.startsWith(needChoice)) {
|
||||
if (removeSelectAnswerFromList) {
|
||||
this.setChoiceByKey(currentChoice.getKey(), false);
|
||||
answers.remove(needChoice);
|
||||
|
|
@ -324,7 +327,9 @@ public class ChoiceImpl implements Choice {
|
|||
// string mode
|
||||
for (String needChoice : answers) {
|
||||
for (String currentChoice : this.getChoices()) {
|
||||
if (currentChoice.equals(needChoice)) {
|
||||
// Clean any html part (for easier unit test matching)
|
||||
String cleanedChoiceValue = currentChoice.replaceAll("<[^<>]*>", "");
|
||||
if (currentChoice.equals(needChoice) || cleanedChoiceValue.equals(needChoice)) {
|
||||
if (removeSelectAnswerFromList) {
|
||||
this.setChoice(needChoice, false);
|
||||
answers.remove(needChoice);
|
||||
|
|
|
|||
|
|
@ -161,10 +161,10 @@ public class Spell extends StackObjectImpl implements Card {
|
|||
this.startingDefense = spell.startingDefense;
|
||||
}
|
||||
|
||||
public boolean activate(Game game, boolean noMana) {
|
||||
public boolean activate(Game game, Set<MageIdentifier> allowedIdentifiers, boolean noMana) {
|
||||
setCurrentActivatingManaAbilitiesStep(ActivationManaAbilityStep.BEFORE); // mana payment step started, can use any mana abilities, see AlternateManaPaymentAbility
|
||||
|
||||
if (!ability.activate(game, noMana)) {
|
||||
if (!ability.activate(game, allowedIdentifiers, noMana)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -182,7 +182,7 @@ public class Spell extends StackObjectImpl implements Card {
|
|||
// see https://github.com/magefree/mage/issues/6603
|
||||
payNoMana |= ability.getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED;
|
||||
|
||||
if (!spellAbility.activate(game, payNoMana)) {
|
||||
if (!spellAbility.activate(game, allowedIdentifiers, payNoMana)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -388,8 +388,8 @@ public class StackAbility extends StackObjectImpl implements Ability {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean activate(Game game, boolean noMana) {
|
||||
return ability.activate(game, noMana);
|
||||
public boolean activate(Game game, Set<MageIdentifier> allowedIdentifiers, boolean noMana) {
|
||||
return ability.activate(game, allowedIdentifiers, noMana);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -584,7 +584,7 @@ public class StackAbility extends StackObjectImpl implements Ability {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, boolean noMana, Player controller, Game game) {
|
||||
public boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, Set<MageIdentifier> allowedIdentifiers, boolean noMana, Player controller, Game game) {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1231,6 +1231,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
// Use ability copy to avoid problems with targets and costs on recast (issue https://github.com/magefree/mage/issues/5189).
|
||||
SpellAbility ability = originalAbility.copy();
|
||||
Set<MageIdentifier> allowedIdentifiers = originalAbility.spellCanBeActivatedNow(getId(), game);
|
||||
ability.setControllerId(getId());
|
||||
ability.setSourceObjectZoneChangeCounter(game.getState().getZoneChangeCounter(ability.getSourceId()));
|
||||
|
||||
|
|
@ -1265,7 +1266,6 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
MageIdentifier identifier = approvingObject == null
|
||||
? MageIdentifier.Default
|
||||
: approvingObject.getApprovingAbility().getIdentifier();
|
||||
|
||||
if (!getCastSourceIdWithAlternateMana().getOrDefault(ability.getSourceId(), Collections.emptySet()).contains(identifier)) {
|
||||
// identifier has no alternate cast entry for that sourceId, using Default instead.
|
||||
identifier = MageIdentifier.Default;
|
||||
|
|
@ -1291,7 +1291,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
spell.getSpellAbility().getId(), spell.getSpellAbility(), playerId, approvingObject);
|
||||
castEvent.setZone(fromZone);
|
||||
game.fireEvent(castEvent);
|
||||
if (spell.activate(game, noMana)) {
|
||||
if (spell.activate(game, allowedIdentifiers, noMana)) {
|
||||
GameEvent castedEvent = GameEvent.getEvent(GameEvent.EventType.SPELL_CAST,
|
||||
ability.getId(), ability, playerId, approvingObject);
|
||||
castedEvent.setZone(fromZone);
|
||||
|
|
@ -1673,7 +1673,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
* @return
|
||||
*/
|
||||
public static Map<UUID, SpellAbility> getCastableSpellAbilities(Game game, UUID playerId, MageObject object, Zone zone, boolean noMana) {
|
||||
// it uses simple check from spellCanBeActivatedRegularlyNow
|
||||
// it uses simple check from spellCanBeActivatedNow
|
||||
// reason: no approved info here (e.g. forced to choose spell ability from cast card)
|
||||
LinkedHashMap<UUID, SpellAbility> useable = new LinkedHashMap<>();
|
||||
Abilities<Ability> allAbilities;
|
||||
|
|
@ -1695,7 +1695,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
// If the card has any mandatory additional costs, those must be paid to cast the spell.
|
||||
// (2021-02-05)
|
||||
if (!noMana) {
|
||||
if (spellAbility.spellCanBeActivatedRegularlyNow(playerId, game)) {
|
||||
Set<MageIdentifier> allowedToBeCastNow = spellAbility.spellCanBeActivatedNow(playerId, game);
|
||||
if (allowedToBeCastNow.contains(MageIdentifier.Default) || allowedToBeCastNow.contains(spellAbility.getIdentifier())) {
|
||||
useable.put(spellAbility.getId(), spellAbility); // example: Chandra, Torch of Defiance +1 loyal ability
|
||||
}
|
||||
return useable;
|
||||
|
|
@ -1735,10 +1736,12 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
}
|
||||
return useable;
|
||||
default:
|
||||
if (spellAbility.spellCanBeActivatedRegularlyNow(playerId, game)) {
|
||||
default: {
|
||||
Set<MageIdentifier> allowedToBeCastNow = spellAbility.spellCanBeActivatedNow(playerId, game);
|
||||
if (allowedToBeCastNow.contains(MageIdentifier.Default) || allowedToBeCastNow.contains(spellAbility.getIdentifier())) {
|
||||
useable.put(spellAbility.getId(), spellAbility);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return useable;
|
||||
|
|
@ -3622,19 +3625,29 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
game.getContinuousEffects().costModification(copy, game);
|
||||
}
|
||||
boolean canBeCastRegularly = true;
|
||||
if (copy instanceof SpellAbility && copy.getManaCosts().isEmpty() && copy.getCosts().isEmpty()) {
|
||||
// 117.6. Some mana costs contain no mana symbols. This represents an unpayable cost...
|
||||
// 117.6a (...) If an alternative cost is applied to an unpayable cost,
|
||||
// including an effect that allows a player to cast a spell without paying its mana cost, the alternative cost may be paid.
|
||||
canBeCastRegularly = false;
|
||||
Set<MageIdentifier> allowedIdentifiers = null;
|
||||
if (copy instanceof SpellAbility) {
|
||||
if (copy.getManaCosts().isEmpty() && copy.getCosts().isEmpty()) {
|
||||
// 117.6. Some mana costs contain no mana symbols. This represents an unpayable cost...
|
||||
// 117.6a (...) If an alternative cost is applied to an unpayable cost,
|
||||
// including an effect that allows a player to cast a spell without paying its mana cost, the alternative cost may be paid.
|
||||
canBeCastRegularly = false;
|
||||
}
|
||||
allowedIdentifiers = ((SpellAbility) copy).spellCanBeActivatedNow(playerId, game);
|
||||
if (!allowedIdentifiers.contains(MageIdentifier.Default)) {
|
||||
// If the timing restriction is lifted only for specific MageIdentifier, the default cast can not be used.
|
||||
canBeCastRegularly = false;
|
||||
}
|
||||
}
|
||||
if (canBeCastRegularly && canPayMinimumManaCost(copy, availableMana, game)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ALTERNATIVE COST FROM dynamic effects
|
||||
|
||||
for (MageIdentifier identifier : getCastSourceIdWithAlternateMana().getOrDefault(copy.getSourceId(), new HashSet<>())) {
|
||||
if (allowedIdentifiers != null && !(allowedIdentifiers.contains(MageIdentifier.Default) || allowedIdentifiers.contains(identifier))) {
|
||||
continue;
|
||||
}
|
||||
ManaCosts alternateCosts = getCastSourceIdManaCosts().get(copy.getSourceId()).get(identifier);
|
||||
Costs<Cost> costs = getCastSourceIdCosts().get(copy.getSourceId()).get(identifier);
|
||||
|
||||
|
|
@ -3767,123 +3780,146 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
*/
|
||||
protected boolean canPlayCardByAlternateCost(Card sourceObject, ManaOptions availableMana, Ability ability, Game game) {
|
||||
// TODO: Why is the "sourceObject instanceof Permanent" in there?
|
||||
if (sourceObject != null && !(sourceObject instanceof Permanent)) {
|
||||
Ability copyAbility; // for alternative cost and reduce tries
|
||||
for (Ability alternateSourceCostsAbility : sourceObject.getAbilities(game)) {
|
||||
// if cast for noMana no Alternative costs are allowed
|
||||
if (alternateSourceCostsAbility instanceof AlternativeSourceCosts) {
|
||||
if (((AlternativeSourceCosts) alternateSourceCostsAbility).isAvailable(ability, game)) {
|
||||
if (alternateSourceCostsAbility.getCosts().canPay(ability, ability, playerId, game)) {
|
||||
ManaCostsImpl manaCosts = new ManaCostsImpl<>();
|
||||
for (Cost cost : alternateSourceCostsAbility.getCosts()) {
|
||||
// AlternativeCost2 replaced by real cost on activate, so getPlayable need to extract that costs here
|
||||
if (cost instanceof AlternativeCost) {
|
||||
if (((AlternativeCost) cost).getCost() instanceof ManaCost) {
|
||||
manaCosts.add((ManaCost) ((AlternativeCost) cost).getCost());
|
||||
}
|
||||
} else {
|
||||
if (cost instanceof ManaCost) {
|
||||
manaCosts.add((ManaCost) cost);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (manaCosts.isEmpty()) {
|
||||
return true;
|
||||
} else {
|
||||
if (availableMana == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// alternative cost reduce
|
||||
copyAbility = ability.copy();
|
||||
copyAbility.clearManaCostsToPay();
|
||||
copyAbility.addManaCostsToPay(manaCosts.copy());
|
||||
copyAbility.adjustCosts(game);
|
||||
game.getContinuousEffects().costModification(copyAbility, game);
|
||||
|
||||
// reduced all cost
|
||||
if (copyAbility.getManaCostsToPay().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (Mana mana : copyAbility.getManaCostsToPay().getOptions()) {
|
||||
if (availableMana.enough(mana)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sourceObject == null || (sourceObject instanceof Permanent)) {
|
||||
return false;
|
||||
}
|
||||
Ability copyAbility; // for alternative cost and reduce tries
|
||||
Set<MageIdentifier> allowedIdentifiers = null;
|
||||
if (ability instanceof SpellAbility) {
|
||||
// This returns the set of MageIdentifier that allow to play the card at that timing.
|
||||
// If MageIdentifier.Default is in the set, everything is allowed to be cast at this time.
|
||||
// If not, then only the AlternativeSourceCosts with a matching MageIdentifier are allowed at this time.
|
||||
allowedIdentifiers = ((SpellAbility) ability).spellCanBeActivatedNow(getId(), game);
|
||||
}
|
||||
// Try the source specific AlternativeSourceCosts
|
||||
for (Ability alternateSourceCostsAbility : sourceObject.getAbilities(game)) {
|
||||
// if cast for noMana no Alternative costs are allowed
|
||||
if (!(alternateSourceCostsAbility instanceof AlternativeSourceCosts)) {
|
||||
continue;
|
||||
}
|
||||
if (!((AlternativeSourceCosts) alternateSourceCostsAbility).isAvailable(ability, game)) {
|
||||
continue;
|
||||
}
|
||||
if (!alternateSourceCostsAbility.getCosts().canPay(ability, ability, playerId, game)) {
|
||||
continue;
|
||||
}
|
||||
if (allowedIdentifiers != null && !allowedIdentifiers.contains(MageIdentifier.Default)
|
||||
&& !allowedIdentifiers.contains(alternateSourceCostsAbility.getIdentifier())) {
|
||||
continue;
|
||||
}
|
||||
ManaCostsImpl manaCosts = new ManaCostsImpl<>();
|
||||
for (Cost cost : alternateSourceCostsAbility.getCosts()) {
|
||||
// AlternativeCost2 replaced by real cost on activate, so getPlayable need to extract that costs here
|
||||
if (cost instanceof AlternativeCost) {
|
||||
if (((AlternativeCost) cost).getCost() instanceof ManaCost) {
|
||||
manaCosts.add((ManaCost) ((AlternativeCost) cost).getCost());
|
||||
}
|
||||
} else {
|
||||
if (cost instanceof ManaCost) {
|
||||
manaCosts.add((ManaCost) cost);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// controller specific alternate spell costs
|
||||
for (AlternativeSourceCosts alternateSourceCosts : getAlternativeSourceCosts()) {
|
||||
if (alternateSourceCosts instanceof Ability) {
|
||||
if (alternateSourceCosts.isAvailable(ability, game)) {
|
||||
if (((Ability) alternateSourceCosts).getCosts().canPay(ability, ability, playerId, game)) {
|
||||
ManaCostsImpl manaCosts = new ManaCostsImpl<>();
|
||||
for (Cost cost : ((Ability) alternateSourceCosts).getCosts()) {
|
||||
// AlternativeCost2 replaced by real cost on activate, so getPlayable need to extract that costs here
|
||||
if (cost instanceof AlternativeCost) {
|
||||
if (((AlternativeCost) cost).getCost() instanceof ManaCost) {
|
||||
manaCosts.add((ManaCost) ((AlternativeCost) cost).getCost());
|
||||
}
|
||||
} else {
|
||||
if (cost instanceof ManaCost) {
|
||||
manaCosts.add((ManaCost) cost);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (manaCosts.isEmpty()) {
|
||||
return true;
|
||||
} else {
|
||||
if (availableMana == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add a copy of the dynamic cost for this ability if there is one.
|
||||
// E.g. from Kentaro, the Smiling Cat
|
||||
if (alternateSourceCosts instanceof AlternativeCostSourceAbility) {
|
||||
DynamicCost dynamicCost = ((AlternativeCostSourceAbility) alternateSourceCosts).getDynamicCost();
|
||||
if (dynamicCost != null) {
|
||||
Cost cost = dynamicCost.getCost(ability, game);
|
||||
// TODO: I don't know if this first if-check is needed, I don't think any of the dynamics values are alternative costs.
|
||||
if (cost instanceof AlternativeCost) {
|
||||
cost = ((AlternativeCost) cost).getCost();
|
||||
}
|
||||
if (cost instanceof ManaCost) {
|
||||
manaCosts.add((ManaCost) cost);
|
||||
}
|
||||
}
|
||||
}
|
||||
// alternative cost reduce
|
||||
copyAbility = ability.copy();
|
||||
copyAbility.clearManaCostsToPay();
|
||||
copyAbility.addManaCostsToPay(manaCosts.copy());
|
||||
copyAbility.adjustCosts(game);
|
||||
game.getContinuousEffects().costModification(copyAbility, game);
|
||||
|
||||
if (manaCosts.isEmpty()) {
|
||||
return true;
|
||||
} else {
|
||||
// TODO: Why is it returning true if availableMana == null, one would think it should return false
|
||||
if (availableMana == null) {
|
||||
return true;
|
||||
}
|
||||
// reduced all cost
|
||||
if (copyAbility.getManaCostsToPay().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// alternative cost reduce
|
||||
copyAbility = ability.copy();
|
||||
copyAbility.clearManaCostsToPay();
|
||||
// TODO: IDE warning:
|
||||
// Unchecked assignment: 'mage.abilities.costs.mana.ManaCosts' to
|
||||
// 'java.util.Collection<? extends mage.abilities.costs.mana.ManaCost>'.
|
||||
// Reason: 'manaCosts' has raw type, so result of copy is erased
|
||||
copyAbility.addManaCostsToPay(manaCosts.copy());
|
||||
copyAbility.adjustCosts(game);
|
||||
game.getContinuousEffects().costModification(copyAbility, game);
|
||||
for (Mana mana : copyAbility.getManaCostsToPay().getOptions()) {
|
||||
if (availableMana.enough(mana)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reduced all cost
|
||||
if (copyAbility.getManaCostsToPay().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
// controller specific alternate spell costs
|
||||
for (AlternativeSourceCosts alternateSourceCosts : getAlternativeSourceCosts()) {
|
||||
if (!(alternateSourceCosts instanceof Ability)) {
|
||||
continue;
|
||||
}
|
||||
if (!alternateSourceCosts.isAvailable(ability, game)) {
|
||||
continue;
|
||||
}
|
||||
if (!((Ability) alternateSourceCosts).getCosts().canPay(ability, ability, playerId, game)) {
|
||||
continue;
|
||||
}
|
||||
if (allowedIdentifiers != null && !allowedIdentifiers.contains(MageIdentifier.Default)
|
||||
&& !allowedIdentifiers.contains(((Ability) alternateSourceCosts).getIdentifier())) {
|
||||
continue;
|
||||
}
|
||||
ManaCostsImpl manaCosts = new ManaCostsImpl<>();
|
||||
for (Cost cost : ((Ability) alternateSourceCosts).getCosts()) {
|
||||
// AlternativeCost2 replaced by real cost on activate, so getPlayable need to extract that costs here
|
||||
if (cost instanceof AlternativeCost) {
|
||||
if (((AlternativeCost) cost).getCost() instanceof ManaCost) {
|
||||
manaCosts.add((ManaCost) ((AlternativeCost) cost).getCost());
|
||||
}
|
||||
} else {
|
||||
if (cost instanceof ManaCost) {
|
||||
manaCosts.add((ManaCost) cost);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Mana mana : copyAbility.getManaCostsToPay().getOptions()) {
|
||||
if (availableMana.enough(mana)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add a copy of the dynamic cost for this ability if there is one.
|
||||
// E.g. from Kentaro, the Smiling Cat
|
||||
if (alternateSourceCosts instanceof AlternativeCostSourceAbility) {
|
||||
DynamicCost dynamicCost = ((AlternativeCostSourceAbility) alternateSourceCosts).getDynamicCost();
|
||||
if (dynamicCost != null) {
|
||||
Cost cost = dynamicCost.getCost(ability, game);
|
||||
// TODO: I don't know if this first if-check is needed, I don't think any of the dynamics values are alternative costs.
|
||||
if (cost instanceof AlternativeCost) {
|
||||
cost = ((AlternativeCost) cost).getCost();
|
||||
}
|
||||
if (cost instanceof ManaCost) {
|
||||
manaCosts.add((ManaCost) cost);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (manaCosts.isEmpty()) {
|
||||
return true;
|
||||
} else {
|
||||
// TODO: Why is it returning true if availableMana == null, one would think it should return false
|
||||
if (availableMana == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// alternative cost reduce
|
||||
copyAbility = ability.copy();
|
||||
copyAbility.clearManaCostsToPay();
|
||||
// TODO: IDE warning:
|
||||
// Unchecked assignment: 'mage.abilities.costs.mana.ManaCosts' to
|
||||
// 'java.util.Collection<? extends mage.abilities.costs.mana.ManaCost>'.
|
||||
// Reason: 'manaCosts' has raw type, so result of copy is erased
|
||||
copyAbility.addManaCostsToPay(manaCosts.copy());
|
||||
copyAbility.adjustCosts(game);
|
||||
game.getContinuousEffects().costModification(copyAbility, game);
|
||||
|
||||
// reduced all cost
|
||||
if (copyAbility.getManaCostsToPay().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (Mana mana : copyAbility.getManaCostsToPay().getOptions()) {
|
||||
if (availableMana.enough(mana)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue