diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerTest.java index 41b0af1036..7d51a71f6c 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerTest.java @@ -278,6 +278,39 @@ public class KickerTest extends CardTestPlayerBase { assertGraveyardCount(playerA, "Sunscape Battlemage", 1); } + @Test + public void test_Conditional_TriggeredAbilityMustSeeMultikickedStatus() { + // bug: + // Hallar Not Procing Right: When I kick Thornscape Battlemage it doesn't proc Hallar effect for some reason. + // I tried this 3 times and it never triggered properly. + + // Kicker {R} and/or {W} (You may pay an additional {R} and/or {W} as you cast this spell.) + // When Thornscape Battlemage enters the battlefield, if it was kicked with its {R} kicker, it deals 2 damage to any target. + // When Thornscape Battlemage enters the battlefield, if it was kicked with its {W} kicker, destroy target artifact. + addCard(Zone.HAND, playerA, "Thornscape Battlemage", 1); // {2}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + // + // Whenever you cast a spell, if that spell was kicked, put a +1/+1 counter on Hallar, the Firefletcher, + // then Hallar deals damage equal to the number of +1/+1 counters on it to each opponent. + addCard(Zone.BATTLEFIELD, playerA, "Hallar, the Firefletcher", 1); + + // cast kicked spell + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Thornscape Battlemage"); + setChoice(playerA, "Yes"); // use kicker {R} - 2 damage to any target + setChoice(playerA, "No"); // not use kicker {W} - destroy target + addTarget(playerA, playerB); // target for 2 damage + setChoice(playerA, "Yes"); // put counter on hallar + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertCounterCount(playerA, "Hallar, the Firefletcher", CounterType.P1P1, 1); + assertLife(playerB, 20 - 2 - 1); // 2 damage from kicked spell, 1 damage from hallar's trigger + } + @Test public void test_ZCC_ReturnedPermanentMustNotBeKicked() { // bug: diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 8ade40e88c..789d409597 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -1869,6 +1869,20 @@ public class TestPlayer implements Player { printAbilities(game, computerPlayer.getPlayable(game, true)); printEnd(); } + if (choiceType.equals("choice")) { + printStart("Unused choices"); + if (!choices.isEmpty()) { + System.out.println(String.join("\n", choices)); + } + printEnd(); + } + if (choiceType.equals("target")) { + printStart("Unused targets"); + if (targets.isEmpty()) { + System.out.println(String.join("\n", targets)); + } + printEnd(); + } Assert.fail("Missing " + choiceType.toUpperCase(Locale.ENGLISH) + " def for" + " turn " + game.getTurnNum() + ", step " + (game.getStep() != null ? game.getStep().getType().name() : "not started") diff --git a/Mage/src/main/java/mage/abilities/condition/common/KickedCondition.java b/Mage/src/main/java/mage/abilities/condition/common/KickedCondition.java index 1c9d5681d3..2213538d4f 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/KickedCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/KickedCondition.java @@ -27,7 +27,7 @@ public enum KickedCondition implements Condition { if (card != null) { for (Ability ability : card.getAbilities()) { if (ability instanceof KickerAbility) { - if (((KickerAbility) ability).isKicked(game, source, "")) { + if (((KickerAbility) ability).isKicked(game, source)) { return true; } } diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/GetKickerXValue.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/GetKickerXValue.java index 79a01cf6d1..dfac960ff9 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/GetKickerXValue.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/GetKickerXValue.java @@ -36,7 +36,6 @@ public enum GetKickerXValue implements DynamicValue { .stream() .anyMatch(varCost -> !((OptionalAdditionalCostImpl) varCost).getVariableCosts().isEmpty()); - if (haveVarCost) { int kickedCount = ((KickerAbility) ability).getKickedCounter(game, sourceAbility); if (kickedCount > 0) { diff --git a/Mage/src/main/java/mage/abilities/keyword/KickerAbility.java b/Mage/src/main/java/mage/abilities/keyword/KickerAbility.java index 3cc6fbcbf6..0d7468fe56 100644 --- a/Mage/src/main/java/mage/abilities/keyword/KickerAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/KickerAbility.java @@ -122,27 +122,63 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo } } - public int getKickedCounter(Game game, Ability source) { - String key = getActivationKey(source, "", game); - return activations.getOrDefault(key, 0); - } + private int getKickedCounterStrict(Game game, Ability source, String needKickerCost) { + String key; + if (needKickerCost.isEmpty()) { + // need all kickers + key = getActivationKey(source, "", game); + } else { + // need only cost related kickers + key = getActivationKey(source, needKickerCost, game); + } - public boolean isKicked(Game game, Ability source, String costText) { - String key = getActivationKey(source, costText, game); + int totalActivations = 0; if (kickerCosts.size() > 1) { for (String activationKey : activations.keySet()) { - if (activationKey.startsWith(key) - && activations.get(activationKey) > 0) { - return true; + if (activationKey.startsWith(key) && activations.get(activationKey) > 0) { + totalActivations += activations.get(activationKey); } } } else { - if (activations.containsKey(key)) { - return activations.get(key) > 0; - + if (activations.containsKey(key) && activations.get(key) > 0) { + totalActivations += activations.get(key); } } - return false; + return totalActivations; + } + + /** + * Return total kicker activations (kicker + multikicker) + * + * @param game + * @param source + * @return + */ + public int getKickedCounter(Game game, Ability source) { + return getKickedCounterStrict(game, source, ""); + } + + /** + * If spell was kicked + * + * @param game + * @param source + * @return + */ + public boolean isKicked(Game game, Ability source) { + return isKicked(game, source, ""); + } + + /** + * If spell was kicked by specific kicker cost + * + * @param game + * @param source + * @param needKickerCost use cost.getText(true) + * @return + */ + public boolean isKicked(Game game, Ability source, String needKickerCost) { + return getKickedCounterStrict(game, source, needKickerCost) > 0; } public List getKickerCosts() { @@ -196,7 +232,7 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo while (player.canRespond() && again) { String times = ""; if (kickerCost.isRepeatable()) { - int activatedCount = getKickedCounter(game, ability); + int activatedCount = getKickedCounterStrict(game, ability, kickerCost.getText(true)); times = (activatedCount + 1) + (activatedCount == 0 ? " time " : " times "); } // TODO: add AI support to find max number of possible activations (from available mana) diff --git a/Mage/src/main/java/mage/abilities/keyword/KickerWithAnyNumberModesAbility.java b/Mage/src/main/java/mage/abilities/keyword/KickerWithAnyNumberModesAbility.java index 0cb9099b90..c9c58fb208 100644 --- a/Mage/src/main/java/mage/abilities/keyword/KickerWithAnyNumberModesAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/KickerWithAnyNumberModesAbility.java @@ -26,7 +26,7 @@ public class KickerWithAnyNumberModesAbility extends KickerAbility implements Op @Override public void changeModes(Ability ability, Game game) { - if (!isKicked(game, ability, "")) { + if (!isKicked(game, ability)) { return; }