From 53a2021a1277f9dc6dd4f34d4478750d58b91a1e Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sun, 7 Mar 2021 23:41:52 +0400 Subject: [PATCH] * Caller of the Hunt - fixed rollback error on usage, added AI support for the card; --- .../src/mage/cards/c/CallerOfTheHunt.java | 92 ++++++++++++++----- .../cards/single/mmq/CallerOfTheHuntTest.java | 60 ++++++++++++ 2 files changed, 129 insertions(+), 23 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/mmq/CallerOfTheHuntTest.java diff --git a/Mage.Sets/src/mage/cards/c/CallerOfTheHunt.java b/Mage.Sets/src/mage/cards/c/CallerOfTheHunt.java index 7789349340..6a10b85f2b 100644 --- a/Mage.Sets/src/mage/cards/c/CallerOfTheHunt.java +++ b/Mage.Sets/src/mage/cards/c/CallerOfTheHunt.java @@ -4,26 +4,30 @@ import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.CostAdjuster; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.InfoEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.continuous.SetPowerToughnessSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.choices.Choice; import mage.choices.ChoiceCreatureType; import mage.constants.*; +import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.players.Player; -import java.util.UUID; -import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; -import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; -import mage.abilities.effects.common.continuous.SetPowerToughnessSourceEffect; -import mage.filter.common.FilterCreaturePermanent; import mage.target.targetpointer.FixedTarget; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + /** - * @author jeffwadsworth + * @author jeffwadsworth, JayDi85 */ public final class CallerOfTheHunt extends CardImpl { @@ -56,25 +60,67 @@ enum CallerOfTheHuntAdjuster implements CostAdjuster { @Override public void adjustCosts(Ability ability, Game game) { - MageObject mageObject = game.getObject(ability.getSourceId()); - Effect effect = new ChooseCreatureTypeEffect(Outcome.Benefit); - if (mageObject != null) { - effect.apply(game, ability); + if (game.inCheckPlayableState()) { + return; } - if (mageObject != null) { - SubType typeChoice = (SubType) game.getState().getValue(mageObject.getId() + "_type"); - if (typeChoice != null) { - FilterCreaturePermanent filter = new FilterCreaturePermanent("chosen creature type"); - filter.add(typeChoice.getPredicate()); - ContinuousEffect effectPowerToughness = new SetPowerToughnessSourceEffect( - new PermanentsOnBattlefieldCount(filter), Duration.EndOfGame); - effectPowerToughness.setText(""); - SimpleStaticAbility setPT = new SimpleStaticAbility(Zone.ALL, effectPowerToughness); - GainAbilityTargetEffect gainAbility = new GainAbilityTargetEffect(setPT, Duration.EndOfGame); - gainAbility.setTargetPointer(new FixedTarget(ability.getSourceId())); - game.getState().addEffect(gainAbility, ability); + + Player controller = game.getPlayer(ability.getControllerId()); + if (controller == null) { + return; + } + + MageObject sourceObject = game.getObject(ability.getSourceId()); + if (sourceObject == null) { + return; + } + + // AI hint - find best creature type with max permanents, all creature type supports too + Map usedSubTypeStats = new HashMap<>(); + game.getBattlefield().getActivePermanents(ability.getControllerId(), game) + .stream() + .map(permanent -> permanent.getSubtype(game)) + .flatMap(Collection::stream) + .distinct() + .forEach(subType -> { + FilterCreaturePermanent filter = new FilterCreaturePermanent(); + filter.add(subType.getPredicate()); + int amount = new PermanentsOnBattlefieldCount(filter).calculate(game, ability, null); + usedSubTypeStats.put(subType, amount); + }); + int maxAmount = 0; + SubType maxSubType = null; + for (Map.Entry entry : usedSubTypeStats.entrySet()) { + if (entry.getValue() > maxAmount) { + maxSubType = entry.getKey(); + maxAmount = entry.getValue(); } } + + // choose creature type + SubType typeChoice; + if (controller.isComputer()) { + // AI hint - simulate type choose + game.getState().setValue(sourceObject.getId() + "_type", maxSubType); + } else { + // human choose + Effect effect = new ChooseCreatureTypeEffect(Outcome.Benefit); + effect.apply(game, ability); + } + typeChoice = (SubType) game.getState().getValue(sourceObject.getId() + "_type"); + if (typeChoice == null) { + return; + } + + // apply boost + FilterCreaturePermanent filter = new FilterCreaturePermanent("chosen creature type"); + filter.add(typeChoice.getPredicate()); + ContinuousEffect effectPowerToughness = new SetPowerToughnessSourceEffect( + new PermanentsOnBattlefieldCount(filter), Duration.EndOfGame); + effectPowerToughness.setText(""); + SimpleStaticAbility setPT = new SimpleStaticAbility(Zone.ALL, effectPowerToughness); + GainAbilityTargetEffect gainAbility = new GainAbilityTargetEffect(setPT, Duration.EndOfGame); + gainAbility.setTargetPointer(new FixedTarget(ability.getSourceId())); + game.getState().addEffect(gainAbility, ability); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mmq/CallerOfTheHuntTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mmq/CallerOfTheHuntTest.java new file mode 100644 index 0000000000..acf689217e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mmq/CallerOfTheHuntTest.java @@ -0,0 +1,60 @@ +package org.mage.test.cards.single.mmq; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; + +/** + * @author JayDi85 + */ + +public class CallerOfTheHuntTest extends CardTestPlayerBaseWithAIHelps { + + @Test + public void test_Play_Manual() { + // As an additional cost to cast Caller of the Hunt, choose a creature type. + // Caller of the Hunt's power and toughness are each equal to the number of creatures of the chosen type on the battlefield. + addCard(Zone.HAND, playerA, "Caller of the Hunt"); // {2}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + // + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1); + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears", 2); + addCard(Zone.BATTLEFIELD, playerB, "Goblin Archaeologist", 2); + + // cast Caller of the Hunt and choose bear as a type (+3 boost) + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Caller of the Hunt"); + setChoice(playerA, "Bear"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Caller of the Hunt", 1); + assertPowerToughness(playerA, "Caller of the Hunt", 3, 3); // +3 boost + } + + @Test + public void test_Play_AI() { + // As an additional cost to cast Caller of the Hunt, choose a creature type. + // Caller of the Hunt's power and toughness are each equal to the number of creatures of the chosen type on the battlefield. + addCard(Zone.HAND, playerA, "Caller of the Hunt"); // {2}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + // + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1); + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears", 2); + addCard(Zone.BATTLEFIELD, playerB, "Goblin Archaeologist", 2); + + // ai must cast Caller of the Hunt and choose bear as a type (+3 boost) + aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Caller of the Hunt", 1); + assertPowerToughness(playerA, "Caller of the Hunt", 3, 3); // +3 boost + } +} \ No newline at end of file