From fb1e284960d3f3b5b056025507e018901b2f43d1 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Fri, 19 Jun 2020 13:20:32 +0400 Subject: [PATCH] Reworked Assist ability (#768) --- .../cards/abilities/keywords/AssistTest.java | 138 ++++++++++++++++++ .../mage/abilities/keyword/AssistAbility.java | 97 ++++++++++-- 2 files changed, 221 insertions(+), 14 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/AssistTest.java diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/AssistTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/AssistTest.java new file mode 100644 index 00000000000..7210f12e694 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/AssistTest.java @@ -0,0 +1,138 @@ +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.CardTestPlayerBaseWithAIHelps; + +/** + * @author JayDi85 + */ + +public class AssistTest extends CardTestPlayerBaseWithAIHelps { + + @Test + public void test_Playable_NoMana_NoAssist() { + // {7}{G} + // Assist (Another player can pay up to {7} of this spell's cost.) + addCard(Zone.HAND, playerA, "Charging Binox", 1); + + checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Charging Binox", false); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_Playable_Mana_NoAssist() { + // {7}{G} + // Assist (Another player can pay up to {7} of this spell's cost.) + addCard(Zone.HAND, playerA, "Charging Binox", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 8); + + checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Charging Binox", true); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_Playable_NoMana_Assist() { + // {7}{G} + // Assist (Another player can pay up to {7} of this spell's cost.) + addCard(Zone.HAND, playerA, "Charging Binox", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 6); + addCard(Zone.BATTLEFIELD, playerB, "Forest", 2); // assist pay + + checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Charging Binox", true); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_Playable_Mana_Assist() { + // {7}{G} + // Assist (Another player can pay up to {7} of this spell's cost.) + addCard(Zone.HAND, playerA, "Charging Binox", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 8); + addCard(Zone.BATTLEFIELD, playerB, "Forest", 2); // assist pay + + checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Charging Binox", true); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_Playable_ManaPartly_AssistPartly() { + // {7}{G} + // Assist (Another player can pay up to {7} of this spell's cost.) + addCard(Zone.HAND, playerA, "Charging Binox", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + addCard(Zone.BATTLEFIELD, playerB, "Forest", 2); // assist pay + + checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Charging Binox", false); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_PlayAssist_Manual() { + // {7}{G} + // Assist (Another player can pay up to {7} of this spell's cost.) + addCard(Zone.HAND, playerA, "Charging Binox", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 8 - 2); + addCard(Zone.BATTLEFIELD, playerB, "Forest", 2); // assist pay + + // disabled auto-payment and prepare mana pool to control payment + disableManaAutoPayment(playerA); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 6); + // cast and assist + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Charging Binox"); + setChoice(playerA, "Green", 6); // normal pay x6 + setChoice(playerA, "Assist"); // activate assist + addTarget(playerA, playerB); // player to assist + setChoice(playerB, "X=2"); // can pay (auto-pay from B to A's mana pool as colorless x2) + setChoice(playerA, "Colorless", 2 - 1); // assist pay x2, but 1 mana was unlocked in assist code (wtf) + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Charging Binox", 1); + } + + @Test + public void test_PlayAssist_AI_MustIgnoreAssist() { + // {7}{G} + // Assist (Another player can pay up to {7} of this spell's cost.) + addCard(Zone.HAND, playerA, "Charging Binox", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 8 - 2); + addCard(Zone.BATTLEFIELD, playerB, "Forest", 2); // assist pay + + // AI must ignore assist + checkPlayableAbility("playable", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Charging Binox", true); + aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertHandCount(playerA, "Charging Binox", 1); + assertTappedCount("Forest", false, 8); // no mana used + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/keyword/AssistAbility.java b/Mage/src/main/java/mage/abilities/keyword/AssistAbility.java index 7da5d3a4476..ae4e8371948 100644 --- a/Mage/src/main/java/mage/abilities/keyword/AssistAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/AssistAbility.java @@ -5,21 +5,37 @@ import mage.abilities.Ability; import mage.abilities.SpecialAction; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.Cost; +import mage.abilities.costs.mana.ActivationManaAbilityStep; import mage.abilities.costs.mana.AlternateManaPaymentAbility; import mage.abilities.costs.mana.ManaCost; import mage.abilities.effects.OneShotEffect; +import mage.abilities.mana.ManaOptions; import mage.constants.*; import mage.filter.FilterPlayer; import mage.game.Game; +import mage.game.stack.Spell; import mage.players.ManaPool; import mage.players.Player; import mage.target.Target; import mage.target.TargetPlayer; import mage.util.ManaUtil; -/* - * @author emerald000 +import java.util.UUID; + +/** + * 702.131. Assist + *

+ * 702.131a Assist is a static ability that modifies the rules of paying for the spell with assist (see rules 601.2g-h). + * If the total cost to cast a spell with assist includes a generic mana component, before you activate mana + * abilities while casting it, you may choose another player. That player has a chance to activate mana abilities. + * Once that player chooses not to activate any more mana abilities, you have a chance to activate mana abilities. + * Before you begin to pay the total cost of the spell, the player you chose may pay for any amount of the generic + * mana in the spell’s total cost. + * + * @author emerald000, JayDi85 */ + + public class AssistAbility extends SimpleStaticAbility implements AlternateManaPaymentAbility { private static final FilterPlayer filter = new FilterPlayer("another player"); @@ -29,7 +45,7 @@ public class AssistAbility extends SimpleStaticAbility implements AlternateManaP } public AssistAbility() { - super(Zone.STACK, null); + super(Zone.ALL, null); this.setRuleAtTheTop(true); } @@ -42,14 +58,24 @@ public class AssistAbility extends SimpleStaticAbility implements AlternateManaP return new AssistAbility(this); } + @Override + public String getRule() { + return "Assist (Another player can help pay the generic mana of this spell's cost.)"; + } + + @Override + public ActivationManaAbilityStep useOnActivationManaAbilityStep() { + return ActivationManaAbilityStep.BEFORE; + } + @Override public void addSpecialAction(Ability source, Game game, ManaCost unpaid) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null && source.getAbilityType() == AbilityType.SPELL && unpaid.getMana().getGeneric() >= 1 - && game.getState().getValue(source.getSourceId().toString() + game.getState().getZoneChangeCounter(source.getSourceId())) == null) { - SpecialAction specialAction = new AssistSpecialAction(unpaid); + && game.getState().getValue(source.getSourceId().toString() + game.getState().getZoneChangeCounter(source.getSourceId()) + "_assisted") == null) { + SpecialAction specialAction = new AssistSpecialAction(unpaid, this); specialAction.setControllerId(source.getControllerId()); specialAction.setSourceId(source.getSourceId()); Target target = new TargetPlayer(1, 1, true, filter); @@ -61,15 +87,47 @@ public class AssistAbility extends SimpleStaticAbility implements AlternateManaP } @Override - public String getRule() { - return "Assist (Another player can help pay the generic mana of this spell's cost.)"; + public ManaOptions getManaOptions(Ability source, Game game, ManaCost unpaid) { + ManaOptions options = new ManaOptions(); + if (unpaid.getMana().getGeneric() == 0) { + // nothing to pay + return options; + } + + // AI can't use assist (can't ask another player to help), maybe in teammode it can be enabled, but tests must works all the time + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null && !controller.isTestMode() && !controller.isHuman()) { + return options; + } + + // search opponents who can help with generic pay + int opponentCanPayMax = 0; + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + Player opponent = game.getPlayer(opponentId); + if (opponent != null) { + // basic and pool, but no coditional mana + ManaOptions availableMana = opponent.getManaAvailable(game); + availableMana.addMana(opponent.getManaPool().getMana()); + for (Mana mana : availableMana) { + if (mana.count() > 0) { + opponentCanPayMax = Math.max(opponentCanPayMax, mana.count()); + } + } + } + } + + if (opponentCanPayMax > 0) { + options.addMana(Mana.GenericMana(Math.min(unpaid.getMana().getGeneric(), opponentCanPayMax))); + } + + return options; } } class AssistSpecialAction extends SpecialAction { - AssistSpecialAction(ManaCost unpaid) { - super(Zone.ALL, true); + AssistSpecialAction(ManaCost unpaid, AlternateManaPaymentAbility manaAbility) { + super(Zone.ALL, manaAbility); setRuleVisible(false); this.addEffect(new AssistEffect(unpaid)); } @@ -108,17 +166,28 @@ class AssistEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); Player targetPlayer = game.getPlayer(this.getTargetPointer().getFirst(game, source)); - if (controller != null && targetPlayer != null) { - int amountToPay = targetPlayer.announceXMana(0, unpaid.getMana().getGeneric(), "How much mana to pay?", game, source); + Spell spell = game.getStack().getSpell(source.getSourceId()); + if (controller != null && spell != null && targetPlayer != null) { + // AI can't assist other players, maybe for teammates only (but tests must work as normal) + int amountToPay = 0; + if (targetPlayer.isHuman() || targetPlayer.isTestMode()) { + amountToPay = targetPlayer.announceXMana(0, unpaid.getMana().getGeneric(), + "How much mana to pay as assist for " + controller.getName() + "?", game, source); + } + if (amountToPay > 0) { Cost cost = ManaUtil.createManaCost(amountToPay, false); if (cost.pay(source, game, source.getSourceId(), targetPlayer.getId(), false)) { ManaPool manaPool = controller.getManaPool(); manaPool.addMana(Mana.ColorlessMana(amountToPay), game, source); - manaPool.unlockManaType(ManaType.COLORLESS); - game.informPlayers(targetPlayer.getLogName() + " paid {" + amountToPay + "}."); - game.getState().setValue(source.getSourceId().toString() + game.getState().getZoneChangeCounter(source.getSourceId()), true); + manaPool.unlockManaType(ManaType.COLORLESS); // it's unlock mana for one use/click, but it can gives more + game.informPlayers(targetPlayer.getLogName() + " paid {" + amountToPay + "} for " + controller.getLogName()); + game.getState().setValue(source.getSourceId().toString() + game.getState().getZoneChangeCounter(source.getSourceId()) + "_assisted", true); } + + // assist must be used before activating mana abilities, so no need to switch step after usage + // (mana and other special abilities can be used after assist) + //spell.setCurrentActivatingManaAbilitiesStep(ActivationManaAbilityStep.NORMAL); } return true; }