From 8169799213d66860268cd9cd40b5e3b051547bd4 Mon Sep 17 00:00:00 2001 From: Susucre <34709007+Susucre@users.noreply.github.com> Date: Wed, 16 Aug 2023 14:53:02 +0200 Subject: [PATCH] [WOE] implement Troublemaker Ouphe, Torch the Tower (add Bargain ability) (#10812) * add start of Bargain Current version probably has a bunch of bugs related to zcc and copy. * add Torch the Tower * add Torch the Tower tests * add better than nothing activationKey before tag cost tracking gets cleaned up --------- Co-authored-by: Evan Kranzler --- Mage.Sets/src/mage/cards/t/TorchTheTower.java | 54 ++++ .../src/mage/cards/t/TroublemakerOuphe.java | 64 +++++ Mage.Sets/src/mage/sets/WildsOfEldraine.java | 3 + .../cards/abilities/keywords/BargainTest.java | 230 ++++++++++++++++++ .../condition/common/BargainedCondition.java | 38 +++ .../abilities/hint/ConditionTrueHint.java | 58 +++++ .../hint/common/BargainCostWasPaidHint.java | 27 ++ .../abilities/keyword/BargainAbility.java | 149 ++++++++++++ 8 files changed, 623 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/TorchTheTower.java create mode 100644 Mage.Sets/src/mage/cards/t/TroublemakerOuphe.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/BargainTest.java create mode 100644 Mage/src/main/java/mage/abilities/condition/common/BargainedCondition.java create mode 100644 Mage/src/main/java/mage/abilities/hint/ConditionTrueHint.java create mode 100644 Mage/src/main/java/mage/abilities/hint/common/BargainCostWasPaidHint.java create mode 100644 Mage/src/main/java/mage/abilities/keyword/BargainAbility.java diff --git a/Mage.Sets/src/mage/cards/t/TorchTheTower.java b/Mage.Sets/src/mage/cards/t/TorchTheTower.java new file mode 100644 index 00000000000..88d06ed7016 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TorchTheTower.java @@ -0,0 +1,54 @@ +package mage.cards.t; + +import mage.abilities.condition.common.BargainedCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.replacement.DealtDamageToCreatureBySourceDies; +import mage.abilities.effects.keyword.ScryEffect; +import mage.abilities.keyword.BargainAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.target.common.TargetCreatureOrPlaneswalker; +import mage.watchers.common.DamagedByWatcher; + +import java.util.UUID; + +/** + * + * @author Susucr + */ +public final class TorchTheTower extends CardImpl { + + public TorchTheTower(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}"); + + // Bargain (You may sacrifice an artifact, enchantment, or token as you cast this spell.) + this.addAbility(new BargainAbility()); + + // Torch the Tower deals 2 damage to target creature or planeswalker. If this spell was bargained, instead it deals 3 damage to that permanent and you scry 1. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new DamageTargetEffect(3), + new DamageTargetEffect(2), + BargainedCondition.instance, + "{this} deals 2 damage to target creature or planeswalker. " + + "If this spell was bargained, instead it deals 3 damage to that permanent and you scry 1" + ).addEffect(new ScryEffect(1))); + this.getSpellAbility().addTarget(new TargetCreatureOrPlaneswalker()); + + // If a permanent dealt damage by Torch the Tower would die this turn, exile it instead. + this.getSpellAbility().addEffect(new DealtDamageToCreatureBySourceDies(this, Duration.EndOfTurn) + .setText("if a permanent dealt damage by {this} would die this turn, exile it instead")); + this.getSpellAbility().addWatcher(new DamagedByWatcher(false)); + } + + private TorchTheTower(final TorchTheTower card) { + super(card); + } + + @Override + public TorchTheTower copy() { + return new TorchTheTower(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TroublemakerOuphe.java b/Mage.Sets/src/mage/cards/t/TroublemakerOuphe.java new file mode 100644 index 00000000000..1b29211f920 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TroublemakerOuphe.java @@ -0,0 +1,64 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.TriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.BargainedCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.keyword.BargainAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class TroublemakerOuphe extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("artifact or enchantment an opponent controls"); + + static { + filter.add(TargetController.OPPONENT.getControllerPredicate()); + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.ENCHANTMENT.getPredicate() + )); + } + + public TroublemakerOuphe(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.OUPHE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Bargain (You may sacrifice an artifact, enchantment, or token as you cast this spell.) + this.addAbility(new BargainAbility()); + + // When Troublemaker Ouphe enters the battlefield, if it was bargained, exile target artifact or enchantment an opponent controls. + TriggeredAbility trigger = new EntersBattlefieldTriggeredAbility(new ExileTargetEffect()); + trigger.addTarget(new TargetPermanent(filter)); + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + trigger, + BargainedCondition.instance, + "When {this} enters the battlefield, if it was bargained, exile target artifact or enchantment an opponent controls." + )); + } + + private TroublemakerOuphe(final TroublemakerOuphe card) { + super(card); + } + + @Override + public TroublemakerOuphe copy() { + return new TroublemakerOuphe(this); + } +} diff --git a/Mage.Sets/src/mage/sets/WildsOfEldraine.java b/Mage.Sets/src/mage/sets/WildsOfEldraine.java index b5b1bf7d390..2e1fdb1fada 100644 --- a/Mage.Sets/src/mage/sets/WildsOfEldraine.java +++ b/Mage.Sets/src/mage/sets/WildsOfEldraine.java @@ -42,6 +42,9 @@ public final class WildsOfEldraine extends ExpansionSet { cards.add(new SetCardInfo("Syr Ginger, the Meal Ender", 252, Rarity.RARE, mage.cards.s.SyrGingerTheMealEnder.class)); cards.add(new SetCardInfo("Talion, the Kindly Lord", 215, Rarity.MYTHIC, mage.cards.t.TalionTheKindlyLord.class)); cards.add(new SetCardInfo("Tanglespan Lookout", 188, Rarity.UNCOMMON, mage.cards.t.TanglespanLookout.class)); + cards.add(new SetCardInfo("Torch the Tower", 153, Rarity.COMMON, mage.cards.t.TorchTheTower.class)); + cards.add(new SetCardInfo("Tough Cookie", 193, Rarity.UNCOMMON, mage.cards.t.ToughCookie.class)); + cards.add(new SetCardInfo("Troublemaker Ouphe", 194, Rarity.COMMON, mage.cards.t.TroublemakerOuphe.class)); cards.add(new SetCardInfo("The Goose Mother", 204, Rarity.RARE, mage.cards.t.TheGooseMother.class)); cards.add(new SetCardInfo("Tough Cookie", 193, Rarity.UNCOMMON, mage.cards.t.ToughCookie.class)); cards.add(new SetCardInfo("Troyan, Gutsy Explorer", 217, Rarity.UNCOMMON, mage.cards.t.TroyanGutsyExplorer.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/BargainTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/BargainTest.java new file mode 100644 index 00000000000..8380fef973f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/BargainTest.java @@ -0,0 +1,230 @@ +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class BargainTest extends CardTestPlayerBase { + + /** + * Troublemaker Ouphe + * {1}{G} + * Creature — Ouphe + *

+ * Bargain (You may sacrifice an artifact, enchantment, or token as you cast this spell.) + * When Troublemaker Ouphe enters the battlefield, if it was bargained, exile target artifact or enchantment an opponent controls. + */ + private static final String troublemakerOuphe = "Troublemaker Ouphe"; + + // Artifact to be targetted by Troublemaker's Ouphe trigger, and be bargain fodder. + private static final String glider = "Aesthir Glider"; + // To blink Ouphe. + private static final String cloudshift = "Cloudshift"; + + /** + * Torch the Tower + * {R} + * Instant + *

+ * Bargain (You may sacrifice an artifact, enchantment, or token as you cast this spell.) + * Torch the Tower deals 2 damage to target creature or planeswalker. If this spell was bargained, instead it deals 3 damage to that permanent and you scry 1. + * If a permanent dealt damage by Torch the Tower would die this turn, exile it instead. + */ + private static final String torchTheTower = "Torch the Tower"; + + // 3 damage to target -- to finish off creatures and check torch's exile. + private static final String lightningBolt = "Lightning Bolt"; + + // 4/4 Artifact + private static final String stoneGolem = "Stone Golem"; + + @Test + public void testBargainNotPaidOuphe() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, troublemakerOuphe); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + addCard(Zone.BATTLEFIELD, playerA, glider); // Could be bargain. + + addCard(Zone.BATTLEFIELD, playerB, glider); + + setStrictChooseMode(true); + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, troublemakerOuphe); + setChoice(playerA, false); // Do not bargain. + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerB, glider, 1); + } + + @Test + public void testBargainNothingToBargain() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, troublemakerOuphe); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + + addCard(Zone.BATTLEFIELD, playerB, glider); + + setStrictChooseMode(true); + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, troublemakerOuphe); + // Nothing to bargain. + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerB, glider, 1); + } + + @Test + public void testBargainPaidOupheNothingToTarget() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, troublemakerOuphe); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + addCard(Zone.BATTLEFIELD, playerA, glider); // Is an artifact, hence something you can sac to Bargain. + + setStrictChooseMode(true); + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, troublemakerOuphe); + setChoice(playerA, true); // true to bargain + setChoice(playerA, glider); // choose to sac glider. + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, glider, 1); + } + + @Test + public void testBargainPaidOuphe() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, troublemakerOuphe); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + addCard(Zone.BATTLEFIELD, playerA, glider); // Is an artifact, hence something you can sac to Bargain. + + addCard(Zone.BATTLEFIELD, playerB, glider); + + setStrictChooseMode(true); + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, troublemakerOuphe); + setChoice(playerA, true); // true to bargain + setChoice(playerA, glider); // choose to sac glider. + addTarget(playerA, glider); // exile opponent's glider. + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerB, glider, 0); + assertExileCount(playerB, glider, 1); + assertGraveyardCount(playerA, glider, 1); + } + + @Test + public void testBargainPaidOupheThenFlicker() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, troublemakerOuphe); + addCard(Zone.HAND, playerA, cloudshift); + addCard(Zone.BATTLEFIELD, playerA, "Savannah", 3); + addCard(Zone.BATTLEFIELD, playerA, glider); // Is an artifact, hence something you can sac to Bargain. + + addCard(Zone.BATTLEFIELD, playerB, glider); + addCard(Zone.BATTLEFIELD, playerB, stoneGolem); + + setStrictChooseMode(true); + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, troublemakerOuphe); + setChoice(playerA, true); // true to bargain + setChoice(playerA, glider); // choose to sac glider. + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); // let Ouphe resolve, trigger on the stack. + addTarget(playerA, glider); // exile opponent's glider with etb. + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, cloudshift, troublemakerOuphe); // blink Ouphe. + // No trigger. It is no longer bargaining. + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerB, glider, 0); + assertExileCount(playerB, glider, 1); + assertPermanentCount(playerB, stoneGolem, 1); + assertExileCount(playerB, stoneGolem, 0); + assertGraveyardCount(playerA, glider, 1); + } + + @Test + public void testBargainNotPaidTorch() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, torchTheTower, 1); + addCard(Zone.HAND, playerA, lightningBolt, 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, glider); // Could be bargain. + + addCard(Zone.BATTLEFIELD, playerB, stoneGolem); + + setStrictChooseMode(true); + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, torchTheTower, stoneGolem); + setChoice(playerA, false); // Do not bargain. + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertDamageReceived(playerB, stoneGolem, 2); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, lightningBolt, stoneGolem); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, glider, 1); + assertPermanentCount(playerB, stoneGolem, 0); + assertExileCount(playerB, stoneGolem, 1); + } + + @Test + public void testBargainPaidTorch() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, torchTheTower, 1); + addCard(Zone.HAND, playerA, lightningBolt, 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, glider); // Could be bargain. + + addCard(Zone.BATTLEFIELD, playerB, stoneGolem); + + setStrictChooseMode(true); + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, torchTheTower, stoneGolem); + setChoice(playerA, true); // Do bargain. + setChoice(playerA, glider); // Bargain the glider away. + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertDamageReceived(playerB, stoneGolem, 3); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, lightningBolt, stoneGolem); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, glider, 0); + assertPermanentCount(playerB, stoneGolem, 0); + assertExileCount(playerB, stoneGolem, 1); + } +} diff --git a/Mage/src/main/java/mage/abilities/condition/common/BargainedCondition.java b/Mage/src/main/java/mage/abilities/condition/common/BargainedCondition.java new file mode 100644 index 00000000000..cda1156d377 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/BargainedCondition.java @@ -0,0 +1,38 @@ +package mage.abilities.condition.common; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.keyword.BargainAbility; +import mage.cards.Card; +import mage.game.Game; + +/** + * Checks if the spell was cast with the alternate Bargain cost + * + * @author Susucr + */ +public enum BargainedCondition implements Condition { + + instance; + + @Override + public boolean apply(Game game, Ability source) { + // TODO: replace by Tag Cost Tracking. + MageObject sourceObject = source.getSourceObject(game); + if (sourceObject instanceof Card) { + for (Ability ability : ((Card) sourceObject).getAbilities(game)) { + if (ability instanceof BargainAbility) { + return ((BargainAbility) ability).wasBargained(game, source); + } + } + } + return false; + } + + @Override + public String toString() { + return "{this} was Bargained"; + } + +} diff --git a/Mage/src/main/java/mage/abilities/hint/ConditionTrueHint.java b/Mage/src/main/java/mage/abilities/hint/ConditionTrueHint.java new file mode 100644 index 00000000000..66c114a8b72 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/hint/ConditionTrueHint.java @@ -0,0 +1,58 @@ +package mage.abilities.hint; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.game.Game; +import mage.util.CardUtil; + +import java.awt.*; + +/** + * Displays an hint only when the condition is true. + * + * @author Susucr + */ +public class ConditionTrueHint implements Hint { + + private Condition condition; + private String trueText; + private Color trueColor; + private Boolean useIcons; + + public ConditionTrueHint(Condition condition) { + this(condition, condition.toString()); + } + + public ConditionTrueHint(Condition condition, String textWithIcons) { + this(condition, textWithIcons, null, true); + } + + public ConditionTrueHint(Condition condition, String trueText, Color trueColor, Boolean useIcons) { + this.condition = condition; + this.trueText = CardUtil.getTextWithFirstCharUpperCase(trueText); + this.trueColor = trueColor; + this.useIcons = useIcons; + } + + protected ConditionTrueHint(final ConditionTrueHint hint) { + this.condition = hint.condition; + this.trueText = hint.trueText; + this.trueColor = hint.trueColor; + this.useIcons = hint.useIcons; + } + + @Override + public String getText(Game game, Ability ability) { + String icon; + if (condition.apply(game, ability)) { + icon = this.useIcons ? HintUtils.HINT_ICON_GOOD : null; + return HintUtils.prepareText(this.trueText, this.trueColor, icon); + } + return ""; + } + + @Override + public Hint copy() { + return new ConditionTrueHint(this); + } +} diff --git a/Mage/src/main/java/mage/abilities/hint/common/BargainCostWasPaidHint.java b/Mage/src/main/java/mage/abilities/hint/common/BargainCostWasPaidHint.java new file mode 100644 index 00000000000..462fa942dc3 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/hint/common/BargainCostWasPaidHint.java @@ -0,0 +1,27 @@ +package mage.abilities.hint.common; + +import mage.abilities.Ability; +import mage.abilities.condition.common.BargainedCondition; +import mage.abilities.hint.ConditionTrueHint; +import mage.abilities.hint.Hint; +import mage.game.Game; + +/** + * @author Susucr + */ +public enum BargainCostWasPaidHint implements Hint { + + instance; + private static final ConditionTrueHint hint = new ConditionTrueHint(BargainedCondition.instance, "bargained"); + + @Override + public String getText(Game game, Ability ability) { + return hint.getText(game, ability); + } + + @Override + public Hint copy() { + return instance; + } + +} diff --git a/Mage/src/main/java/mage/abilities/keyword/BargainAbility.java b/Mage/src/main/java/mage/abilities/keyword/BargainAbility.java new file mode 100644 index 00000000000..f2bc9074fc0 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/BargainAbility.java @@ -0,0 +1,149 @@ + +package mage.abilities.keyword; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.StaticAbility; +import mage.abilities.costs.*; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.hint.common.BargainCostWasPaidHint; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.util.CardUtil; + +/** + * Written before ruling was clarified. Feel free to put the ruling once it gets there. + *

+ * Bargain is a keyword static ability that adds an optional additional cost. + *

+ * Bargain means "You may sacrifice an artifact, enchantment, or token as you cast this spell". + *

+ * If a spell bargain cost is paid, the spell or the permanent it becomes is bargained. + * + * @author Susucr + */ +public class BargainAbility extends StaticAbility implements OptionalAdditionalSourceCosts { + + private static final FilterControlledPermanent bargainFilter = new FilterControlledPermanent("an artifact, enchantment, or token"); + private static final String promptString = "Bargain? (To Bargain, sacrifice an artifact, enchantment, or token)"; + private static final String keywordText = "Bargain"; + private static final String reminderText = "You may sacrifice an artifact, enchantment, or token as you cast this spell."; + private final String rule; + + private String activationKey; // TODO: replace by Tag Cost Tracking. + + protected OptionalAdditionalCost additionalCost; + + static { + bargainFilter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.ENCHANTMENT.getPredicate(), + TokenPredicate.TRUE + )); + } + + public BargainAbility() { + super(Zone.STACK, null); + this.additionalCost = new OptionalAdditionalCostImpl(keywordText, reminderText, new SacrificeTargetCost(bargainFilter)); + this.additionalCost.setRepeatable(false); + this.rule = additionalCost.getName() + additionalCost.getReminderText(); + this.setRuleAtTheTop(true); + this.addHint(BargainCostWasPaidHint.instance); + this.activationKey = null; + } + + private BargainAbility(final BargainAbility ability) { + super(ability); + this.rule = ability.rule; + this.additionalCost = ability.additionalCost.copy(); + this.activationKey = ability.activationKey; + } + + @Override + public BargainAbility copy() { + return new BargainAbility(this); + } + + public void resetBargain() { + if (additionalCost != null) { + additionalCost.reset(); + } + this.activationKey = null; + } + + @Override + public void addOptionalAdditionalCosts(Ability ability, Game game) { + if (!(ability instanceof SpellAbility)) { + return; + } + + Player player = game.getPlayer(ability.getControllerId()); + if (player == null) { + return; + } + + this.resetBargain(); + boolean canPay = additionalCost.canPay(ability, this, ability.getControllerId(), game); + if (!canPay || !player.chooseUse(Outcome.Sacrifice, promptString, ability, game)) { + return; + } + + additionalCost.activate(); + for (Cost cost : ((Costs) additionalCost)) { + ability.getCosts().add(cost.copy()); + } + this.activationKey = getActivationKey(ability, game); + } + + @Override + public String getCastMessageSuffix() { + return additionalCost.getCastSuffixMessage(0); + } + + public boolean wasBargained(Game game, Ability source) { + return activationKey != null && getActivationKey(source, game).equalsIgnoreCase(activationKey); + } + + + /** + * TODO: remove with Tag Cost Tracking. + * Return activation zcc key for searching spell's settings in source object + * + * @param source + * @param game + */ + public static String getActivationKey(Ability source, Game game) { + // Bargain activates in STACK zone so all zcc must be from "stack moment" + // Use cases: + // * resolving spell have same zcc (example: check kicker status in sorcery/instant); + // * copied spell have same zcc as source spell (see Spell.copySpell and zcc sync); + // * creature/token from resolved spell have +1 zcc after moved to battlefield (example: check kicker status in ETB triggers/effects); + + // find object info from the source ability (it can be a permanent or a spell on stack, on the moment of trigger/resolve) + MageObject sourceObject = source.getSourceObject(game); + Zone sourceObjectZone = game.getState().getZone(sourceObject.getId()); + int zcc = CardUtil.getActualSourceObjectZoneChangeCounter(game, source); + + // find "stack moment" zcc: + // * permanent cards enters from STACK to BATTLEFIELD (+1 zcc) + // * permanent tokens enters from OUTSIDE to BATTLEFIELD (+1 zcc, see prepare code in TokenImpl.putOntoBattlefieldHelper) + // * spells and copied spells resolves on STACK (zcc not changes) + if (sourceObjectZone != Zone.STACK) { + --zcc; + } + + return zcc + ""; + } + + @Override + public String getRule() { + return rule; + } +} \ No newline at end of file