Reworking effects which allow casting spells from a selection of cards (ready for review) (#8136)

* added function for casting spells with specific attributes from a selection of cards

* updated cascade to use new method

* refactored various cards to use new methods

* added TestPlayer method

* fixed a small error

* text fix

* broke out some repeated code

* added missing notTarget setting

* add additional retain zone check

* some more cards refactored

* more refactoring

* added interface for split/modal cards

* reworked spell casting methods

* reworked multiple cast to prevent unnecessary dialogs

* fixed test failures due to change in functionality

* add AI code

* small nonfunctional change

* reworked Kaya, the Inexorable

* added currently failing test

* added more tests

* updated Geode Golem implementation

* fixed adventure/cascade interaction, added/updated tests

* some nonfunctional refactoring

* added interface for subcards

* [AFC] Implemented Fevered Suspicion

* [AFC] Implemented Extract Brain

* [AFC] updated Arcane Endeavor implementation

* [C17] reworked implementation of Izzet Chemister

* [ZEN] reworked implemented of Chandra Ablaze

* additional merge fix

* [SLD] updated Eleven, the Mage

* [NEO] Implemented Discover the Impossible

* [NEO] Implemented The Dragon-Kami Reborn / Dragon-Kami's Egg

* [NEO] Implemented Invoke Calamity

* [AFR] Implemented Rod of Absorption

* [VOC] Implemented Spectral Arcanist

* [VOC] added additional printings

* [NEO] added all variants

* [SLD] updated implementation of Ken, Burning Brawler
This commit is contained in:
Evan Kranzler 2022-03-09 08:03:54 -05:00 committed by GitHub
parent 7fb089db48
commit bbb9382150
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
83 changed files with 2551 additions and 2059 deletions

View file

@ -143,7 +143,7 @@ public class BuybackTest extends CardTestPlayerBase {
// bolt 2 - cast (R) and copy as free cast (R), return reiterate with buyback (RRR)
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}, {T}:");
setChoice(playerA, "Reiterate"); // free cast
setChoice(playerA, true); // cast for free
setChoice(playerA, true); // use buyback
addTarget(playerA, "Lightning Bolt"); // copy target
setChoice(playerA, false); // same bolt's target

View file

@ -670,7 +670,6 @@ public class KickerTest extends CardTestPlayerBase {
// attack and prepare free cast, use kicker
attack(1, playerA, "Etali, Primal Storm", playerB);
setChoice(playerA, true); // cast for free
setChoice(playerA, "Ardent Soldier"); // cast for free
setChoice(playerA, true); // use kicker
setStrictChooseMode(true);
@ -703,7 +702,6 @@ public class KickerTest extends CardTestPlayerBase {
// attack and prepare free cast
attack(1, playerA, "Etali, Primal Storm", playerB);
setChoice(playerA, true); // cast for free
setChoice(playerA, "Thieving Skydiver"); // cast for free
setChoice(playerA, true); // use kicker
setChoiceAmount(playerA, 2); // X=2 for Kicker X
addTarget(playerA, "Brain in a Jar"); // kicker's target (take control of artifact)

View file

@ -766,4 +766,122 @@ public class AdventureCardsTest extends CardTestPlayerBase {
execute();
assertAllCommandsUsed();
}
@Test
public void test_Cascade_CuriousPair() {
// If a player cascades into Curious Pair with Bloodbraid Elf they can cast either spell
removeAllCardsFromLibrary(playerA);
skipInitShuffling();
// Cascade
addCard(Zone.HAND, playerA, "Bloodbraid Elf"); // {2}{R}{G}
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
//
addCard(Zone.LIBRARY, playerA, "Swamp", 2);
addCard(Zone.LIBRARY, playerA, "Curious Pair", 1);
addCard(Zone.LIBRARY, playerA, "Island", 2);
// play elf with cascade
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bloodbraid Elf");
setChoice(playerA, true); // use free cast
setChoice(playerA, "Cast Treats to Share"); // can cast either
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, "Curious Pair", 0);
assertPermanentCount(playerA, "Food", 1);
assertExileCount(playerA, "Curious Pair", 1);
}
@Test
public void test_Cascade_FlaxenIntruder() {
// If a player cascades into Flaxen Intruder with Bloodbraid Elf they shouldn't be able to cast Welcome Home
removeAllCardsFromLibrary(playerA);
skipInitShuffling();
// Cascade
addCard(Zone.HAND, playerA, "Bloodbraid Elf"); // {2}{R}{G}
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
//
addCard(Zone.LIBRARY, playerA, "Swamp", 2);
addCard(Zone.LIBRARY, playerA, "Flaxen Intruder", 1);
addCard(Zone.LIBRARY, playerA, "Island", 2);
// play elf with cascade
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bloodbraid Elf");
setChoice(playerA, true); // use free cast
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, "Flaxen Intruder", 1);
assertPermanentCount(playerA, "Bear", 0);
}
@Test
public void test_SramsExpertise_CuriousPair() {
addCard(Zone.HAND, playerA, "Sram's Expertise");
addCard(Zone.HAND, playerA, "Curious Pair");
addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sram's Expertise");
setChoice(playerA, true); // use free cast
setChoice(playerA, "Cast Treats to Share");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, "Curious Pair", 0);
assertPermanentCount(playerA, "Food", 1);
assertPermanentCount(playerA, "Servo", 3);
assertExileCount(playerA, "Curious Pair", 1);
}
@Test
public void test_SramsExpertise_FlaxenIntruder() {
addCard(Zone.HAND, playerA, "Sram's Expertise");
addCard(Zone.HAND, playerA, "Flaxen Intruder");
addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sram's Expertise");
setChoice(playerA, true); // use free cast
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, "Flaxen Intruder", 1);
assertPermanentCount(playerA, "Bear", 0);
assertPermanentCount(playerA, "Servo", 3);
}
@Test
public void test_SramsExpertise_LonesomeUnicorn() {
addCard(Zone.HAND, playerA, "Sram's Expertise");
addCard(Zone.HAND, playerA, "Lonesome Unicorn");
addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sram's Expertise");
setChoice(playerA, true); // use free cast
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, "Lonesome Unicorn", 0);
assertPermanentCount(playerA, "Knight", 1);
assertPermanentCount(playerA, "Servo", 3);
assertExileCount(playerA, "Lonesome Unicorn", 1);
}
}

View file

@ -866,6 +866,44 @@ public class ModalDoubleFacesCardsTest extends CardTestPlayerBase {
assertPermanentCount(playerA, "The Omenkeel", 1);
}
@Test
public void test_SramsExpertise_ValkiGodOfLies() {
addCard(Zone.HAND, playerA, "Sram's Expertise");
addCard(Zone.HAND, playerA, "Valki, God of Lies");
addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sram's Expertise");
setChoice(playerA, true); // use free cast
setChoice(playerA, TestPlayer.CHOICE_SKIP); // no choices for valki's etb exile
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, "Valki, God of Lies", 1);
assertPermanentCount(playerA, "Servo", 3);
}
@Test
public void test_SramsExpertise_CosimaGodOfTheVoyage() {
addCard(Zone.HAND, playerA, "Sram's Expertise");
addCard(Zone.HAND, playerA, "Cosima, God of the Voyage");
addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sram's Expertise");
setChoice(playerA, true); // use free cast
setChoice(playerA, "Cast The Omenkeel"); // can cast any side here
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, "The Omenkeel", 1);
assertPermanentCount(playerA, "Servo", 3);
}
@Test
public void test_Copy_AsSpell() {
addCard(Zone.HAND, playerA, "Akoum Warrior", 1); // {5}{R}
@ -1000,4 +1038,4 @@ public class ModalDoubleFacesCardsTest extends CardTestPlayerBase {
execute();
assertAllCommandsUsed();
}
}
}

View file

@ -34,7 +34,6 @@ public class CastSplitCardsFromOtherZonesTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mindclaw Shaman");
addTarget(playerA, playerB);
setChoice(playerA, "Wear // Tear"); // select card
setChoice(playerA, true); // confirm to cast
setChoice(playerA, "Cast Tear"); // select tear side
addTarget(playerA, "Sanguine Bond"); // target for tear
@ -67,7 +66,6 @@ public class CastSplitCardsFromOtherZonesTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mindclaw Shaman");
addTarget(playerA, playerB);
setChoice(playerA, "Wear // Tear"); // select card
setChoice(playerA, true); // confirm to cast
setChoice(playerA, "Cast Wear"); // select wear side
addTarget(playerA, "Icy Manipulator"); // target for wear
@ -100,7 +98,6 @@ public class CastSplitCardsFromOtherZonesTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mindclaw Shaman");
addTarget(playerA, playerB);
setChoice(playerA, "Wear // Tear"); // select card
setChoice(playerA, true); // confirm to cast
setChoice(playerA, "Cast fused Wear // Tear"); // select fused
addTarget(playerA, "Icy Manipulator"); // target for wear
@ -137,7 +134,6 @@ public class CastSplitCardsFromOtherZonesTest extends CardTestPlayerBase {
attack(2, playerB, "Etali, Primal Storm");
setChoice(playerB, true); // free cast
setChoice(playerB, "Fire // Ice"); // card to cast
setChoice(playerB, "Cast Fire"); // ability to cast
addTargetAmount(playerB, "Silvercoat Lion", 2);

View file

@ -4364,19 +4364,17 @@ public class TestPlayer implements Player {
@Override
public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) {
assertAliasSupportInChoices(false);
MageObject object = game.getObject(card.getId()); // must be object to find real abilities (example: commander)
Map<UUID, ActivatedAbility> useable = PlayerImpl.getCastableSpellAbilities(game, this.getId(), object, game.getState().getZone(object.getId()), noMana);
String allInfo = useable.values().stream().map(Object::toString).collect(Collectors.joining("\n"));
Map<UUID, SpellAbility> useable = PlayerImpl.getCastableSpellAbilities(game, this.getId(), object, game.getState().getZone(object.getId()), noMana);
if (useable.size() == 1) {
return (SpellAbility) useable.values().iterator().next();
return useable.values().iterator().next();
}
if (!choices.isEmpty()) {
for (ActivatedAbility ability : useable.values()) {
for (SpellAbility ability : useable.values()) {
if (ability.toString().startsWith(choices.get(0))) {
choices.remove(0);
return (SpellAbility) ability;
return ability;
}
}
@ -4384,6 +4382,7 @@ public class TestPlayer implements Player {
//Assert.fail("Wrong choice");
}
String allInfo = useable.values().stream().map(Object::toString).collect(Collectors.joining("\n"));
this.chooseStrictModeFailed("choice", game, getInfo(card) + " - can't select ability to cast.\n" + "Card's abilities:\n" + allInfo);
return computerPlayer.chooseAbilityForCast(card, game, noMana);
}