Unbound Flourishing's X doubling should be a triggered ability (and related refactors) (#12597)

Complete rework of Unbound Flourishing, removing the multiplier code for casting X spells.
Adds ActivateAbilityTriggeredAbility, NotManaAbilityPredicate, AbilitySourceAttachedPredicate
CopyStackObjectEffect now uses a MOR.
OrTriggeredAbility now works with target pointer setting abilities.
This commit is contained in:
ssk97 2024-08-22 13:33:39 -07:00 committed by GitHub
parent 9d83381326
commit b70638acc9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 399 additions and 547 deletions

View file

@ -2,7 +2,6 @@ package mage.player.ai;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.AbilityImpl;
import mage.abilities.ActivatedAbility;
import mage.abilities.TriggeredAbility;
import mage.abilities.common.PassAbility;
@ -154,12 +153,8 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
}
}
// find real X value after replace events
int xMultiplier = 1;
if (newAbility instanceof AbilityImpl) {
xMultiplier = ((AbilityImpl) newAbility).handleManaXMultiplier(game, xMultiplier);
}
newAbility.addManaCostsToPay(new ManaCostsImpl<>(new StringBuilder("{").append(xAnnounceValue).append('}').toString()));
newAbility.getManaCostsToPay().setX(xAnnounceValue * xMultiplier, xAnnounceValue * xInstancesCount);
newAbility.getManaCostsToPay().setX(xAnnounceValue, xAnnounceValue * xInstancesCount);
if (varCost != null) {
varCost.setPaid();
}

View file

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

View file

@ -1668,24 +1668,22 @@ public class HumanPlayer extends PlayerImpl {
*
* @param min
* @param max
* @param multiplier - X multiplier after replace events
* @param message
* @param ability
* @param game
* @return
*/
@Override
public int announceXMana(int min, int max, int multiplier, String message, Game game, Ability ability) {
public int announceXMana(int min, int max, String message, Game game, Ability ability) {
if (gameInCheckPlayableState(game)) {
return 0;
}
int xValue = 0;
String extraMessage = (multiplier == 1 ? "" : ", X will be increased by " + multiplier + " times");
while (canRespond()) {
prepareForResponse(game);
if (!isExecutingMacro()) {
game.fireGetAmountEvent(playerId, message + extraMessage + CardUtil.getSourceLogName(game, ability), min, max);
game.fireGetAmountEvent(playerId, message + CardUtil.getSourceLogName(game, ability), min, max);
}
waitForResponse(game);

View file

@ -17,6 +17,7 @@ import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.StackObject;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
@ -92,8 +93,7 @@ class AbolethSpawnTriggeredAbility extends TriggeredAbilityImpl {
if (triggerEvent.getSourceId() != permanent.getId()) {
return false; // only triggered abilities of that creature
}
// CopyStackObjectEffect needs value set
getEffects().setValue("stackObject", stackObject);
getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game));
return true;
}

View file

@ -15,6 +15,7 @@ import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.StackAbility;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
@ -53,6 +54,8 @@ class AshnodTheUncaringTriggeredAbility extends TriggeredAbilityImpl {
AshnodTheUncaringTriggeredAbility() {
super(Zone.BATTLEFIELD, new CopyStackObjectEffect(), true);
setTriggerPhrase("Whenever you activate an ability of an artifact or creature that isn't a mana ability, " +
"if one or more permanents were sacrificed to activate it, ");
}
private AshnodTheUncaringTriggeredAbility(final AshnodTheUncaringTriggeredAbility ability) {
@ -88,14 +91,7 @@ class AshnodTheUncaringTriggeredAbility extends TriggeredAbilityImpl {
if (permanent == null || (!permanent.isArtifact(game) && !permanent.isCreature(game))) {
return false;
}
this.getEffects().setValue("stackObject", stackAbility);
getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game));
return true;
}
@Override
public String getRule() {
return "Whenever you activate an ability of an artifact or creature that isn't a mana ability, " +
"if one or more permanents were sacrificed to activate it, " +
"you may copy that ability. You may choose new targets for the copy.";
}
}

View file

@ -1,6 +1,6 @@
package mage.cards.b;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.ActivateAbilityTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.common.CopyStackObjectEffect;
@ -11,10 +11,10 @@ import mage.abilities.keyword.HasteAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.StackAbility;
import mage.filter.FilterStackObject;
import mage.filter.common.FilterActivatedOrTriggeredAbility;
import mage.filter.predicate.other.AbilitySourceAttachedPredicate;
import mage.filter.predicate.other.NotManaAbilityPredicate;
import mage.target.common.TargetControlledCreaturePermanent;
import java.util.UUID;
@ -24,6 +24,13 @@ import java.util.UUID;
*/
public final class BattlemagesBracers extends CardImpl {
private static final FilterStackObject filter = new FilterActivatedOrTriggeredAbility("an ability of equipped creature");
static {
filter.add(NotManaAbilityPredicate.instance);
filter.add(AbilitySourceAttachedPredicate.instance);
}
public BattlemagesBracers(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{R}");
@ -35,7 +42,8 @@ public final class BattlemagesBracers extends CardImpl {
)));
// Whenever an ability of equipped creature is activated, if it isn't a mana ability, you may pay {1}. If you do, copy that ability. You may choose new targets for the copy.
this.addAbility(new BattlemagesBracersTriggeredAbility());
this.addAbility(new ActivateAbilityTriggeredAbility(new DoIfCostPaid(new CopyStackObjectEffect(), new GenericManaCost(1)), filter, SetTargetPointer.SPELL)
.setTriggerPhrase("Whenever an ability of equipped creature is activated, if it isn't a mana ability, "));
// Equip {2}
this.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(2), new TargetControlledCreaturePermanent(), false));
@ -50,44 +58,3 @@ public final class BattlemagesBracers extends CardImpl {
return new BattlemagesBracers(this);
}
}
class BattlemagesBracersTriggeredAbility extends TriggeredAbilityImpl {
BattlemagesBracersTriggeredAbility() {
super(Zone.BATTLEFIELD, new DoIfCostPaid(new CopyStackObjectEffect(), new GenericManaCost(1)));
}
private BattlemagesBracersTriggeredAbility(final BattlemagesBracersTriggeredAbility ability) {
super(ability);
}
@Override
public BattlemagesBracersTriggeredAbility copy() {
return new BattlemagesBracersTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent equipment = game.getPermanent(this.getSourceId());
if (equipment == null || !equipment.isAttachedTo(event.getSourceId())) {
return false;
}
StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getSourceId());
if (stackAbility == null || stackAbility.getStackAbility().isManaActivatedAbility()) {
return false;
}
getEffects().setValue("stackObject", stackAbility);
return true;
}
@Override
public String getRule() {
return "Whenever an ability of equipped creature is activated, if it isn't a mana ability, you may pay {1}. " +
"If you do, copy that ability. You may choose new targets for the copy.";
}
}

View file

@ -22,6 +22,7 @@ import mage.game.Game;
import mage.game.stack.Spell;
import mage.players.Player;
import mage.target.common.TargetAnyTarget;
import mage.target.targetpointer.FixedTarget;
import java.util.Optional;
import java.util.UUID;
@ -87,7 +88,7 @@ class BreechesTheBlastmakerEffect extends OneShotEffect {
if (player.flipCoin(source, game, true)) {
Effect effect = new CopyStackObjectEffect();
effect.setText("copy that spell. You may choose new targets for the copy");
effect.setValue("stackObject", spell);
effect.setTargetPointer(new FixedTarget(spell.getId(), game));
ability = new ReflexiveTriggeredAbility(effect, false);
} else {
int mv = Optional

View file

@ -8,18 +8,18 @@ import mage.abilities.costs.common.DiscardTargetCost;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CopyStackObjectEffect;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.constants.CardType;
import mage.constants.SetTargetPointer;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.FilterCard;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.ColorPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.stack.StackAbility;
import mage.players.Player;
import mage.target.common.TargetCardInHand;
import java.util.UUID;
@ -44,7 +44,9 @@ public final class ChandrasRegulator extends CardImpl {
this.supertype.add(SuperType.LEGENDARY);
// Whenever you activate a loyalty ability of a Chandra planeswalker, you may pay {1}. If you do, copy that ability. You may choose new targets for the copy.
this.addAbility(new ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility(new ChandrasRegulatorEffect(), SubType.CHANDRA));
this.addAbility(new ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility(
new DoIfCostPaid(new CopyStackObjectEffect(), new ManaCostsImpl<>("{1}")),
SubType.CHANDRA, SetTargetPointer.SPELL));
// {1}, {T}, Discard a Mountain card or a red card: Draw a card.
Ability ability = new SimpleActivatedAbility(
@ -64,45 +66,3 @@ public final class ChandrasRegulator extends CardImpl {
return new ChandrasRegulator(this);
}
}
class ChandrasRegulatorEffect extends OneShotEffect {
ChandrasRegulatorEffect() {
super(Outcome.Benefit);
staticText = "you may pay {1}. If you do, copy that ability. You may choose new targets for the copy";
}
private ChandrasRegulatorEffect(final ChandrasRegulatorEffect effect) {
super(effect);
}
@Override
public ChandrasRegulatorEffect copy() {
return new ChandrasRegulatorEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
ManaCostsImpl cost = new ManaCostsImpl<>("{1}");
if (player == null) {
return false;
}
if (!cost.canPay(source, source, player.getId(), game)
|| !player.chooseUse(Outcome.Benefit, "Pay " + cost.getText() +
"? If you do, copy that ability. You may choose new targets for the copy.", source, game)) {
return true;
}
if (!cost.pay(source, game, source, source.getControllerId(), false, null)) {
return true;
}
StackAbility ability = (StackAbility) getValue("stackObject");
Player controller = game.getPlayer(source.getControllerId());
Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId());
if (ability == null || controller == null || sourcePermanent == null) {
return false;
}
ability.createCopyOnStack(game, source, source.getControllerId(), true);
return true;
}
}

View file

@ -17,6 +17,7 @@ import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.StackAbility;
import mage.target.targetpointer.FixedTarget;
import mage.watchers.common.ManaPaidSourceWatcher;
import java.util.UUID;
@ -91,6 +92,7 @@ class DynaheirInvokerAdeptTriggeredAbility extends DelayedTriggeredAbility {
DynaheirInvokerAdeptTriggeredAbility() {
super(new CopyStackObjectEffect(), Duration.EndOfTurn, true);
setTriggerPhrase("When you next activate an ability that isn't a mana ability this turn by spending four or more mana to activate it, ");
}
private DynaheirInvokerAdeptTriggeredAbility(final DynaheirInvokerAdeptTriggeredAbility ability) {
@ -118,13 +120,7 @@ class DynaheirInvokerAdeptTriggeredAbility extends DelayedTriggeredAbility {
|| ManaPaidSourceWatcher.getTotalPaid(stackAbility.getId(), game) < 4) {
return false;
}
this.getEffects().setValue("stackObject", stackAbility);
getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game));
return true;
}
@Override
public String getRule() {
return "When you next activate an ability that isn't a mana ability this turn by spending four or more mana to activate it, " +
"copy that ability. You may choose new targets for the copy.";
}
}

View file

@ -14,15 +14,12 @@ import mage.constants.Zone;
import mage.filter.FilterPlayer;
import mage.filter.FilterStackObject;
import mage.filter.StaticFilters;
import mage.filter.predicate.ObjectSourcePlayer;
import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.filter.predicate.mageobject.TargetsPermanentOrPlayerPredicate;
import mage.filter.predicate.mageobject.TargetsPermanentPredicate;
import mage.filter.predicate.mageobject.TargetsPlayerPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.token.MercenaryToken;
import mage.game.stack.StackObject;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
@ -94,7 +91,7 @@ class ErthaJoFrontierMentorTriggeredAbility extends TriggeredAbilityImpl {
return false;
}
// For the copy effect to find.
this.getEffects().setValue("stackObject", stackObject);
getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game));
return true;
}
}

View file

@ -1,6 +1,6 @@
package mage.cards.i;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.ActivateAbilityTriggeredAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.common.CopyStackObjectEffect;
import mage.abilities.keyword.EquipAbility;
@ -8,12 +8,12 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SetTargetPointer;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.StackAbility;
import mage.filter.FilterStackObject;
import mage.filter.common.FilterActivatedOrTriggeredAbility;
import mage.filter.predicate.other.AbilitySourceAttachedPredicate;
import mage.filter.predicate.other.NotManaAbilityPredicate;
import java.util.UUID;
@ -22,12 +22,19 @@ import java.util.UUID;
*/
public final class IllusionistsBracers extends CardImpl {
private static final FilterStackObject filter = new FilterActivatedOrTriggeredAbility("an ability of equipped creature");
static {
filter.add(NotManaAbilityPredicate.instance);
filter.add(AbilitySourceAttachedPredicate.instance);
}
public IllusionistsBracers(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}");
this.subtype.add(SubType.EQUIPMENT);
// Whenever an ability of equipped creature is activated, if it isn't a mana ability, copy that ability. You may choose new targets for the copy.
this.addAbility(new IllusionistsBracersTriggeredAbility());
this.addAbility(new ActivateAbilityTriggeredAbility(new CopyStackObjectEffect(), filter, SetTargetPointer.SPELL)
.setTriggerPhrase("Whenever an ability of equipped creature is activated, if it isn't a mana ability, "));
// Equip 3
this.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(3), false));
@ -42,44 +49,3 @@ public final class IllusionistsBracers extends CardImpl {
return new IllusionistsBracers(this);
}
}
class IllusionistsBracersTriggeredAbility extends TriggeredAbilityImpl {
IllusionistsBracersTriggeredAbility() {
super(Zone.BATTLEFIELD, new CopyStackObjectEffect());
}
private IllusionistsBracersTriggeredAbility(final IllusionistsBracersTriggeredAbility ability) {
super(ability);
}
@Override
public IllusionistsBracersTriggeredAbility copy() {
return new IllusionistsBracersTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent equipment = game.getPermanent(this.getSourceId());
if (equipment == null || !equipment.isAttachedTo(event.getSourceId())) {
return false;
}
StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getSourceId());
if (stackAbility == null || stackAbility.getStackAbility().isManaActivatedAbility()) {
return false;
}
this.getEffects().setValue("stackObject", stackAbility);
return true;
}
@Override
public String getRule() {
return "Whenever an ability of equipped creature is activated, if it isn't a mana ability, " +
"copy that ability. You may choose new targets for the copy.";
}
}

View file

@ -6,6 +6,7 @@ import mage.abilities.effects.common.DamagePlayersEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SetTargetPointer;
import mage.constants.SubType;
import mage.constants.TargetController;
@ -19,14 +20,15 @@ public final class KeralKeepDisciples extends CardImpl {
public KeralKeepDisciples(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{R}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.MONK);
this.power = new MageInt(4);
this.toughness = new MageInt(3);
// Whenever you activate a loyalty ability of a Chandra planeswalker, Keral Keep Disciples deals 1 damage to each opponent.
this.addAbility(new ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility(new DamagePlayersEffect(1, TargetController.OPPONENT), SubType.CHANDRA));
this.addAbility(new ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility(
new DamagePlayersEffect(1, TargetController.OPPONENT), SubType.CHANDRA, SetTargetPointer.NONE));
}
private KeralKeepDisciples(final KeralKeepDisciples card) {

View file

@ -1,20 +1,20 @@
package mage.cards.k;
import mage.MageInt;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.ActivateAbilityTriggeredAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.CopyStackObjectEffect;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SetTargetPointer;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.StackAbility;
import mage.filter.FilterStackObject;
import mage.filter.common.FilterActivatedOrTriggeredAbility;
import mage.filter.predicate.other.ArtifactSourcePredicate;
import mage.filter.predicate.other.NotManaAbilityPredicate;
import java.util.UUID;
@ -22,6 +22,12 @@ import java.util.UUID;
* @author emerald000
*/
public final class KurkeshOnakkeAncient extends CardImpl {
private static final FilterStackObject filter = new FilterActivatedOrTriggeredAbility("an ability of an artifact, if it isn't a mana ability");
static {
filter.add(NotManaAbilityPredicate.instance);
filter.add(ArtifactSourcePredicate.instance);
}
public KurkeshOnakkeAncient(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{R}");
@ -33,7 +39,7 @@ public final class KurkeshOnakkeAncient extends CardImpl {
this.toughness = new MageInt(3);
// Whenever you activate an ability of an artifact, if it isn't a mana ability, you may pay {R}. If you do, copy that ability. You may choose new targets for the copy.
this.addAbility(new KurkeshOnakkeAncientTriggeredAbility());
this.addAbility(new ActivateAbilityTriggeredAbility(new DoIfCostPaid(new CopyStackObjectEffect(), new ManaCostsImpl<>("{R}")), filter, SetTargetPointer.SPELL));
}
private KurkeshOnakkeAncient(final KurkeshOnakkeAncient card) {
@ -45,42 +51,3 @@ public final class KurkeshOnakkeAncient extends CardImpl {
return new KurkeshOnakkeAncient(this);
}
}
class KurkeshOnakkeAncientTriggeredAbility extends TriggeredAbilityImpl {
KurkeshOnakkeAncientTriggeredAbility() {
super(Zone.BATTLEFIELD, new DoIfCostPaid(new CopyStackObjectEffect(), new ManaCostsImpl<>("{R}")));
setTriggerPhrase("Whenever you activate an ability of an artifact, if it isn't a mana ability, ");
}
private KurkeshOnakkeAncientTriggeredAbility(final KurkeshOnakkeAncientTriggeredAbility ability) {
super(ability);
}
@Override
public KurkeshOnakkeAncientTriggeredAbility copy() {
return new KurkeshOnakkeAncientTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!event.getPlayerId().equals(getControllerId())) {
return false;
}
Card source = game.getPermanentOrLKIBattlefield(event.getSourceId());
if (source == null || !source.isArtifact(game)) {
return false;
}
StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getSourceId());
if (stackAbility == null || stackAbility.getStackAbility().isManaActivatedAbility()) {
return false;
}
this.getEffects().setValue("stackObject", stackAbility);
return true;
}
}

View file

@ -19,6 +19,7 @@ import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.StackAbility;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
@ -114,6 +115,7 @@ class LeoriSparktouchedHunterTriggeredAbility extends DelayedTriggeredAbility {
super(new CopyStackObjectEffect(), Duration.EndOfTurn, false);
this.subType = subType;
this.addHint(new StaticHint("Chosen Subtype: " + subType));
setTriggerPhrase("Whenever you activate an ability of a planeswalker of the chosen type, ");
}
private LeoriSparktouchedHunterTriggeredAbility(final LeoriSparktouchedHunterTriggeredAbility ability) {
@ -146,13 +148,7 @@ class LeoriSparktouchedHunterTriggeredAbility extends DelayedTriggeredAbility {
if (permanent == null || !permanent.isPlaneswalker(game) || !permanent.hasSubtype(subType, game)) {
return false;
}
this.getEffects().setValue("stackObject", stackAbility);
getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game));
return true;
}
@Override
public String getRule() {
return "Whenever you activate an ability of a planeswalker of the chosen type, copy that ability. " +
"You may choose new targets for the copies.";
}
}

View file

@ -2,7 +2,7 @@ package mage.cards.l;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.ActivateAbilityTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.common.CopyStackObjectEffect;
@ -10,11 +10,12 @@ import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterStackObject;
import mage.filter.common.FilterActivatedOrTriggeredAbility;
import mage.filter.predicate.other.NotManaAbilityPredicate;
import mage.game.ExileZone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.StackAbility;
import mage.util.CardUtil;
import java.util.UUID;
@ -23,7 +24,11 @@ import java.util.UUID;
* @author TheElk801
*/
public final class LocusOfEnlightenment extends CardImpl {
private static final FilterStackObject filter = new FilterActivatedOrTriggeredAbility("an ability that isn't a mana ability");
static {
filter.add(NotManaAbilityPredicate.instance);
}
public LocusOfEnlightenment(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "");
@ -35,7 +40,7 @@ public final class LocusOfEnlightenment extends CardImpl {
this.addAbility(new SimpleStaticAbility(new LocusOfEnlightenmentEffect()));
// Whenever you activate an ability that isn't a mana ability, copy it. You may choose new targets for the copy.
this.addAbility(new LocusOfEnlightenmentTriggeredAbility());
this.addAbility(new ActivateAbilityTriggeredAbility(new CopyStackObjectEffect("it"), filter, SetTargetPointer.SPELL));
}
private LocusOfEnlightenment(final LocusOfEnlightenment card) {
@ -91,38 +96,3 @@ class LocusOfEnlightenmentEffect extends ContinuousEffectImpl {
return true;
}
}
class LocusOfEnlightenmentTriggeredAbility extends TriggeredAbilityImpl {
LocusOfEnlightenmentTriggeredAbility() {
super(Zone.BATTLEFIELD, new CopyStackObjectEffect().setText("copy it. You may choose new targets for the copy"));
this.setTriggerPhrase("Whenever you activate an ability that isn't a mana ability, ");
}
private LocusOfEnlightenmentTriggeredAbility(final LocusOfEnlightenmentTriggeredAbility ability) {
super(ability);
}
@Override
public LocusOfEnlightenmentTriggeredAbility copy() {
return new LocusOfEnlightenmentTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!event.getPlayerId().equals(getControllerId())) {
return false;
}
StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getSourceId());
if (stackAbility == null || stackAbility.isManaAbility()) {
return false;
}
this.getEffects().setValue("stackObject", stackAbility);
return true;
}
}

View file

@ -18,8 +18,8 @@ import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.game.stack.StackAbility;
import mage.game.stack.StackObject;
import mage.target.common.TargetCreaturePermanent;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
@ -65,7 +65,9 @@ public final class MagusLuceaKane extends CardImpl {
class MagusLuceaKaneTriggeredAbility extends DelayedTriggeredAbility {
MagusLuceaKaneTriggeredAbility() {
super(new CopyStackObjectEffect(), Duration.EndOfTurn, true, false);
super(new CopyStackObjectEffect("that spell or ability"), Duration.EndOfTurn, true, false);
setTriggerPhrase("When you next cast a spell with {X} in its mana cost " +
"or activate an ability with {X} in its activation cost this turn, ");
}
private MagusLuceaKaneTriggeredAbility(final MagusLuceaKaneTriggeredAbility ability) {
@ -92,9 +94,9 @@ class MagusLuceaKaneTriggeredAbility extends DelayedTriggeredAbility {
// activated ability
if (event.getType() == GameEvent.EventType.ACTIVATED_ABILITY) {
StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getSourceId());
if (stackAbility != null && !stackAbility.getStackAbility().isManaActivatedAbility()) {
if (stackAbility != null) {
if (stackAbility.getManaCostsToPay().containsX()) {
this.getEffects().setValue("stackObject", (StackObject) stackAbility);
getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game));
return true;
}
}
@ -104,16 +106,10 @@ class MagusLuceaKaneTriggeredAbility extends DelayedTriggeredAbility {
if (event.getType() == GameEvent.EventType.SPELL_CAST) {
Spell spell = game.getStack().getSpell(event.getTargetId());
if (spell != null && spell.getSpellAbility().getManaCostsToPay().containsX()) {
this.getEffects().setValue("stackObject", (StackObject) spell);
getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game));
return true;
}
}
return false;
}
@Override
public String getRule() {
return "When you next cast a spell with {X} in its mana cost or activate an ability with {X} in its "
+ "activation cost this turn, copy that spell or ability. You may choose new targets for the copy.";
}
}

View file

@ -14,6 +14,7 @@ import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.StackObject;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
@ -79,7 +80,7 @@ class MirrorShieldHopliteTriggeredAbility extends TriggeredAbilityImpl {
if (permanent == null || !permanent.isCreature(game) || !permanent.isControlledBy(this.getControllerId())) {
return false;
}
this.getEffects().setValue("stackObject", sourceObject);
getEffects().setTargetPointer(new FixedTarget(event.getSourceId(), game));
return true;
}
}

View file

@ -1,16 +1,16 @@
package mage.cards.r;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.common.ActivateAbilityTriggeredAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.CopyStackObjectEffect;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.StackAbility;
import mage.constants.SetTargetPointer;
import mage.filter.FilterStackObject;
import mage.filter.common.FilterActivatedOrTriggeredAbility;
import mage.filter.predicate.other.NotManaAbilityPredicate;
import java.util.UUID;
@ -18,12 +18,17 @@ import java.util.UUID;
* @author LevelX2
*/
public final class RingsOfBrighthearth extends CardImpl {
private static final FilterStackObject filter = new FilterActivatedOrTriggeredAbility("an ability, if it isn't a mana ability");
static {
filter.add(NotManaAbilityPredicate.instance);
}
public RingsOfBrighthearth(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}");
// Whenever you activate an ability, if it isn't a mana ability, you may pay {2}. If you do, copy that ability. You may choose new targets for the copy.
this.addAbility(new RingsOfBrighthearthTriggeredAbility());
this.addAbility(new ActivateAbilityTriggeredAbility(new DoIfCostPaid(new CopyStackObjectEffect(), new ManaCostsImpl<>("{2}")), filter, SetTargetPointer.SPELL));
}
private RingsOfBrighthearth(final RingsOfBrighthearth card) {
@ -35,43 +40,3 @@ public final class RingsOfBrighthearth extends CardImpl {
return new RingsOfBrighthearth(this);
}
}
class RingsOfBrighthearthTriggeredAbility extends TriggeredAbilityImpl {
RingsOfBrighthearthTriggeredAbility() {
super(Zone.BATTLEFIELD, new DoIfCostPaid(new CopyStackObjectEffect(), new GenericManaCost(2)));
}
private RingsOfBrighthearthTriggeredAbility(final RingsOfBrighthearthTriggeredAbility ability) {
super(ability);
}
@Override
public RingsOfBrighthearthTriggeredAbility copy() {
return new RingsOfBrighthearthTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!event.getPlayerId().equals(getControllerId())) {
return false;
}
StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getSourceId());
if (stackAbility == null || stackAbility.getStackAbility().isManaActivatedAbility()) {
return false;
}
this.getEffects().setValue("stackObject", stackAbility);
return true;
}
@Override
public String getRule() {
return "Whenever you activate an ability, if it isn't a mana ability, you may pay {2}. " +
"If you do, copy that ability. You may choose new targets for the copy.";
}
}

View file

@ -22,6 +22,7 @@ import mage.game.stack.StackObject;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import mage.target.common.TargetPlaneswalkerPermanent;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
@ -72,6 +73,7 @@ class RowansTalentTriggeredAbility extends TriggeredAbilityImpl {
RowansTalentTriggeredAbility() {
super(Zone.BATTLEFIELD, new CopyStackObjectEffect());
setTriggerPhrase("Whenever you activate a loyalty ability of enchanted planeswalker, ");
}
private RowansTalentTriggeredAbility(final RowansTalentTriggeredAbility ability) {
@ -100,13 +102,7 @@ class RowansTalentTriggeredAbility extends TriggeredAbilityImpl {
if (stackObject == null || !(stackObject.getStackAbility() instanceof LoyaltyAbility)) {
return false;
}
this.getEffects().setValue("stackObject", stackObject);
getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game));
return true;
}
@Override
public String getRule() {
return "Whenever you activate a loyalty ability of enchanted planeswalker, " +
"copy that ability. You may choose new targets for the copy.";
}
}

View file

@ -1,24 +1,31 @@
package mage.cards.u;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.common.ActivateAbilityTriggeredAbility;
import mage.abilities.common.SpellCastControllerTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.CopyStackObjectEffect;
import mage.abilities.meta.OrTriggeredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.SetTargetPointer;
import mage.constants.Zone;
import mage.filter.FilterSpell;
import mage.filter.FilterStackObject;
import mage.filter.common.FilterActivatedOrTriggeredAbility;
import mage.filter.common.FilterInstantOrSorcerySpell;
import mage.filter.predicate.Predicate;
import mage.filter.predicate.mageobject.PermanentPredicate;
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.game.stack.StackObject;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.Map;
import java.util.UUID;
/**
@ -26,18 +33,33 @@ import java.util.UUID;
*/
public final class UnboundFlourishing extends CardImpl {
final static String needPrefix = "_UnboundFlourishing_NeedCopy";
private static final FilterSpell filterPermanent = new FilterSpell("a permanent spell with a mana cost that contains {X}");
private static final FilterSpell filterInstantSorcery = new FilterInstantOrSorcerySpell("an instant or sorcery spell with a mana cost that contains {X}");
private static final FilterStackObject filterAbility = new FilterActivatedOrTriggeredAbility("an activated ability with an activation cost that contains {X}");
static {
filterPermanent.add(PermanentPredicate.instance);
filterPermanent.add(UnboundFlourishingCostContainsXPredicate.instance);
filterInstantSorcery.add(UnboundFlourishingCostContainsXPredicate.instance);
filterAbility.add(UnboundFlourishingCostContainsXPredicate.instance);
}
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()));
this.addAbility(new SpellCastControllerTriggeredAbility(new UnboundFlourishingDoubleXEffect(), filterPermanent, false, SetTargetPointer.SPELL));
// 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());
this.addAbility(new OrTriggeredAbility(Zone.BATTLEFIELD,
new CopyStackObjectEffect("that spell or ability"), false,
"Whenever you cast an instant or sorcery spell or activate an ability, " +
"if that spell's mana cost or that ability's activation cost contains {X}, ",
new SpellCastControllerTriggeredAbility(null, filterInstantSorcery, false, SetTargetPointer.SPELL),
new ActivateAbilityTriggeredAbility(null, filterAbility, SetTargetPointer.SPELL)
));
}
private UnboundFlourishing(final UnboundFlourishing card) {
@ -50,130 +72,53 @@ public final class UnboundFlourishing extends CardImpl {
}
}
class UnboundFlourishingDoubleXEffect extends ReplacementEffectImpl {
enum UnboundFlourishingCostContainsXPredicate implements Predicate<StackObject> {
instance;
@Override
public boolean apply(StackObject input, Game game) {
if (input instanceof Spell) {
return ((Spell) input).getSpellAbility().getManaCostsToPay().containsX();
} else if (input instanceof StackAbility) {
return input.getStackAbility().getManaCostsToPay().containsX();
} else {
return false;
}
}
@Override
public String toString() {
return "Contains {X}";
}
}
class UnboundFlourishingDoubleXEffect extends OneShotEffect {
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";
super(Outcome.Benefit);
this.staticText = "double the value of X";
}
private UnboundFlourishingDoubleXEffect(final UnboundFlourishingDoubleXEffect effect) {
super(effect);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
event.setAmount(CardUtil.overflowMultiply(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(game) && spell.isControlledBy(source.getControllerId());
}
@Override
public UnboundFlourishingDoubleXEffect copy() {
return new UnboundFlourishingDoubleXEffect(this);
}
}
class UnboundFlourishingCopyAbility extends TriggeredAbilityImpl {
UnboundFlourishingCopyAbility() {
super(Zone.BATTLEFIELD, new UnboundFlourishingCopyEffect(), false);
setTriggerPhrase("Whenever you cast an instant or sorcery spell or activate an ability, " +
"if that spell's mana cost or that ability's activation cost contains {X}");
}
private 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())) {
return false;
}
// activated ability
if (event.getType() == GameEvent.EventType.ACTIVATED_ABILITY) {
StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getSourceId());
if (stackAbility != null && !stackAbility.getStackAbility().isManaActivatedAbility()) {
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)) {
if (spell.getSpellAbility().getManaCostsToPay().containsX()) {
game.getState().setValue(this.getSourceId() + UnboundFlourishing.needPrefix, spell);
return true;
}
}
}
return false;
}
}
class UnboundFlourishingCopyEffect extends OneShotEffect {
UnboundFlourishingCopyEffect() {
super(Outcome.Benefit);
this.staticText = ", copy that spell or ability. You may choose new targets for the copy";
}
private 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);
return true;
}
// copy spell
if (needObject instanceof Spell) {
Spell spell = (Spell) needObject;
spell.createCopyOnStack(game, source, source.getControllerId(), true);
return true;
Spell needObject = game.getSpell(getTargetPointer().getFirst(game, source));
if (needObject != null) {
Map<String, Object> tagsMap = CardUtil.getSourceCostsTagsMap(game, needObject.getSpellAbility());
if (tagsMap.containsKey("X")) {
tagsMap.put("X", ((int) tagsMap.get("X")) * 2);
}
}
}
return false;

View file

@ -17,6 +17,7 @@ import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.StackAbility;
import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
import java.util.UUID;
@ -96,7 +97,7 @@ class VerrakWarpedSengirTriggeredAbility extends TriggeredAbilityImpl {
if (lifePaid > 0) {
this.getEffects().clear();
this.addEffect(new DoIfCostPaid(new CopyStackObjectEffect(), new PayLifeCost(lifePaid)));
this.getEffects().setValue("stackObject", stackAbility);
this.getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game));
return true;
}
return false;

View file

@ -47,10 +47,10 @@ public class UnboundFlourishingTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerA, "Endless One", 1); // {X}
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
// cast with X=3, but double it
// cast with X=3, but double it twice
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Endless One");
setChoice(playerA, "Unbound Flourishing"); // choose replacement effects
setChoice(playerA, "X=3");
setChoice(playerA, ""); //stack triggers
checkPermanentCounters("after", 1, PhaseStep.BEGIN_COMBAT, playerA, "Endless One", CounterType.P1P1, 3 * 2 * 2);
setStrictChooseMode(true);
@ -163,6 +163,36 @@ public class UnboundFlourishingTest extends CardTestPlayerBase {
execute();
}
@Test
public void test_OnActivatedAbility_MustCopy2Counter() {
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);
//
//
addCard(Zone.BATTLEFIELD, playerB, "Island", 1);
addCard(Zone.HAND, playerB, "Stifle", 1);
// 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");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Stifle"); //Counter the original ability, copy must still work
addTarget(playerB, "stack ability ({X}{R}, {T}, Sacrifice");
setChoice(playerA, true); // change target
addTarget(playerA, playerB); // change to B
checkLife("after", 1, PhaseStep.BEGIN_COMBAT, playerA, 20); // original damage is countered
checkLife("after", 1, PhaseStep.BEGIN_COMBAT, playerB, 20 - 3); // copy damage
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
}
@Test
public void test_VariableManaCost() {
// VariableManaCost contains:

View file

@ -2816,7 +2816,7 @@ public class TestPlayer implements Player {
}
@Override
public int announceXMana(int min, int max, int multiplier, String message, Game game, Ability ability) {
public int announceXMana(int min, int max, String message, Game game, Ability ability) {
assertAliasSupportInChoices(false);
if (!choices.isEmpty()) {
for (String choice : choices) {
@ -2830,7 +2830,7 @@ public class TestPlayer implements Player {
this.chooseStrictModeFailed("choice", game, getInfo(ability, game)
+ "\nMessage: " + message);
return computerPlayer.announceXMana(min, max, multiplier, message, game, ability);
return computerPlayer.announceXMana(min, max, message, game, ability);
}
@Override

View file

@ -278,8 +278,6 @@ public abstract class AbilityImpl implements Ability {
int xValue = CardUtil.getSourceCostsTag(game, this, "X", 0);
this.clearManaCostsToPay();
VariableManaCost xCosts = new VariableManaCost(VariableCostType.ADDITIONAL);
// 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, xValue, false);
addManaCostsToPay(xCosts);
} else {
@ -617,8 +615,6 @@ public abstract class AbilityImpl implements Ability {
Cost fixedCost = variableCost.getFixedCostsFromAnnouncedValue(xValue);
addCost(fixedCost);
// set the xcosts to paid
// 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, xValue, false);
((Cost) variableCost).setPaid();
String message = controller.getLogName() + " announces a value of " + xValue + " (" + variableCost.getActionText() + ')'
@ -653,13 +649,6 @@ 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, this.getId(), this, getControllerId(), value);
game.replaceEvent(xEvent, this);
return xEvent.getAmount();
}
/**
* Handles X mana costs and sets manaCostsToPay.
*
@ -695,9 +684,8 @@ public abstract class AbilityImpl implements Ability {
if (variableManaCost != null) {
if (!variableManaCost.isPaid()) { // should only happen for human players
int xValue;
int xValueMultiplier = handleManaXMultiplier(game, 1);
if (!noMana || variableManaCost.getCostType().canUseAnnounceOnFreeCast()) {
xValue = controller.announceXMana(variableManaCost.getMinX(), variableManaCost.getMaxX(), xValueMultiplier,
xValue = controller.announceXMana(variableManaCost.getMinX(), variableManaCost.getMaxX(),
"Announce the value for " + variableManaCost.getText(), game, this);
int amountMana = xValue * variableManaCost.getXInstancesCount();
StringBuilder manaString = threadLocalBuilder.get();
@ -728,8 +716,8 @@ public abstract class AbilityImpl implements Ability {
}
}
addManaCostsToPay(new ManaCostsImpl<>(manaString.toString()));
getManaCostsToPay().setX(xValue * xValueMultiplier, amountMana);
setCostsTag("X", xValue * xValueMultiplier);
getManaCostsToPay().setX(xValue, amountMana);
setCostsTag("X", xValue);
}
variableManaCost.setPaid();
}

View file

@ -0,0 +1,73 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.SetTargetPointer;
import mage.constants.Zone;
import mage.filter.FilterStackObject;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.StackAbility;
import mage.target.targetpointer.FixedTarget;
public class ActivateAbilityTriggeredAbility extends TriggeredAbilityImpl {
private final FilterStackObject filter;
protected final SetTargetPointer setTargetPointer;
public ActivateAbilityTriggeredAbility(Effect effect, FilterStackObject filter, SetTargetPointer setTargetPointer) {
this(Zone.BATTLEFIELD, effect, filter, setTargetPointer);
}
public ActivateAbilityTriggeredAbility(Zone zone, Effect effect, FilterStackObject filter, SetTargetPointer setTargetPointer) {
super(zone, effect, false);
this.filter = filter;
this.setTargetPointer = setTargetPointer;
setTriggerPhrase("Whenever you activate " + filter.getMessage() + ", ");
}
private ActivateAbilityTriggeredAbility(final ActivateAbilityTriggeredAbility ability) {
super(ability);
this.filter = ability.filter;
this.setTargetPointer = ability.setTargetPointer;
}
@Override
public ActivateAbilityTriggeredAbility copy() {
return new ActivateAbilityTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!event.getPlayerId().equals(getControllerId())) {
return false;
}
StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getTargetId());
if (stackAbility == null) {
return false;
}
if (!filter.match(stackAbility, event.getPlayerId(), this, game)) {
return false;
}
switch (setTargetPointer) {
case NONE:
break;
case PLAYER:
getAllEffects().setTargetPointer(new FixedTarget(getControllerId(), game));
break;
case SPELL:
getAllEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game));
break;
default:
throw new UnsupportedOperationException("Unexpected setTargetPointer in ActivateAbilityTriggeredAbility: " + setTargetPointer);
}
return true;
}
}

View file

@ -3,26 +3,31 @@ package mage.abilities.common;
import mage.abilities.LoyaltyAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.SetTargetPointer;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.StackAbility;
import mage.target.targetpointer.FixedTarget;
public class ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility extends TriggeredAbilityImpl {
private final SubType planeswalkerSubType;
protected final SetTargetPointer setTargetPointer;
public ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility(Effect effect, SubType planeswalkerSubType) {
public ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility(Effect effect, SubType planeswalkerSubType, SetTargetPointer setTargetPointer) {
super(Zone.BATTLEFIELD, effect, false);
this.planeswalkerSubType = planeswalkerSubType;
this.setTargetPointer = setTargetPointer;
setTriggerPhrase("Whenever you activate a loyalty ability of a " + planeswalkerSubType.getDescription() + " planeswalker, ");
}
private ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility(final ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility ability) {
super(ability);
this.planeswalkerSubType = ability.planeswalkerSubType;
this.setTargetPointer = ability.setTargetPointer;
}
@Override
@ -49,7 +54,22 @@ public class ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility extends Triggere
|| !permanent.hasSubtype(planeswalkerSubType, game)) {
return false;
}
this.getEffects().setValue("stackObject", stackAbility);
switch (setTargetPointer) {
case NONE:
break;
case PLAYER:
getAllEffects().setTargetPointer(new FixedTarget(getControllerId(), game));
break;
case SPELL:
getAllEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game));
break;
case PERMANENT:
getAllEffects().setTargetPointer(new FixedTarget(event.getSourceId(), game));
break;
default:
throw new UnsupportedOperationException("Unexpected setTargetPointer in ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility: " + setTargetPointer);
}
return true;
}
}

View file

@ -418,7 +418,6 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
game.undo(playerId);
this.clearPaid();
// TODO: checks Word of Command with Unbound Flourishing's X multiplier
// TODO: checks Word of Command with {X}{X} cards
int amount = 0;
List<VariableCost> variableCosts = getVariableCosts();

View file

@ -3,18 +3,25 @@ package mage.abilities.effects.common;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.stack.StackObject;
import mage.players.Player;
import java.util.UUID;
/**
* @author TheElk801
*/
public class CopyStackObjectEffect extends OneShotEffect {
public CopyStackObjectEffect() {
this("that ability");
}
public CopyStackObjectEffect(String name) {
super(Outcome.Copy);
staticText = "copy that ability. You may choose new targets for the copy";
staticText = "copy "+ name + ". You may choose new targets for the copy";
}
private CopyStackObjectEffect(final CopyStackObjectEffect effect) {
@ -29,11 +36,15 @@ public class CopyStackObjectEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
StackObject ability = (StackObject) getValue("stackObject");
if (controller == null || ability == null) {
UUID id = getTargetPointer().getFirst(game, source);
StackObject object = game.getStack().getStackObject(id);
if (object == null) {
object = (StackObject) game.getLastKnownInformation(id, Zone.STACK);
}
if (controller == null || object == null) {
return false;
}
ability.createCopyOnStack(game, source, source.getControllerId(), true);
object.createCopyOnStack(game, source, source.getControllerId(), true);
return true;
}
}

View file

@ -72,9 +72,13 @@ public class OrTriggeredAbility extends TriggeredAbilityImpl {
public boolean checkTrigger(GameEvent event, Game game) {
boolean toRet = false;
for (TriggeredAbility ability : triggeredAbilities) {
for (Effect e : getEffects()) { //Add effects to the sub-abilities so that they can set target pointers
ability.addEffect(e);
}
if (ability.checkEventType(event, game) && ability.checkTrigger(event, game)) {
toRet = true;
}
ability.getEffects().clear(); //Remove afterwards, ensures that they remain synced even with copying
}
return toRet;
}

View file

@ -0,0 +1,30 @@
package mage.filter.predicate.other;
import mage.MageObject;
import mage.abilities.Ability;
import mage.filter.predicate.ObjectSourcePlayer;
import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.game.Game;
import mage.game.stack.StackAbility;
import mage.game.stack.StackObject;
/**
* @author notgreat
*/
public enum AbilitySourceAttachedPredicate implements ObjectSourcePlayerPredicate<StackObject> {
instance;
@Override
public boolean apply(ObjectSourcePlayer<StackObject> input, Game game) {
MageObject obj = input.getObject();
Ability source = input.getSource();
return obj instanceof StackAbility
&& ((StackAbility) obj).getSourceId().equals(source.getSourcePermanentOrLKI(game).getAttachedTo());
}
@Override
public String toString() {
return "Attached activated/triggered";
}
}

View file

@ -0,0 +1,26 @@
package mage.filter.predicate.other;
import mage.abilities.Ability;
import mage.filter.predicate.Predicate;
import mage.game.Game;
import mage.game.stack.StackObject;
/**
* @author notgreat
*/
public enum NotManaAbilityPredicate implements Predicate<StackObject> {
instance;
@Override
public boolean apply(StackObject input, Game game) {
if (!(input instanceof Ability)) {
return false;
}
return !((Ability) input).isManaAbility();
}
@Override
public String toString() {
return "isn't a mana ability";
}
}

View file

@ -1,22 +1,28 @@
package mage.game.command.emblems;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.ActivateAbilityTriggeredAbility;
import mage.abilities.effects.common.CopyStackObjectEffect;
import mage.constants.SetTargetPointer;
import mage.constants.Zone;
import mage.game.Game;
import mage.filter.FilterStackObject;
import mage.filter.common.FilterActivatedOrTriggeredAbility;
import mage.filter.predicate.other.NotManaAbilityPredicate;
import mage.game.command.Emblem;
import mage.game.events.GameEvent;
import mage.game.stack.StackAbility;
/**
* @author TheElk801
*/
public final class RowanKenrithEmblem extends Emblem {
// Target player gets an emblem with "Whenever you activate an ability that isn't a mana ability, copy it. You may choose new targets for the copy."
private static final FilterStackObject filter = new FilterActivatedOrTriggeredAbility("an ability that isn't a mana ability");
static {
filter.add(NotManaAbilityPredicate.instance);
}
public RowanKenrithEmblem() {
super("Emblem Rowan Kenrith");
this.getAbilities().add(new RowanKenrithEmblemTriggeredAbility());
this.getAbilities().add(new ActivateAbilityTriggeredAbility(Zone.COMMAND, new CopyStackObjectEffect("it"), filter, SetTargetPointer.SPELL));
}
private RowanKenrithEmblem(final RowanKenrithEmblem card) {
@ -28,42 +34,3 @@ public final class RowanKenrithEmblem extends Emblem {
return new RowanKenrithEmblem(this);
}
}
class RowanKenrithEmblemTriggeredAbility extends TriggeredAbilityImpl {
RowanKenrithEmblemTriggeredAbility() {
super(Zone.COMMAND, new CopyStackObjectEffect(), false);
}
private RowanKenrithEmblemTriggeredAbility(final RowanKenrithEmblemTriggeredAbility ability) {
super(ability);
}
@Override
public RowanKenrithEmblemTriggeredAbility copy() {
return new RowanKenrithEmblemTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!event.getPlayerId().equals(getControllerId())) {
return false;
}
StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getSourceId());
if (stackAbility == null || stackAbility.getStackAbility().isManaActivatedAbility()) {
return false;
}
this.getEffects().setValue("stackObject", stackAbility);
return true;
}
@Override
public String getRule() {
return "Whenever you activate an ability that isn't a mana ability, copy it. You may choose new targets for the copy.";
}
}

View file

@ -219,13 +219,6 @@ public class GameEvent implements Serializable {
sourceId sourceId of the mount
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 or ability
amount X multiplier to change X value, default 1
*/
CAST_SPELL,
CAST_SPELL_LATE,
/* SPELL_CAST, CAST_SPELL_LATE

View file

@ -1,18 +1,19 @@
package mage.game.stack;
import java.util.ArrayDeque;
import java.util.Date;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.constants.PutCards;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.util.CardUtil;
import org.apache.log4j.Logger;
import java.util.ArrayDeque;
import java.util.Date;
import java.util.UUID;
/**
* @author BetaSteward_at_googlemail.com
*/
@ -82,6 +83,7 @@ public class SpellStack extends ArrayDeque<StackObject> {
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER, objectId, source, stackObject.getControllerId()))) {
if (!(stackObject instanceof Spell)) { // spells are removed from stack by the card movement
this.remove(stackObject, game);
game.rememberLKI(Zone.STACK, stackObject);
}
stackObject.counter(source, game, putCard);
if (!game.isSimulation()) {

View file

@ -731,11 +731,7 @@ public interface Player extends MageItem, Copyable<Player> {
boolean shuffleCardsToLibrary(Card card, Game game, Ability source);
// set the value for X mana spells and abilities
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 multiplier, String message, Game game, Ability ability);
int announceXMana(int min, int max, 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

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