mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 02:30:08 -08:00
update exile zone for CraftAbility to share between card sides
* updated and added test coverage for previously converted cards Altar of the Wretched and Eye of Ojer Taq
This commit is contained in:
parent
ae97f8944d
commit
99bb467bdc
5 changed files with 216 additions and 42 deletions
|
|
@ -117,7 +117,7 @@ enum WretchedBonemassDynamicValue implements DynamicValue {
|
|||
ExileZone exileZone = game
|
||||
.getExile()
|
||||
.getExileZone(CardUtil.getExileZoneId(
|
||||
game, permanent.getId(), permanent.getZoneChangeCounter(game) - 2
|
||||
game, permanent.getMainCard().getId(), permanent.getZoneChangeCounter(game) - 1
|
||||
));
|
||||
if (exileZone == null) {
|
||||
return 0;
|
||||
|
|
@ -167,7 +167,7 @@ class WretchedBonemassGainAbilityEffect extends ContinuousEffectImpl {
|
|||
ExileZone exileZone = game
|
||||
.getExile()
|
||||
.getExileZone(CardUtil.getExileZoneId(
|
||||
game, wretchedBonemass.getId(), wretchedBonemass.getZoneChangeCounter(game) - 2
|
||||
game, wretchedBonemass.getMainCard().getId(), wretchedBonemass.getZoneChangeCounter(game) - 1
|
||||
));
|
||||
if (exileZone != null
|
||||
&& !exileZone.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@ package mage.cards.e;
|
|||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.abilities.costs.AlternativeCostSourceAbility;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.abilities.effects.EntersBattlefieldEffect;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.TapSourceEffect;
|
||||
import mage.abilities.effects.common.cost.CostModificationEffectImpl;
|
||||
import mage.abilities.keyword.CraftAbility;
|
||||
import mage.abilities.mana.AnyColorManaAbility;
|
||||
import mage.cards.Card;
|
||||
|
|
@ -27,6 +28,7 @@ import mage.game.permanent.Permanent;
|
|||
import mage.players.Player;
|
||||
import mage.target.common.TargetCardInGraveyardBattlefieldOrStack;
|
||||
import mage.util.CardUtil;
|
||||
import mage.watchers.common.SpellsCastWatcher;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
|
@ -149,7 +151,10 @@ class ChooseCardTypeEffect extends OneShotEffect {
|
|||
if (permanent == null) {
|
||||
return false;
|
||||
}
|
||||
ExileZone exileZone = game.getState().getExile().getExileZone(CardUtil.getExileZoneId(game, source, game.getState().getZoneChangeCounter(mageObject.getId()) - 1));
|
||||
ExileZone exileZone = game.getState()
|
||||
.getExile()
|
||||
.getExileZone(CardUtil
|
||||
.getExileZoneId(game, permanent.getMainCard().getId(), permanent.getMainCard().getZoneChangeCounter(game)));
|
||||
if (exileZone == null) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -232,63 +237,74 @@ class ApexObservatoryEffect extends OneShotEffect {
|
|||
}
|
||||
}
|
||||
|
||||
class ApexObservatoryCastWithoutManaEffect extends CostModificationEffectImpl {
|
||||
class ApexObservatoryCastWithoutManaEffect extends ContinuousEffectImpl {
|
||||
|
||||
class ApexObservatoryCondition implements Condition {
|
||||
private final int spellCastCount;
|
||||
|
||||
private ApexObservatoryCondition(int spellCastCount) {
|
||||
this.spellCastCount = spellCastCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class);
|
||||
if (watcher != null) {
|
||||
return watcher.getSpellsCastThisTurn(playerId).size() == spellCastCount;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private final FilterCard filter;
|
||||
private final String chosenCardType;
|
||||
private final UUID playerId;
|
||||
private boolean used = false;
|
||||
private int spellCastCount;
|
||||
private AlternativeCostSourceAbility alternativeCostSourceAbility;
|
||||
|
||||
ApexObservatoryCastWithoutManaEffect(String chosenCardType, UUID playerId) {
|
||||
super(Duration.EndOfTurn, Outcome.Benefit, CostModificationType.SET_COST);
|
||||
super(Duration.EndOfTurn, Layer.RulesEffects, SubLayer.NA, Outcome.PlayForFree);
|
||||
this.chosenCardType = chosenCardType;
|
||||
this.playerId = playerId;
|
||||
this.filter = new FilterCard("spell of the chosen type");
|
||||
filter.add(CardType.fromString(chosenCardType).getPredicate());
|
||||
staticText = "The next spell you cast this turn of the chosen type can be cast without paying its mana cost";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Ability source, Game game) {
|
||||
super.init(source, game);
|
||||
SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class);
|
||||
if (watcher != null) {
|
||||
spellCastCount = watcher.getSpellsCastThisTurn(playerId).size();
|
||||
Condition condition = new ApexObservatoryCondition(spellCastCount);
|
||||
alternativeCostSourceAbility = new AlternativeCostSourceAbility(
|
||||
null, condition, null, filter, true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private ApexObservatoryCastWithoutManaEffect(final ApexObservatoryCastWithoutManaEffect effect) {
|
||||
super(effect);
|
||||
this.chosenCardType = effect.chosenCardType;
|
||||
this.playerId = effect.playerId;
|
||||
this.used = effect.used;
|
||||
this.spellCastCount = effect.spellCastCount;
|
||||
this.filter = effect.filter;
|
||||
this.alternativeCostSourceAbility = effect.alternativeCostSourceAbility;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source, Ability abilityToModify) {
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(playerId);
|
||||
if (controller != null) {
|
||||
MageObject spell = abilityToModify.getSourceObject(game);
|
||||
if (spell != null && !game.isSimulation()) {
|
||||
String message = "Cast " + spell.getIdName() + " without paying its mana cost?";
|
||||
if (controller.chooseUse(Outcome.Benefit, message, source, game)) {
|
||||
abilityToModify.getManaCostsToPay().clear();
|
||||
used = true;
|
||||
}
|
||||
}
|
||||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
alternativeCostSourceAbility.setSourceId(source.getSourceId());
|
||||
controller.getAlternativeSourceCosts().add(alternativeCostSourceAbility);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInactive(Ability source, Game game) {
|
||||
return used || super.isInactive(source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(Ability ability, Ability source, Game game) {
|
||||
if (used) {
|
||||
return false;
|
||||
}
|
||||
if (!ability.isControlledBy(playerId)) {
|
||||
return false;
|
||||
}
|
||||
if (!(ability instanceof SpellAbility)) {
|
||||
return false;
|
||||
}
|
||||
MageObject object = game.getObject(ability.getSourceId());
|
||||
return object != null && object.getCardType(game).stream()
|
||||
.anyMatch(cardType -> cardType.toString().equals(chosenCardType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApexObservatoryCastWithoutManaEffect copy() {
|
||||
return new ApexObservatoryCastWithoutManaEffect(this);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
package org.mage.test.cards.single.lcc;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.keyword.FlyingAbility;
|
||||
import mage.abilities.keyword.LifelinkAbility;
|
||||
import mage.abilities.keyword.VigilanceAbility;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jmlundeen
|
||||
*/
|
||||
public class AltarOfTheWretchedTest extends CardTestPlayerBase {
|
||||
|
||||
/*
|
||||
Altar of the Wretched
|
||||
{2}{B}
|
||||
Artifact
|
||||
When Altar of the Wretched enters the battlefield, you may sacrifice a nontoken creature. If you do, draw X cards, then mill X cards, where X is that creature's power.
|
||||
Craft with one or more creatures {2}{B}{B}
|
||||
{2}{B}: Return Altar of the Wretched from your graveyard to your hand.
|
||||
|
||||
Wretched Bonemass
|
||||
Color Indicator: Black
|
||||
Creature — Skeleton Horror
|
||||
Wretched Bonemass’s power and toughness are each equal to the total power of the exiled cards used to craft it.
|
||||
This creature has flying as long as an exiled card used to craft it has flying. The same is true for first strike, double strike, deathtouch, haste, hexproof, indestructible, lifelink, menace, protection, reach, trample, and vigilance.
|
||||
0/0
|
||||
*/
|
||||
private static final String altarOfTheWretched = "Altar of the Wretched";
|
||||
private static final String wretchedBoneMass = "Wretched Bonemass";
|
||||
|
||||
/*
|
||||
Angel of Invention
|
||||
{3}{W}{W}
|
||||
Creature - Angel
|
||||
Flying, vigilance, lifelink
|
||||
Fabricate 2
|
||||
Other creatures you control get +1/+1.
|
||||
2/1
|
||||
*/
|
||||
private static final String angelOfInvention = "Angel of Invention";
|
||||
|
||||
|
||||
@Test
|
||||
public void testAltarOfTheWretched() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, altarOfTheWretched);
|
||||
addCard(Zone.BATTLEFIELD, playerA, angelOfInvention);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Craft with one or more");
|
||||
addTarget(playerA, angelOfInvention);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertPowerToughness(playerA, wretchedBoneMass, 2, 2);
|
||||
assertExileCount(playerA, angelOfInvention, 1);
|
||||
List<Ability> abilities = new ArrayList<>();
|
||||
abilities.add(FlyingAbility.getInstance());
|
||||
abilities.add(VigilanceAbility.getInstance());
|
||||
abilities.add(LifelinkAbility.getInstance());
|
||||
assertAbilities(playerA, wretchedBoneMass, abilities);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package org.mage.test.cards.single.lcc;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jmlundeen
|
||||
*/
|
||||
public class EyeOfOjerTaqTest extends CardTestPlayerBase {
|
||||
|
||||
/*
|
||||
Eye of Ojer Taq
|
||||
{3}
|
||||
Artifact
|
||||
{T}: Add one mana of any color.
|
||||
Craft with two that share a card type {6}
|
||||
|
||||
Apex Observatory
|
||||
Artifact
|
||||
Apex Observatory enters the battlefield tapped. As it enters, choose a card type shared among two exiled cards used to craft it.
|
||||
{T}: The next spell you cast this turn of the chosen type can be cast without paying its mana cost.
|
||||
*/
|
||||
private static final String eyeOfOjerTaq = "Eye of Ojer Taq";
|
||||
private static final String apexObservatory = "Apex Observatory";
|
||||
|
||||
/*
|
||||
Lightning Bolt
|
||||
{R}
|
||||
Instant
|
||||
Lightning Bolt deals 3 damage to any target.
|
||||
*/
|
||||
private static final String lightningBolt = "Lightning Bolt";
|
||||
|
||||
/*
|
||||
Ponder
|
||||
{U}
|
||||
Sorcery
|
||||
Look at the top three cards of your library, then put them back in any order. You may shuffle your library.
|
||||
Draw a card.
|
||||
*/
|
||||
private static final String ponder = "Ponder";
|
||||
|
||||
/*
|
||||
Shock
|
||||
{R}
|
||||
Instant
|
||||
Shock deals 2 damage to any target.
|
||||
*/
|
||||
private static final String shock = "Shock";
|
||||
|
||||
@Test
|
||||
public void testEyeOfOjerTaq() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, eyeOfOjerTaq);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 6);
|
||||
addCard(Zone.GRAVEYARD, playerA, lightningBolt);
|
||||
addCard(Zone.GRAVEYARD, playerA, shock);
|
||||
addCard(Zone.HAND, playerA, shock, 2);
|
||||
addCard(Zone.HAND, playerA, ponder);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Craft with two that share a card type");
|
||||
addTarget(playerA, lightningBolt + "^" + shock);
|
||||
setChoice(playerA, "Instant");
|
||||
|
||||
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: The next spell");
|
||||
waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
|
||||
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, shock, playerB); // should be able to cast for free
|
||||
setChoice(playerA, "Cast without paying");
|
||||
checkPlayableAbility("Can't cast second shock", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Shock", false);
|
||||
|
||||
checkPlayableAbility("Can't cast ponder", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast Ponder", false);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertLife(playerB, 20 - 2); // took 2 damage from shock
|
||||
assertExileCount(playerA, lightningBolt, 1);
|
||||
assertExileCount(playerA, shock, 1);
|
||||
assertGraveyardCount(playerA, shock, 1);
|
||||
assertPermanentCount(playerA, apexObservatory, 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,6 @@ import mage.abilities.costs.common.ExileSourceCost;
|
|||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.TransformingDoubleFacedCardHalf;
|
||||
import mage.constants.*;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.filter.FilterPermanent;
|
||||
|
|
@ -122,7 +121,8 @@ class CraftCost extends CostImpl {
|
|||
@Override
|
||||
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
if (player == null) {
|
||||
Card sourceCard = game.getCard(source.getSourceId());
|
||||
if (player == null || sourceCard == null) {
|
||||
paid = false;
|
||||
return paid;
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@ class CraftCost extends CostImpl {
|
|||
.collect(Collectors.toSet());
|
||||
player.moveCardsToExile(
|
||||
cards, source, game, true,
|
||||
CardUtil.getExileZoneId(game, source),
|
||||
CardUtil.getExileZoneId(game, sourceCard.getMainCard().getId(), sourceCard.getMainCard().getZoneChangeCounter(game)),
|
||||
CardUtil.getSourceName(game, source)
|
||||
);
|
||||
paid = true;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue