Implement [BLB] Alania, Divergent Storm (#12601)

This commit is contained in:
jimga150 2024-08-14 21:12:28 -04:00 committed by GitHub
parent 3321cd03bc
commit 9a872b4446
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 446 additions and 0 deletions

View file

@ -0,0 +1,204 @@
package mage.cards.a;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import mage.MageInt;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.common.SpellCastControllerTriggeredAbility;
import mage.abilities.condition.Condition;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.common.CopyTargetStackObjectEffect;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.constants.*;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.Spell;
import mage.players.Player;
import mage.target.common.TargetOpponent;
import mage.watchers.Watcher;
/**
*
* @author jimga150
*/
public final class AlaniaDivergentStorm extends CardImpl {
public AlaniaDivergentStorm(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{R}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.OTTER);
this.subtype.add(SubType.WIZARD);
this.power = new MageInt(3);
this.toughness = new MageInt(5);
// Whenever you cast a spell, if it's the first instant spell, the first sorcery spell, or the first Otter
// spell other than Alania you've cast this turn, you may have target opponent draw a card. If you do, copy
// that spell. You may choose new targets for the copy.
Ability ability = new ConditionalInterveningIfTriggeredAbility(
new SpellCastControllerTriggeredAbility(new DoIfCostPaid(
new CopyTargetStackObjectEffect(true),
new AlaniaDivergentStormCost()
), null, false, SetTargetPointer.SPELL)
.setTriggerPhrase("Whenever you cast a spell, if it's the first instant spell, the first sorcery " +
"spell, or the first Otter spell other than Alania you've cast this turn, "),
AlaniaDivergentStormCondition.instance, ""
);
ability.addWatcher(new AlaniaDivergentStormWatcher());
this.addAbility(ability);
}
private AlaniaDivergentStorm(final AlaniaDivergentStorm card) {
super(card);
}
@Override
public AlaniaDivergentStorm copy() {
return new AlaniaDivergentStorm(this);
}
}
// Based on MarathWillOfTheWildRemoveCountersCost
class AlaniaDivergentStormCost extends CostImpl {
AlaniaDivergentStormCost() {
this.text = "have target opponent draw a card";
this.addTarget(new TargetOpponent());
}
private AlaniaDivergentStormCost(AlaniaDivergentStormCost cost) {
super(cost);
}
@Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
Player player = game.getPlayer(controllerId);
if (player == null) {
return false;
}
for (UUID opponentID : game.getOpponents(controllerId)){
Player opponent = game.getPlayer(opponentID);
if (opponent == null) {
continue;
}
if (opponent.canBeTargetedBy(source.getSourceObject(game), controllerId, source, game)) {
return true;
}
}
return false;
}
@Override
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
this.getTargets().clearChosen();
paid = false;
if (this.getTargets().choose(Outcome.DrawCard, controllerId, source.getSourceId(), source, game)) {
Player opponent = game.getPlayer(this.getTargets().getFirstTarget());
if (opponent == null || !opponent.canRespond()){
return false;
}
paid = opponent.drawCards(1, source, game) > 0;
}
return paid;
}
@Override
public AlaniaDivergentStormCost copy() {
return new AlaniaDivergentStormCost(this);
}
}
// Based on MastermindPlumCondition
enum AlaniaDivergentStormCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
Spell spell = (Spell) source.getEffects().get(0).getValue("spellCast");
if (spell == null) {
return false;
}
Permanent sourcePermanent = source.getSourcePermanentOrLKI(game);
SpellAbility sourceCastAbility = sourcePermanent.getSpellAbility();
// Get source permanent MOR from when it was on the stack
// The UUID of a spell on the stack is NOT the same as the card that produced it--it uses the UUID of the SpellAbility from that card instead (synced to the ZCC of the source Card).
MageObjectReference sourceSpellMOR = new MageObjectReference(sourceCastAbility.getId(), sourcePermanent.getZoneChangeCounter(game) - 1, game);
AlaniaDivergentStormWatcher watcher = game.getState().getWatcher(AlaniaDivergentStormWatcher.class);
UUID spellControllerID = spell.getControllerId();
MageObjectReference spellMOR = new MageObjectReference(spell, game);
return watcher.spellIsFirstISOCast(spellControllerID, spellMOR, sourceSpellMOR);
}
}
// Based on FirstSpellCastThisTurnWatcher
class AlaniaDivergentStormWatcher extends Watcher {
private final Map<UUID, MageObjectReference> playerFirstInstantCast = new HashMap<>();
private final Map<UUID, MageObjectReference> playerFirstSorceryCast = new HashMap<>();
private final Map<UUID, MageObjectReference> playerFirstOtterCast = new HashMap<>();
private final Map<UUID, MageObjectReference> playerSecondOtterCast = new HashMap<>();
public AlaniaDivergentStormWatcher() {
super(WatcherScope.GAME);
}
@Override
public void watch(GameEvent event, Game game) {
if (event.getType() != GameEvent.EventType.SPELL_CAST) {
return;
}
Spell spell = (Spell) game.getObject(event.getTargetId());
if (spell == null) {
return;
}
UUID spellControllerID = spell.getControllerId();
MageObjectReference spellMOR = new MageObjectReference(spell, game);
if (spell.getCardType(game).contains(CardType.INSTANT)) {
playerFirstInstantCast.putIfAbsent(spellControllerID, spellMOR);
}
if (spell.getCardType(game).contains(CardType.SORCERY)) {
playerFirstSorceryCast.putIfAbsent(spellControllerID, spellMOR);
}
if (spell.getSubtype(game).contains(SubType.OTTER)){
if (playerFirstOtterCast.containsKey(spellControllerID)) {
// We already cast an otter this turn, put it on the second otter list
playerSecondOtterCast.putIfAbsent(spellControllerID, spellMOR);
}
// Will only put if we didnt cast an otter this turn yet
playerFirstOtterCast.putIfAbsent(spellControllerID, spellMOR);
}
}
@Override
public void reset() {
super.reset();
playerFirstInstantCast.clear();
playerFirstSorceryCast.clear();
playerFirstOtterCast.clear();
playerSecondOtterCast.clear();
}
public boolean spellIsFirstISOCast(UUID controllerID, MageObjectReference spell, MageObjectReference AlaniaMOR) {
MageObjectReference firstOtterMOR = playerFirstOtterCast.get(controllerID);
if (firstOtterMOR != null && firstOtterMOR.equals(AlaniaMOR)) {
// The first otter we cast was the triggering Alania! check if the second otter matches instead
firstOtterMOR = playerSecondOtterCast.get(controllerID);
}
return spell.equals(playerFirstInstantCast.get(controllerID)) ||
spell.equals(playerFirstSorceryCast.get(controllerID)) ||
(spell.equals(firstOtterMOR));
}
}

View file

@ -25,6 +25,7 @@ public final class Bloomburrow extends ExpansionSet {
cards.add(new SetCardInfo("Agate Assault", 122, Rarity.COMMON, mage.cards.a.AgateAssault.class)); cards.add(new SetCardInfo("Agate Assault", 122, Rarity.COMMON, mage.cards.a.AgateAssault.class));
cards.add(new SetCardInfo("Agate-Blade Assassin", 82, Rarity.COMMON, mage.cards.a.AgateBladeAssassin.class)); cards.add(new SetCardInfo("Agate-Blade Assassin", 82, Rarity.COMMON, mage.cards.a.AgateBladeAssassin.class));
cards.add(new SetCardInfo("Alania's Pathmaker", 123, Rarity.COMMON, mage.cards.a.AlaniasPathmaker.class)); cards.add(new SetCardInfo("Alania's Pathmaker", 123, Rarity.COMMON, mage.cards.a.AlaniasPathmaker.class));
cards.add(new SetCardInfo("Alania, Divergent Storm", 204, Rarity.RARE, mage.cards.a.AlaniaDivergentStorm.class));
cards.add(new SetCardInfo("Artist's Talent", 124, Rarity.RARE, mage.cards.a.ArtistsTalent.class)); cards.add(new SetCardInfo("Artist's Talent", 124, Rarity.RARE, mage.cards.a.ArtistsTalent.class));
cards.add(new SetCardInfo("Azure Beastbinder", 41, Rarity.RARE, mage.cards.a.AzureBeastbinder.class)); cards.add(new SetCardInfo("Azure Beastbinder", 41, Rarity.RARE, mage.cards.a.AzureBeastbinder.class));
cards.add(new SetCardInfo("Bakersbane Duo", 163, Rarity.COMMON, mage.cards.b.BakersbaneDuo.class)); cards.add(new SetCardInfo("Bakersbane Duo", 163, Rarity.COMMON, mage.cards.b.BakersbaneDuo.class));

View file

@ -0,0 +1,241 @@
package org.mage.test.cards.single.blb;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.HasteAbility;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author jimga150
*/
public class AlaniaDivergentStormTest extends CardTestPlayerBase {
/**
* {@link mage.cards.a.AlaniaDivergentStorm Alania, Divergent Storm} {3}{U}{R}
* Legendary Creature Otter Wizard
* Whenever you cast a spell, if it's the first instant spell, the first sorcery spell, or the first Otter spell
* other than Alania you've cast this turn, you may have target opponent draw a card. If you do, copy that spell.
* You may choose new targets for the copy.
*/
private static final String alania = "Alania, Divergent Storm";
@Test
public void test_TwoOtters() {
// Test that the "first Otter spell other than Alania you've cast this turn" clause works
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5);
addCard(Zone.BATTLEFIELD, playerA, "Island", 5);
addCard(Zone.HAND, playerA, "Coruscation Mage");
addCard(Zone.HAND, playerA, alania);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, alania, true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Coruscation Mage", true);
setChoice(playerA, "No"); // Offspring?
setChoice(playerA, "Yes"); // Copy spell?
setChoice(playerA, "PlayerB"); // Who draws?
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
// Copied
assertPermanentCount(playerA, "Coruscation Mage", 2);
// Not copied
assertPermanentCount(playerA, alania, 1);
// Opponent drew a card
assertHandCount(playerB, 1);
}
@Test
public void test_TwoOttersNextTurn() {
// Test that the "first Otter spell other than Alania you've cast this turn" clause works on the next turn
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5);
addCard(Zone.BATTLEFIELD, playerA, "Island", 5);
addCard(Zone.HAND, playerA, "Coruscation Mage");
addCard(Zone.HAND, playerA, "Stormcatch Mentor");
addCard(Zone.HAND, playerA, alania);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, alania, true);
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Stormcatch Mentor", true);
setChoice(playerA, "Yes"); // Copy spell?
setChoice(playerA, "PlayerB"); // Who draws?
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Coruscation Mage", true);
setChoice(playerA, "No"); // Offspring?
setStrictChooseMode(true);
setStopAt(3, PhaseStep.BEGIN_COMBAT);
execute();
// Copied
assertPermanentCount(playerA, "Stormcatch Mentor", 2);
// Not copied
assertPermanentCount(playerA, alania, 1);
assertPermanentCount(playerA, "Coruscation Mage", 1);
// Opponent drew a card (plus the one for turn draw)
assertHandCount(playerB, 1 + 1);
}
@Test
public void test_ThreeOttersAdventureInstant() {
// Test that the "first Otter spell other than Alania you've cast this turn" clause excludes the third otter cast on the same turn you cast Alania
// Also throws in an adventure otter, cast for creature and instant
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 10);
addCard(Zone.BATTLEFIELD, playerA, "Island", 10);
addCard(Zone.HAND, playerA, "Coruscation Mage");
addCard(Zone.HAND, playerA, "Frolicking Familiar", 2);
addCard(Zone.HAND, playerA, alania);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, alania, true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Frolicking Familiar", true);
setChoice(playerA, "Yes"); // Copy spell?
setChoice(playerA, "PlayerB"); // Who draws?
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Coruscation Mage", true);
setChoice(playerA, "No"); // Offspring?
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Blow Off Steam", true);
setChoice(playerA, "Whenever you cast an instant", 2); // Add Frolicking Familiar triggers first
setChoice(playerA, "Whenever you cast a noncreature"); // Add Coruscation Mage trigger
// Alania's trigger will add last
setChoice(playerA, "Yes"); // Copy spell?
setChoice(playerA, "PlayerB"); // Who draws?
addTarget(playerA, playerB);
setChoice(playerA, "No"); // Change target?
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
// Copied
assertPermanentCount(playerA, "Frolicking Familiar", 2);
// Got boost from single cast of Blow Off Steam (copy =/= cast)
assertPowerToughness(playerA, "Frolicking Familiar", 3, 3);
// Not copied
assertPermanentCount(playerA, "Coruscation Mage", 1);
// Not copied
assertPermanentCount(playerA, alania, 1);
// Blow Off Steam copied, pinged twice, plus the Coruscation Mage ping
assertLife(playerB, currentGame.getStartingLife() - 3);
// opponent drew 2 cards
assertHandCount(playerB, 2);
}
@Test
public void test_TwoInstants() {
// Test that the "first instant you've cast this turn" clause works
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5);
addCard(Zone.BATTLEFIELD, playerA, "Island", 5);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
addCard(Zone.HAND, playerA, "Acrobatic Leap");
addCard(Zone.HAND, playerA, "Ancestral Recall");
addCard(Zone.HAND, playerA, alania);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, alania, true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Acrobatic Leap", true);
setChoice(playerA, "Yes"); // Copy spell?
setChoice(playerA, "PlayerB"); // Who draws?
addTarget(playerA, alania); // Target creature
setChoice(playerA, "No"); // Change target?
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ancestral Recall", true);
addTarget(playerA, playerA); // Target player
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
// Copied: Acrobatic Leap
assertPowerToughness(playerA, alania, 3 + 2, 5 + 2*3);
// Not copied: Ancestral Recall
assertHandCount(playerA, 3);
// Opponent drew a card
assertHandCount(playerB, 1);
}
@Test
public void test_TwoSorceries() {
// Test that the "first sorcery you've cast this turn" clause works
// Also copies an adventure sorcery
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5);
addCard(Zone.BATTLEFIELD, playerA, "Island", 5);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
addCard(Zone.HAND, playerA, "Faerie Guidemother"); // adventure card
addCard(Zone.HAND, playerA, "Maximize Velocity");
addCard(Zone.HAND, playerA, alania);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, alania, true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Gift of the Fae", true);
setChoice(playerA, "Yes"); // Copy spell?
setChoice(playerA, "PlayerB"); // Who draws?
addTarget(playerA, alania); // Target creature
setChoice(playerA, "No"); // Change target?
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Maximize Velocity", true);
addTarget(playerA, alania); // Target creature
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
// Copied: Gift of the Fae, Not copied: Ancestral Recall
assertPowerToughness(playerA, alania, 3 + 2*2 + 1, 5 + 2 + 1);
assertAbility(playerA, alania, FlyingAbility.getInstance(), true);
assertAbility(playerA, alania, HasteAbility.getInstance(), true);
// Opponent drew a card
assertHandCount(playerB, 1);
}
@Test
public void test_OtherCard() {
// Test that card cast that is first of its type but is not an instant, sorcery, or otter will not trigger Alania
addCard(Zone.BATTLEFIELD, playerA, "Plains", 3);
addCard(Zone.HAND, playerA, "Memnite");
addCard(Zone.HAND, playerA, "Arcane Signet");
addCard(Zone.HAND, playerA, "Ajani's Welcome");
addCard(Zone.BATTLEFIELD, playerA, alania);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Memnite", true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Arcane Signet", true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ajani's Welcome", true);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
// Nothing copied
assertPermanentCount(playerA, "Memnite", 1);
assertPermanentCount(playerA, "Arcane Signet", 1);
assertPermanentCount(playerA, "Ajani's Welcome", 1);
// Opponent did not draw a card
assertHandCount(playerB, 0);
}
@Test
public void test_TwoOttersOpponentsHexproof() {
// Test that Alania cannot copy spells if all opponents have hexproof
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
addCard(Zone.BATTLEFIELD, playerB, "Plains", 1);
addCard(Zone.HAND, playerA, "Coruscation Mage");
addCard(Zone.BATTLEFIELD, playerA, alania);
addCard(Zone.HAND, playerB, "Blossoming Calm");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Blossoming Calm");
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Coruscation Mage", true);
setChoice(playerA, "No"); // Offspring?
setStrictChooseMode(true);
setStopAt(3, PhaseStep.BEGIN_COMBAT);
execute();
// Not Copied
assertPermanentCount(playerA, "Coruscation Mage", 1);
}
}