Verrak, Warped Sengir - improved combo support with phyrexian style effects like K'rrik, Son of Yawgmoth (closes #10119)

This commit is contained in:
Oleg Agafonov 2024-10-27 14:52:29 +04:00
parent 8a8773971d
commit c0e027f4f4
8 changed files with 271 additions and 87 deletions

View file

@ -2,11 +2,11 @@ package mage.abilities;
import mage.MageIdentifier;
import mage.MageObject;
import mage.Mana;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.condition.Condition;
import mage.abilities.costs.*;
import mage.abilities.costs.common.PayLifeCost;
import mage.abilities.costs.common.SacrificeTargetCost;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.costs.mana.ManaCostsImpl;
@ -24,6 +24,7 @@ import mage.choices.Choice;
import mage.choices.ChoiceHintType;
import mage.choices.ChoiceImpl;
import mage.constants.*;
import mage.filter.FilterMana;
import mage.game.Game;
import mage.game.command.Dungeon;
import mage.game.command.Emblem;
@ -329,7 +330,10 @@ public abstract class AbilityImpl implements Ability {
VariableManaCost variableManaCost = handleManaXCosts(game, noMana, controller);
String announceString = handleOtherXCosts(game, controller);
handlePhyrexianManaCosts(game, controller);
// 601.2b If a cost that will be paid as the spell is being cast includes
// Phyrexian mana symbols, the player announces whether they intend to pay 2
// life or the corresponding colored mana cost for each of those symbols.
AbilityImpl.handlePhyrexianCosts(game, this, this, this.getManaCostsToPay());
// 20241022 - 601.2b
// Not yet included in 601.2b but this is where it will be
@ -632,24 +636,75 @@ public abstract class AbilityImpl implements Ability {
}
/**
* 601.2b If a cost that will be paid as the spell is being cast includes
* Phyrexian mana symbols, the player announces whether they intend to pay 2
* life or the corresponding colored mana cost for each of those symbols.
* Prepare Phyrexian costs (choose life to pay instead mana)
* Must be called on cast announce before any cost modifications
*
* @param abilityToPay paying ability (will receive life cost)
* @param manaCostsToPay paying cost (will remove P and replace it by mana or nothing)
*/
private void handlePhyrexianManaCosts(Game game, Player controller) {
Iterator<ManaCost> costIterator = getManaCostsToPay().iterator();
public static void handlePhyrexianCosts(Game game, Ability source, Ability abilityToPay, ManaCosts manaCostsToPay) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return;
}
Iterator<ManaCost> costIterator = manaCostsToPay.iterator();
while (costIterator.hasNext()) {
ManaCost cost = costIterator.next();
if (!cost.isPhyrexian()) {
continue;
}
PayLifeCost payLifeCost = new PayLifeCost(2);
if (payLifeCost.canPay(this, this, controller.getId(), game)
&& controller.chooseUse(Outcome.LoseLife, "Pay 2 life instead of " + cost.getText().replace("/P", "") + '?', this, game)) {
if (payLifeCost.canPay(abilityToPay, source, controller.getId(), game)
&& controller.chooseUse(Outcome.LoseLife, "Pay 2 life instead of " + cost.getText().replace("/P", "")
+ " (phyrexian cost)?", source, game)) {
costIterator.remove();
addCost(payLifeCost);
getManaCostsToPay().incrPhyrexianPaid();
abilityToPay.addCost(payLifeCost);
manaCostsToPay.incrPhyrexianPaid(); // mark it as real phyrexian pay, e.g. for planeswalkers with Compleated ability
}
}
}
/**
* Prepare and pay Phyrexian style effects like replace mana by life
* Must be called after original Phyrexian mana processing and after cost modifications, e.g. on payment
*
* @param abilityToPay paying ability (will receive life cost)
* @param manaCostsToPay paying cost (will replace mana by nothing)
*/
public static void handlePhyrexianLikeEffects(Game game, Ability source, Ability abilityToPay, ManaCosts manaCostsToPay) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return;
}
// If a cost contains a mana symbol that may be paid in multiple ways, such as {B/R}, {B/P}, or {2/B},
// you choose how you'll pay it before you do so. If you choose to pay {B} this way, K'rrik's ability allows
// you to pay life rather than pay that mana.
// (2019-08-23)
FilterMana phyrexianColors = controller.getPhyrexianColors();
if (controller.getPhyrexianColors() == null) {
return;
}
Iterator<ManaCost> costIterator = manaCostsToPay.iterator();
while (costIterator.hasNext()) {
ManaCost cost = costIterator.next();
Mana mana = cost.getMana();
if ((!phyrexianColors.isWhite() || mana.getWhite() <= 0)
&& (!phyrexianColors.isBlue() || mana.getBlue() <= 0)
&& (!phyrexianColors.isBlack() || mana.getBlack() <= 0)
&& (!phyrexianColors.isRed() || mana.getRed() <= 0)
&& (!phyrexianColors.isGreen() || mana.getGreen() <= 0)) {
continue;
}
PayLifeCost payLifeCost = new PayLifeCost(2);
if (payLifeCost.canPay(abilityToPay, source, controller.getId(), game)
&& controller.chooseUse(Outcome.LoseLife, "Pay 2 life instead of " + cost.getText().replace("/P", "")
+ " (pay life cost)?", source, game)) {
if (payLifeCost.pay(abilityToPay, game, source, controller.getId(), false, null)) {
costIterator.remove();
abilityToPay.addCost(payLifeCost);
}
}
}
}

View file

@ -2,6 +2,7 @@ package mage.abilities.costs.mana;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.AbilityImpl;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.abilities.mana.ManaOptions;
@ -254,6 +255,13 @@ public abstract class ManaCostImpl extends CostImpl implements ManaCost {
return true;
}
Player player = game.getPlayer(controllerId);
if (player == null) {
return false;
}
// TODO: is it require Phyrexian stile effects here for single payment?
//AbilityImpl.preparePhyrexianCost(game, source, player, ability, this);
if (!player.getManaPool().isForcedToPay()) {
assignPayment(game, ability, player.getManaPool(), costToPay != null ? costToPay : this);
}

View file

@ -2,6 +2,7 @@ package mage.abilities.costs.mana;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.AbilityImpl;
import mage.abilities.costs.*;
import mage.abilities.costs.common.PayLifeCost;
import mage.abilities.mana.ManaOptions;
@ -127,17 +128,17 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
return false;
}
handleLikePhyrexianManaCosts(player, ability, game); // e.g. K'rrik, Son of Yawgmoth
AbilityImpl.handlePhyrexianLikeEffects(game, source, ability, this);
if (!player.getManaPool().isForcedToPay()) {
assignPayment(game, ability, player.getManaPool(), this);
assignPayment(game, ability, player.getManaPool(), costToPay != null ? costToPay : this);
}
game.getState().getSpecialActions().removeManaActions();
while (player.canRespond() && !isPaid()) {
ManaCost unpaid = this.getUnpaid();
String promptText = ManaUtil.addSpecialManaPayAbilities(ability, game, unpaid);
if (player.playMana(ability, unpaid, promptText, game)) {
assignPayment(game, ability, player.getManaPool(), this);
assignPayment(game, ability, player.getManaPool(), costToPay != null ? costToPay : this);
} else {
return false;
}
@ -195,40 +196,6 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
tempCosts.pay(source, game, source, payingPlayer.getId(), false, null);
}
private void handleLikePhyrexianManaCosts(Player player, Ability source, Game game) {
if (this.isEmpty()) {
return; // nothing to be done without any mana costs. prevents NRE from occurring here
}
FilterMana phyrexianColors = player.getPhyrexianColors();
if (player.getPhyrexianColors() == null) {
return;
}
Costs<PayLifeCost> tempCosts = new CostsImpl<>();
Iterator<T> manaCostIterator = this.iterator();
while (manaCostIterator.hasNext()) {
ManaCost manaCost = manaCostIterator.next();
Mana mana = manaCost.getMana();
/* find which color mana is in the cost and set it in the temp Phyrexian cost */
if ((!phyrexianColors.isWhite() || mana.getWhite() <= 0)
&& (!phyrexianColors.isBlue() || mana.getBlue() <= 0)
&& (!phyrexianColors.isBlack() || mana.getBlack() <= 0)
&& (!phyrexianColors.isRed() || mana.getRed() <= 0)
&& (!phyrexianColors.isGreen() || mana.getGreen() <= 0)) {
continue;
}
PayLifeCost payLifeCost = new PayLifeCost(2);
if (payLifeCost.canPay(source, source, player.getId(), game)
&& player.chooseUse(Outcome.LoseLife, "Pay 2 life (using an active ability) instead of " + manaCost.getMana() + '?', source, game)) {
manaCostIterator.remove();
tempCosts.add(payLifeCost);
}
}
tempCosts.pay(source, game, source, player.getId(), false, null);
}
@Override
public ManaCosts<T> getUnpaid() {
ManaCosts<T> unpaid = new ManaCostsImpl<>();

View file

@ -1229,8 +1229,6 @@ public interface Player extends MageItem, Copyable<Player> {
/**
* Mana colors the player can pay instead with 2 life
*
* @return
*/
FilterMana getPhyrexianColors();