[MH1] added Unbound Flourishing

This commit is contained in:
Oleg Agafonov 2019-06-06 16:52:06 +04:00
parent 3599d6343c
commit 12fc854777
16 changed files with 457 additions and 51 deletions

View file

@ -2,6 +2,7 @@ package mage.player.ai;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.AbilityImpl;
import mage.abilities.TriggeredAbility;
import mage.abilities.common.PassAbility;
import mage.abilities.costs.mana.ManaCost;
@ -142,8 +143,12 @@ public class SimulatedPlayer2 extends ComputerPlayer {
}
}
// add the specific value for x
int xMultiplier = 1;
if (newAbility instanceof AbilityImpl) {
xMultiplier = ((AbilityImpl) newAbility).handleManaXMultiplier(game, xMultiplier);
}
newAbility.getManaCostsToPay().add(new ManaCostsImpl(new StringBuilder("{").append(xAmount).append('}').toString()));
newAbility.getManaCostsToPay().setX(xAmount);
newAbility.getManaCostsToPay().setX(xAmount, xMultiplier);
if (varCost != null) {
varCost.setPaid();
}

View file

@ -1502,18 +1502,20 @@ public class ComputerPlayer extends PlayerImpl implements Player {
}
@Override
public int announceXMana(int min, int max, String message, Game game, Ability ability) {
public int announceXMana(int min, int max, int multilier, String message, Game game, Ability ability) {
log.debug("announceXMana");
//TODO: improve this
int xMin = min * multilier;
int xMax = (max == Integer.MAX_VALUE ? max : max * multilier);
int numAvailable = getAvailableManaProducers(game).size() - ability.getManaCosts().convertedManaCost();
if (numAvailable < 0) {
numAvailable = 0;
} else {
if (numAvailable < min) {
numAvailable = min;
if (numAvailable < xMin) {
numAvailable = xMin;
}
if (numAvailable > max) {
numAvailable = max;
if (numAvailable > xMax) {
numAvailable = xMax;
}
}
return numAvailable;

View file

@ -1128,21 +1128,17 @@ public class HumanPlayer extends PlayerImpl {
/**
* Gets the amount of mana the player want to spent for a x spell
*
* @param min
* @param max
* @param message
* @param game
* @param ability
* @return
* @param multilier - X multiplier after replace events
*/
@Override
public int announceXMana(int min, int max, String message, Game game, Ability ability) {
public int announceXMana(int min, int max, int multilier, String message, Game game, Ability ability) {
int xValue = 0;
String extraMessage = (multilier == 1 ? "" : ", X will be increased by " + multilier + " times");
updateGameStatePriority("announceXMana", game);
do {
prepareForResponse(game);
if (!isExecutingMacro()) {
game.fireGetAmountEvent(playerId, message, min, max);
game.fireGetAmountEvent(playerId, message + extraMessage, min, max);
}
waitForResponse(game);
} while (response.getInteger() == null

View file

@ -0,0 +1,189 @@
package mage.cards.u;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.mana.ActivatedManaAbilityImpl;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.Spell;
import mage.game.stack.StackAbility;
import mage.players.Player;
import java.util.UUID;
/**
* @author JayDi85
*/
public final class UnboundFlourishing extends CardImpl {
final static String needPrefix = "_UnboundFlourishing_NeedCopy";
public UnboundFlourishing(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}");
// Whenever you cast a permanent spell with a mana cost that contains {X}, double the value of X.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new UnboundFlourishingDoubleXEffect()));
// Whenever you cast an instant or sorcery spell or activate an ability,
// if that spells mana cost or that abilitys activation cost contains {X}, copy that spell or ability.
// You may choose new targets for the copy.
this.addAbility(new UnboundFlourishingCopyAbility());
}
public UnboundFlourishing(final UnboundFlourishing card) {
super(card);
}
@Override
public UnboundFlourishing copy() {
return new UnboundFlourishing(this);
}
}
class UnboundFlourishingDoubleXEffect extends ReplacementEffectImpl {
UnboundFlourishingDoubleXEffect() {
super(Duration.WhileOnBattlefield, Outcome.Benefit, false);
staticText = "Whenever you cast a permanent spell with a mana cost that contains {X}, double the value of X";
}
UnboundFlourishingDoubleXEffect(final UnboundFlourishingDoubleXEffect effect) {
super(effect);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
event.setAmount(event.getAmount() * 2);
return false;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.X_MANA_ANNOUNCE;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
Spell spell = game.getSpell(event.getTargetId());
return spell != null && spell.isPermanent() && spell.isControlledBy(source.getControllerId());
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public UnboundFlourishingDoubleXEffect copy() {
return new UnboundFlourishingDoubleXEffect(this);
}
}
class UnboundFlourishingCopyAbility extends TriggeredAbilityImpl {
UnboundFlourishingCopyAbility() {
super(Zone.BATTLEFIELD, new UnboundFlourishingCopyEffect(), false);
}
UnboundFlourishingCopyAbility(final UnboundFlourishingCopyAbility ability) {
super(ability);
}
@Override
public UnboundFlourishingCopyAbility copy() {
return new UnboundFlourishingCopyAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY
|| event.getType() == GameEvent.EventType.SPELL_CAST;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getPlayerId().equals(getControllerId())) {
// activated ability
if (event.getType() == GameEvent.EventType.ACTIVATED_ABILITY) {
StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getSourceId());
if (stackAbility != null && !(stackAbility.getStackAbility() instanceof ActivatedManaAbilityImpl)) {
if (stackAbility.getManaCostsToPay().containsX()) {
game.getState().setValue(this.getSourceId() + UnboundFlourishing.needPrefix, stackAbility);
return true;
}
}
}
// spell
if (event.getType() == GameEvent.EventType.SPELL_CAST) {
Spell spell = game.getStack().getSpell(event.getTargetId());
if (spell != null && spell.isInstantOrSorcery()) {
game.getState().setValue(this.getSourceId() + UnboundFlourishing.needPrefix, spell);
return true;
}
}
}
return false;
}
@Override
public String getRule() {
return "Whenever you cast an instant or sorcery spell or activate an ability, if that spells mana cost or that abilitys activation cost contains {X}" + super.getRule();
}
}
class UnboundFlourishingCopyEffect extends OneShotEffect {
UnboundFlourishingCopyEffect() {
super(Outcome.Benefit);
this.staticText = ", copy that spell or ability. You may choose new targets for the copy";
}
UnboundFlourishingCopyEffect(final UnboundFlourishingCopyEffect effect) {
super(effect);
}
@Override
public UnboundFlourishingCopyEffect copy() {
return new UnboundFlourishingCopyEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
Player controller = game.getPlayer(source.getControllerId());
Permanent sourcePermanent = game.getPermanent(source.getSourceId());
if (player != null && controller != null) {
Object needObject = game.getState().getValue(source.getSourceId() + UnboundFlourishing.needPrefix);
// copy ability
if (needObject instanceof StackAbility) {
StackAbility stackAbility = (StackAbility) needObject;
stackAbility.createCopyOnStack(game, source, source.getControllerId(), true);
game.informPlayers(sourcePermanent.getName() + ": " + controller.getLogName() + " copied activated ability");
return true;
}
// copy spell
if (needObject instanceof Spell) {
Spell spell = (Spell) needObject;
spell.createCopyOnStack(game, source, source.getControllerId(), true);
game.informPlayers(sourcePermanent.getName() + ": " + controller.getLogName() + " copied casted spell");
return true;
}
}
return false;
}
}

View file

@ -34,7 +34,10 @@ public final class WordOfCommand extends CardImpl {
public WordOfCommand(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{B}{B}");
// Look at target opponent's hand and choose a card from it. You control that player until Word of Command finishes resolving. The player plays that card if able. While doing so, the player can activate mana abilities only if they're from lands that player controls and only if mana they produce is spent to activate other mana abilities of lands the player controls and/or to play that card. If the chosen card is cast as a spell, you control the player while that spell is resolving.
// Look at target opponent's hand and choose a card from it. You control that player until Word of Command finishes resolving.
// The player plays that card if able. While doing so, the player can activate mana abilities only if they're from lands that player controls
// and only if mana they produce is spent to activate other mana abilities of lands the player controls and/or to play that card.
// If the chosen card is cast as a spell, you control the player while that spell is resolving.
this.getSpellAbility().addEffect(new WordOfCommandEffect());
this.getSpellAbility().addTarget(new TargetOpponent());
}

View file

@ -253,6 +253,7 @@ public final class ModernHorizons extends ExpansionSet {
cards.add(new SetCardInfo("Twin-Silk Spider", 188, Rarity.COMMON, mage.cards.t.TwinSilkSpider.class));
cards.add(new SetCardInfo("Twisted Reflection", 74, Rarity.UNCOMMON, mage.cards.t.TwistedReflection.class));
cards.add(new SetCardInfo("Umezawa's Charm", 111, Rarity.COMMON, mage.cards.u.UmezawasCharm.class));
cards.add(new SetCardInfo("Unbound Flourishing", 189, Rarity.MYTHIC, mage.cards.u.UnboundFlourishing.class));
cards.add(new SetCardInfo("Undead Augur", 112, Rarity.UNCOMMON, mage.cards.u.UndeadAugur.class));
cards.add(new SetCardInfo("Unearth", 113, Rarity.COMMON, mage.cards.u.Unearth.class));
cards.add(new SetCardInfo("Universal Automaton", 235, Rarity.COMMON, mage.cards.u.UniversalAutomaton.class));

View file

@ -0,0 +1,170 @@
package org.mage.test.cards.continuous;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.counters.CounterType;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author JayDi85
*/
public class UnboundFlourishingTest extends CardTestPlayerBase {
// Unbound Flourishing
// Whenever you cast a permanent spell with a mana cost that contains {X}, double the value of X.
// Whenever you cast an instant or sorcery spell or activate an ability, if that spells mana cost or that abilitys activation cost contains {X},
// copy that spell or ability. You may choose new targets for the copy.
@Test
public void test_OnCastPermanent_MustDoubleX() {
addCard(Zone.BATTLEFIELD, playerA, "Unbound Flourishing", 1);
//
// Endless One enters the battlefield with X +1/+1 counters on it.
addCard(Zone.HAND, playerA, "Endless One", 1); // {X}
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
// cast with X=3, but double it
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Endless One");
setChoice(playerA, "X=3");
checkPermanentCounters("after", 1, PhaseStep.BEGIN_COMBAT, playerA, "Endless One", CounterType.P1P1, 3 * 2);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
}
@Test
public void test_OnCastPermanent_MustDoubleX_MultipleTimes() {
addCard(Zone.BATTLEFIELD, playerA, "Unbound Flourishing", 2);
//
// Endless One enters the battlefield with X +1/+1 counters on it.
addCard(Zone.HAND, playerA, "Endless One", 1); // {X}
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
// cast with X=3, but double it
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Endless One");
setChoice(playerA, "X=3");
setChoice(playerA, "Unbound Flourishing"); // choose replacement effects
checkPermanentCounters("after", 1, PhaseStep.BEGIN_COMBAT, playerA, "Endless One", CounterType.P1P1, 3 * 2 * 2);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
}
@Test
public void test_OnCastInstantOrSourcery_MustCopy() {
addCard(Zone.BATTLEFIELD, playerA, "Unbound Flourishing", 1);
//
// Banefire deals X damage to any target.
addCard(Zone.HAND, playerA, "Banefire", 1); // {X}{R}
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
// cast with X=3 and make copy with another target, not double X
checkLife("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 20);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Banefire", playerA);
setChoice(playerA, "X=3");
setChoice(playerA, "Yes"); // change target
addTarget(playerA, playerB); // change to B
checkLife("after", 1, PhaseStep.BEGIN_COMBAT, playerA, 20 - 3); // original damage
checkLife("after", 1, PhaseStep.BEGIN_COMBAT, playerB, 20 - 3); // copy damage
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
}
@Test
public void test_OnCastPermanent_MustIgnoreAdditionCost() {
addCard(Zone.BATTLEFIELD, playerA, "Unbound Flourishing", 1);
//
// As an additional cost to cast this spell, pay X life.
// Each other player loses X life.
addCard(Zone.HAND, playerA, "Bond of Agony", 1); // {X}{B}
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4);
// cast with X=3, pay addition (normal X) and apply effect (double X)
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bond of Agony");
setChoice(playerA, "X=3");
checkLife("after", 1, PhaseStep.BEGIN_COMBAT, playerA, 20 - 3); // addition cost X
checkLife("after", 1, PhaseStep.BEGIN_COMBAT, playerB, 20 - 3 * 2); // damage double X
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
}
@Test
public void test_OnActivatedAbility_MustCopy1() {
addCard(Zone.BATTLEFIELD, playerA, "Unbound Flourishing", 1);
//
// {X}: Put X tower counters on Helix Pinnacle.
addCard(Zone.BATTLEFIELD, playerA, "Helix Pinnacle", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
// pux 3 counters two times
checkPermanentCounters("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Helix Pinnacle", CounterType.TOWER, 0);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{X}:");
setChoice(playerA, "X=3");
// it haven't target to change
checkPermanentCounters("after", 1, PhaseStep.BEGIN_COMBAT, playerA, "Helix Pinnacle", CounterType.TOWER, 3 + 3);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
}
@Test
public void test_OnActivatedAbility_MustCopy1_MultipleTimes() {
addCard(Zone.BATTLEFIELD, playerA, "Unbound Flourishing", 2);
//
// {X}: Put X tower counters on Helix Pinnacle.
addCard(Zone.BATTLEFIELD, playerA, "Helix Pinnacle", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
// pux 3 counters two times from two cards
checkPermanentCounters("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Helix Pinnacle", CounterType.TOWER, 0);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{X}:");
setChoice(playerA, "X=3");
setChoice(playerA, "Whenever you cast an instant or sorcery spell"); // choose triggered abilities from two instances
// it haven't target to change
checkPermanentCounters("after", 1, PhaseStep.BEGIN_COMBAT, playerA, "Helix Pinnacle", CounterType.TOWER, 3 + 3 * 2);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
}
@Test
public void test_OnActivatedAbility_MustCopy2() {
addCard(Zone.BATTLEFIELD, playerA, "Unbound Flourishing", 1);
//
// {X}{R}, {T}, Sacrifice Cinder Elemental: It deals X damage to any target.
addCard(Zone.BATTLEFIELD, playerA, "Cinder Elemental", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
// activate with X=3 and make copy with another target, not double X
checkLife("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 20);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{X}{R}", playerA);
setChoice(playerA, "X=3");
setChoice(playerA, "Yes"); // change target
addTarget(playerA, playerB); // change to B
checkLife("after", 1, PhaseStep.BEGIN_COMBAT, playerA, 20 - 3); // original damage
checkLife("after", 1, PhaseStep.BEGIN_COMBAT, playerB, 20 - 3); // copy damage
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
}
}

View file

@ -1994,7 +1994,7 @@ public class TestPlayer implements Player {
}
@Override
public int announceXMana(int min, int max, String message, Game game, Ability ability) {
public int announceXMana(int min, int max, int multilier, String message, Game game, Ability ability) {
if (!choices.isEmpty()) {
for (String choice : choices) {
if (choice.startsWith("X=")) {
@ -2006,7 +2006,7 @@ public class TestPlayer implements Player {
}
this.chooseStrictModeFailed(game, getInfo(ability) + "; " + message);
return computerPlayer.announceXMana(min, max, message, game, ability);
return computerPlayer.announceXMana(min, max, multilier, message, game, ability);
}
@Override

View file

@ -912,7 +912,7 @@ public class PlayerStub implements Player {
}
@Override
public int announceXMana(int min, int max, String message, Game game, Ability ability) {
public int announceXMana(int min, int max, int multilier, String message, Game game, Ability ability) {
return min;
}

View file

@ -254,6 +254,8 @@ public abstract class AbilityImpl implements Ability {
int xValue = this.getManaCostsToPay().getX();
this.getManaCostsToPay().clear();
VariableManaCost xCosts = new VariableManaCost();
// no x events - rules from Unbound Flourishing:
// - Spells with additional costs that include X won't be affected by Unbound Flourishing. X must be in the spell's mana cost.
xCosts.setAmount(xValue);
this.getManaCostsToPay().add(xCosts);
} else {
@ -288,11 +290,13 @@ public abstract class AbilityImpl implements Ability {
if (getAbilityType() == AbilityType.SPELL && (getManaCostsToPay().isEmpty() && getCosts().isEmpty()) && !noMana) {
return false;
}
// 20121001 - 601.2b
// If the spell has a variable cost that will be paid as it's being cast (such as an {X} in
// its mana cost; see rule 107.3), the player announces the value of that variable.
VariableManaCost variableManaCost = handleManaXCosts(game, noMana, controller);
String announceString = handleOtherXCosts(game, controller);
// For effects from cards like Void Winnower x costs have to be set
if (this.getAbilityType() == AbilityType.SPELL
&& game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.CAST_SPELL_LATE, getId(), getSourceId(), getControllerId()), this)) {
@ -499,7 +503,9 @@ public abstract class AbilityImpl implements Ability {
costs.add(fixedCost);
}
// set the xcosts to paid
variableCost.setAmount(xValue);
// no x events - rules from Unbound Flourishing:
// - Spells with additional costs that include X won't be affected by Unbound Flourishing. X must be in the spell's mana cost.
variableCost.setAmount(xValue); //
((Cost) variableCost).setPaid();
String message = controller.getLogName() + " announces a value of " + xValue + " (" + variableCost.getActionText() + ')';
announceString.append(message);
@ -530,6 +536,13 @@ public abstract class AbilityImpl implements Ability {
}
}
public int handleManaXMultiplier(Game game, int value) {
// some spells can change X value without new pays (Unbound Flourishing doubles X)
GameEvent xEvent = GameEvent.getEvent(GameEvent.EventType.X_MANA_ANNOUNCE, getId(), getSourceId(), getControllerId(), value);
game.replaceEvent(xEvent, this);
return xEvent.getAmount();
}
/**
* Handles X mana costs and sets manaCostsToPay.
*
@ -546,15 +559,21 @@ public abstract class AbilityImpl implements Ability {
VariableManaCost variableManaCost = null;
for (ManaCost cost : manaCostsToPay) {
if (cost instanceof VariableManaCost) {
variableManaCost = (VariableManaCost) cost;
break; // only one VariableManCost per spell (or is it possible to have more?)
if (variableManaCost == null) {
variableManaCost = (VariableManaCost) cost;
} else {
// only one VariableManCost per spell (or is it possible to have more?)
logger.error("Variable mana cost allowes only in one instance per ability: " + this);
}
}
}
if (variableManaCost != null) {
int xValue;
if (!variableManaCost.isPaid()) { // should only happen for human players
int xValue;
int xValueMultiplier = handleManaXMultiplier(game, 1);
if (!noMana) {
xValue = controller.announceXMana(variableManaCost.getMinX(), variableManaCost.getMaxX(), "Announce the value for " + variableManaCost.getText(), game, this);
xValue = controller.announceXMana(variableManaCost.getMinX(), variableManaCost.getMaxX(), xValueMultiplier,
"Announce the value for " + variableManaCost.getText(), game, this);
int amountMana = xValue * variableManaCost.getMultiplier();
StringBuilder manaString = threadLocalBuilder.get();
if (variableManaCost.getFilter() == null || variableManaCost.getFilter().isGeneric()) {
@ -584,7 +603,7 @@ public abstract class AbilityImpl implements Ability {
}
}
manaCostsToPay.add(new ManaCostsImpl(manaString.toString()));
manaCostsToPay.setX(amountMana);
manaCostsToPay.setX(xValue, xValueMultiplier);
}
variableManaCost.setPaid();
}

View file

@ -1,8 +1,5 @@
package mage.abilities.costs.common;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
@ -16,8 +13,11 @@ import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetCardInHand;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public class ExileFromHandCost extends CostImpl {
@ -30,10 +30,9 @@ public class ExileFromHandCost extends CostImpl {
}
/**
*
* @param target
* @param setXFromCMC the spells X value on the stack is set to the
* converted mana costs of the exiled card
* converted mana costs of the exiled card
*/
public ExileFromHandCost(TargetCardInHand target, boolean setXFromCMC) {
this.addTarget(target);
@ -68,6 +67,8 @@ public class ExileFromHandCost extends CostImpl {
paid = true;
if (setXFromCMC) {
VariableManaCost vmc = new VariableManaCost();
// no x events - rules from Unbound Flourishing:
// - Spells with additional costs that include X won't be affected by Unbound Flourishing. X must be in the spell's mana cost.
vmc.setAmount(cmc);
vmc.setPaid();
ability.getManaCostsToPay().add(vmc);

View file

@ -1,4 +1,3 @@
package mage.abilities.costs.mana;
import mage.Mana;
@ -11,9 +10,8 @@ import java.util.UUID;
import java.util.stream.Collectors;
/**
*
* @author BetaSteward_at_googlemail.com
* @param <T>
* @author BetaSteward_at_googlemail.com
*/
public interface ManaCosts<T extends ManaCost> extends List<T>, ManaCost {
@ -21,9 +19,15 @@ public interface ManaCosts<T extends ManaCost> extends List<T>, ManaCost {
List<VariableCost> getVariableCosts();
boolean containsX();
int getX();
void setX(int x);
/**
* @param xValue announced X value
* @param xMultiplier special X multiplier to change announced X value without pay increase, see Unbound Flourishing
*/
void setX(int xValue, int xMultiplier);
void load(String mana);
@ -41,7 +45,7 @@ public interface ManaCosts<T extends ManaCost> extends List<T>, ManaCost {
static ManaCosts<ManaCost> removeVariableManaCost(ManaCosts<ManaCost> m) {
return m.stream()
.filter(mc -> !(mc instanceof VariableManaCost))
.collect(Collectors.toCollection(ManaCostsImpl<ManaCost>::new));
.collect(Collectors.toCollection(ManaCostsImpl::new));
}

View file

@ -1,7 +1,5 @@
package mage.abilities.costs.mana;
import java.util.*;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
@ -20,9 +18,11 @@ import mage.players.Player;
import mage.target.Targets;
import mage.util.ManaUtil;
import java.util.*;
/**
* @author BetaSteward_at_googlemail.com
* @param <T>
* @author BetaSteward_at_googlemail.com
*/
public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements ManaCosts<T> {
@ -213,6 +213,11 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
return variableCosts;
}
@Override
public boolean containsX() {
return !getVariableCosts().isEmpty();
}
@Override
public int getX() {
int amount = 0;
@ -224,10 +229,10 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
}
@Override
public void setX(int x) {
public void setX(int xValue, int xMultiplier) {
List<VariableCost> variableCosts = getVariableCosts();
if (!variableCosts.isEmpty()) {
variableCosts.get(0).setAmount(x);
variableCosts.get(0).setAmount(xValue * xMultiplier);
}
}
@ -339,7 +344,7 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
if (player != null) {
game.undo(playerId);
this.clearPaid();
this.setX(referenceCosts.getX());
this.setX(referenceCosts.getX(), 1); // TODO: checks Word of Command with Unbound Flourishing's X multiplier
player.getManaPool().restoreMana(pool.getPoolBookmark());
game.bookmarkState();
}
@ -378,15 +383,15 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
} else if (!symbol.equals("X")) {
this.add(new ColoredManaCost(ColoredManaSymbol.lookup(symbol.charAt(0))));
} else // check X wasn't added before
if (modifierForX == 0) {
// count X occurence
for (String s : symbols) {
if (s.equals("X")) {
modifierForX++;
if (modifierForX == 0) {
// count X occurence
for (String s : symbols) {
if (s.equals("X")) {
modifierForX++;
}
}
}
this.add(new VariableManaCost(modifierForX));
} //TODO: handle multiple {X} and/or {Y} symbols
this.add(new VariableManaCost(modifierForX));
} //TODO: handle multiple {X} and/or {Y} symbols
} else if (Character.isDigit(symbol.charAt(0))) {
this.add(new MonoHybridManaCost(ColoredManaSymbol.lookup(symbol.charAt(2))));
} else if (symbol.contains("P")) {

View file

@ -124,6 +124,13 @@ public class GameEvent implements Serializable {
sourceId sourceId of the vehicle
playerId the id of the controlling player
*/
X_MANA_ANNOUNCE,
/* X_MANA_ANNOUNCE
mana x-costs announced by players (X value can be changed by replace events like Unbound Flourishing)
targetId id of the spell that's cast
playerId player that casts the spell
amount X multiplier to change X value, default 1
*/
CAST_SPELL,
/* SPELL_CAST
x-Costs are already defined

View file

@ -564,7 +564,11 @@ public interface Player extends MageItem, Copyable<Player> {
boolean putCardsOnTopOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder);
// set the value for X mana spells and abilities
int announceXMana(int min, int max, String message, Game game, Ability ability);
default int announceXMana(int min, int max, String message, Game game, Ability ability) {
return announceXMana(min, max, 1, message, game, ability);
}
int announceXMana(int min, int max, int multilier, String message, Game game, Ability ability);
// set the value for non mana X costs
int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variableCost);

View file

@ -149,7 +149,7 @@ public class StubPlayer extends PlayerImpl implements Player {
}
@Override
public int announceXMana(int min, int max, String message, Game game, Ability ability) {
public int announceXMana(int min, int max, int multilier, String message, Game game, Ability ability) {
return 0;
}