From ce3cf742c99beab3f2d18c10dafb6bc7d37cc3a8 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sun, 11 Jun 2023 11:05:06 +0400 Subject: [PATCH] Blast-Furnace Hellkite - fixed game error on offering an artifact lands and treasures (#9940, #10218) --- .../abilities/keywords/OfferingTest.java | 82 +++++++++++-------- .../abilities/keyword/OfferingAbility.java | 8 +- 2 files changed, 50 insertions(+), 40 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/OfferingTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/OfferingTest.java index 2babfda05b0..abcadc83272 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/OfferingTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/OfferingTest.java @@ -9,10 +9,8 @@ public class OfferingTest extends CardTestPlayerBase { private static final String nezumiPatron = "Patron of the Nezumi"; - @Test - public void testOfferRatDecreaseCC() { - + public void test_OfferRatDecreaseCC() { String kurosTaken = "Kuro's Taken"; addCard(Zone.HAND, playerA, nezumiPatron, 1); @@ -22,6 +20,8 @@ public class OfferingTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, nezumiPatron); setChoice(playerA, true); addTarget(playerA, kurosTaken); + + setStrictChooseMode(true); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); @@ -31,7 +31,7 @@ public class OfferingTest extends CardTestPlayerBase { } @Test - public void testDontOfferRatNotDecreaseCC() { + public void test_DontOfferRatNotDecreaseCC() { String kurosTaken = "Kuro's Taken"; @@ -41,6 +41,8 @@ public class OfferingTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, nezumiPatron); setChoice(playerA, false); + + setStrictChooseMode(true); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); @@ -48,92 +50,100 @@ public class OfferingTest extends CardTestPlayerBase { assertPermanentCount(playerA, nezumiPatron, 1); assertTappedCount("Swamp", true, 7); // {5}{B}{B} - {1}{B} = {4}{B} = 7 swamps tapped } - + @Test - public void testCastWithMinimalMana() { - setStrictChooseMode(true); - + public void test_CastWithMinimalMana() { // Goblin offering (You may cast this card any time you could cast an instant by sacrificing a Goblin and paying the difference in mana costs between this and the sacrificed Goblin. Mana cost includes color.) // Whenever Patron of the Akki attacks, creatures you control get +2/+0 until end of turn. String patron = "Patron of the Akki"; // Creature {4}{R}{R} (5/5) addCard(Zone.HAND, playerA, patron, 1); - + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); addCard(Zone.BATTLEFIELD, playerA, "Akki Drillmaster"); // Creature Goblin {2}{R} (2/2) - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, patron); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, patron); setChoice(playerA, true); addTarget(playerA, "Akki Drillmaster"); - + + setStrictChooseMode(true); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); assertPermanentCount(playerA, patron, 1); - assertGraveyardCount(playerA, "Akki Drillmaster", 1); + } + - } - - @Test - public void testCastWithBorosRecruit() { - setStrictChooseMode(true); - + public void test_CastWithBorosRecruit() { // Goblin offering (You may cast this card any time you could cast an instant by sacrificing a Goblin and paying the difference in mana costs between this and the sacrificed Goblin. Mana cost includes color.) // Whenever Patron of the Akki attacks, creatures you control get +2/+0 until end of turn. String patron = "Patron of the Akki"; // Creature {4}{R}{R} (5/5) addCard(Zone.HAND, playerA, patron, 1); - + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); - + // First strike addCard(Zone.BATTLEFIELD, playerA, "Boros Recruit"); // Creature Goblin {R/W} (1/1) - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, patron); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, patron); setChoice(playerA, true); addTarget(playerA, "Boros Recruit"); - + + setStrictChooseMode(true); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); assertPermanentCount(playerA, patron, 1); - assertGraveyardCount(playerA, "Boros Recruit", 1); + } - } - @Test - public void testCastWithMultipleOptions() { - setStrictChooseMode(true); - + public void test_CastWithMultipleOptions() { // Goblin offering (You may cast this card any time you could cast an instant by sacrificing a Goblin and paying the difference in mana costs between this and the sacrificed Goblin. Mana cost includes color.) // Whenever Patron of the Akki attacks, creatures you control get +2/+0 until end of turn. String patron = "Patron of the Akki"; // Creature {4}{R}{R} (5/5) addCard(Zone.HAND, playerA, patron, 1); - + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); - + // First strike addCard(Zone.BATTLEFIELD, playerA, "Boros Recruit"); // Creature Goblin {R/W} (1/1) addCard(Zone.BATTLEFIELD, playerA, "Akki Drillmaster"); // Creature Goblin {2}{R} (2/2) addCard(Zone.BATTLEFIELD, playerA, "Boggart Ram-Gang"); // Creature Goblin Warrior {R/G}{R/G}{R/G} (3/3) - - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, patron); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, patron); setChoice(playerA, true); addTarget(playerA, "Boggart Ram-Gang"); - + + setStrictChooseMode(true); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); assertPermanentCount(playerA, patron, 1); - assertGraveyardCount(playerA, "Boggart Ram-Gang", 1); + } - } + @Test + public void test_SacrificeLand() { + // bug: sacrifce permanent without a spell ability can cause NPE error + // Artifact offering + addCard(Zone.HAND, playerA, "Blast-Furnace Hellkite", 1); // {7}{R}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 9); + addCard(Zone.BATTLEFIELD, playerA, "Ancient Den", 1); // Artifact Land + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Blast-Furnace Hellkite"); + setChoice(playerA, true); // use offering + addTarget(playerA, "Ancient Den"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Blast-Furnace Hellkite", 1); + } } diff --git a/Mage/src/main/java/mage/abilities/keyword/OfferingAbility.java b/Mage/src/main/java/mage/abilities/keyword/OfferingAbility.java index 0c38ace8867..99313032c7a 100644 --- a/Mage/src/main/java/mage/abilities/keyword/OfferingAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/OfferingAbility.java @@ -50,9 +50,6 @@ public class OfferingAbility extends StaticAbility implements AlternateManaPayme private final FilterControlledPermanent filter; - /** - * @param subtype name of the subtype that can be offered - */ public OfferingAbility(FilterControlledPermanent filter) { super(Zone.ALL, null); this.filter = filter; @@ -226,7 +223,10 @@ class OfferingCostReductionEffect extends CostModificationEffectImpl { Permanent toOffer = game.getPermanent(getTargetPointer().getFirst(game, source)); if (toOffer != null) { toOffer.sacrifice(source, game); - CardUtil.reduceCost((SpellAbility) abilityToModify, toOffer.getSpellAbility().getManaCosts()); + if (toOffer.getSpellAbility() != null) { + // artifact land don't have spell ability + CardUtil.reduceCost((SpellAbility) abilityToModify, toOffer.getSpellAbility().getManaCosts()); + } } game.getState().setValue("offering_" + source.getSourceId(), null); game.getState().setValue("offering_ok_" + source.getSourceId(), null);