mirror of
https://github.com/correl/mage.git
synced 2024-12-26 03:00:11 +00:00
[DMU] Implement Thran Portal (#9456)
* [NCC] Implement Thran Portal * Address comments * Fixed usage of {this}
This commit is contained in:
parent
c16ead128b
commit
09b069ceb2
3 changed files with 295 additions and 0 deletions
167
Mage.Sets/src/mage/cards/t/ThranPortal.java
Normal file
167
Mage.Sets/src/mage/cards/t/ThranPortal.java
Normal file
|
@ -0,0 +1,167 @@
|
|||
package mage.cards.t;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.AsEntersBattlefieldAbility;
|
||||
import mage.abilities.common.EntersBattlefieldAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.abilities.condition.InvertCondition;
|
||||
import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition;
|
||||
import mage.abilities.costs.common.PayLifeCost;
|
||||
import mage.abilities.decorator.ConditionalOneShotEffect;
|
||||
import mage.abilities.effects.ContinuousEffect;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.abilities.effects.common.ChooseBasicLandTypeEffect;
|
||||
import mage.abilities.effects.common.TapSourceEffect;
|
||||
import mage.abilities.effects.common.continuous.AddChosenSubtypeEffect;
|
||||
import mage.abilities.effects.common.cost.CostModificationEffectImpl;
|
||||
import mage.abilities.effects.common.enterAttribute.EnterAttributeAddChosenSubtypeEffect;
|
||||
import mage.abilities.mana.*;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.filter.common.FilterLandPermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author Alex-Vasile
|
||||
*/
|
||||
public class ThranPortal extends CardImpl {
|
||||
|
||||
public ThranPortal(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.LAND}, "");
|
||||
|
||||
addSubType(SubType.GATE);
|
||||
|
||||
// Thran Portal enters the battlefield tapped unless you control two or fewer other lands.
|
||||
Condition controls = new InvertCondition(new PermanentsOnTheBattlefieldCondition(new FilterLandPermanent(), ComparisonType.FEWER_THAN, 3));
|
||||
String abilityText = " tapped unless you control two or fewer other lands";
|
||||
this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new TapSourceEffect(), controls, abilityText), abilityText));
|
||||
|
||||
// As Thran Portal enters the battlefield, choose a basic land type.
|
||||
// Thran Portal is the chosen type in addition to its other types.
|
||||
AsEntersBattlefieldAbility chooseLandTypeAbility = new AsEntersBattlefieldAbility(new ChooseBasicLandTypeEffect(Outcome.AddAbility));
|
||||
chooseLandTypeAbility.addEffect(new EnterAttributeAddChosenSubtypeEffect()); // While it enters
|
||||
this.addAbility(chooseLandTypeAbility);
|
||||
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new AddChosenSubtypeEffect())); // While on the battlefield
|
||||
|
||||
// Mana abilities of Thran Portal cost an additional 1 life to activate.
|
||||
// This also adds the mana ability
|
||||
this.addAbility(new SimpleStaticAbility(new ThranPortalAdditionalCostEffect()));
|
||||
this.addAbility(new SimpleStaticAbility(new ThranPortalManaAbilityContinousEffect()));
|
||||
}
|
||||
|
||||
private ThranPortal(final ThranPortal card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThranPortal copy() {
|
||||
return new ThranPortal(this);
|
||||
}
|
||||
}
|
||||
|
||||
class ThranPortalManaAbilityContinousEffect extends ContinuousEffectImpl {
|
||||
|
||||
private static final Map<SubType, BasicManaAbility> abilityMap = new HashMap<SubType, BasicManaAbility>() {{
|
||||
put(SubType.PLAINS, new WhiteManaAbility());
|
||||
put(SubType.ISLAND, new BlueManaAbility());
|
||||
put(SubType.SWAMP, new BlackManaAbility());
|
||||
put(SubType.MOUNTAIN, new RedManaAbility());
|
||||
put(SubType.FOREST, new GreenManaAbility());
|
||||
}};
|
||||
|
||||
public ThranPortalManaAbilityContinousEffect() {
|
||||
super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Neutral);
|
||||
staticText = "mana abilities of {this} cost an additional 1 life to activate";
|
||||
}
|
||||
|
||||
public ThranPortalManaAbilityContinousEffect(final ThranPortalManaAbilityContinousEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThranPortalManaAbilityContinousEffect copy() {
|
||||
return new ThranPortalManaAbilityContinousEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Ability source, Game game) {
|
||||
super.init(source, game);
|
||||
SubType choice = SubType.byDescription((String) game.getState().getValue(source.getSourceId().toString() + ChooseBasicLandTypeEffect.VALUE_KEY));
|
||||
switch (choice) {
|
||||
case FOREST:
|
||||
dependencyTypes.add(DependencyType.BecomeForest);
|
||||
break;
|
||||
case PLAINS:
|
||||
dependencyTypes.add(DependencyType.BecomePlains);
|
||||
break;
|
||||
case MOUNTAIN:
|
||||
dependencyTypes.add(DependencyType.BecomeMountain);
|
||||
break;
|
||||
case ISLAND:
|
||||
dependencyTypes.add(DependencyType.BecomeIsland);
|
||||
break;
|
||||
case SWAMP:
|
||||
dependencyTypes.add(DependencyType.BecomeSwamp);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Incorrect mana choice " + choice + "for Thran Portal");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent thranPortal = game.getPermanent(source.getSourceId());
|
||||
SubType choice = SubType.byDescription((String) game.getState().getValue(source.getSourceId().toString() + ChooseBasicLandTypeEffect.VALUE_KEY));
|
||||
if (thranPortal == null || choice == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!thranPortal.hasSubtype(choice, game)) {
|
||||
thranPortal.addSubType(choice);
|
||||
}
|
||||
if (!thranPortal.hasAbility(abilityMap.get(choice), game)) {
|
||||
thranPortal.addAbility(abilityMap.get(choice), source.getId(), game);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class ThranPortalAdditionalCostEffect extends CostModificationEffectImpl {
|
||||
|
||||
ThranPortalAdditionalCostEffect() {
|
||||
super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.INCREASE_COST);
|
||||
this.staticText = "mana abilities of {this} cost an additional 1 life to activate";
|
||||
}
|
||||
|
||||
private ThranPortalAdditionalCostEffect(final ThranPortalAdditionalCostEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThranPortalAdditionalCostEffect copy() {
|
||||
return new ThranPortalAdditionalCostEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source, Ability abilityToModify) {
|
||||
abilityToModify.addCost(new PayLifeCost(1));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(Ability abilityToModify, Ability source, Game game) {
|
||||
if (!abilityToModify.getSourceId().equals(source.getSourceId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return abilityToModify instanceof ManaAbility;
|
||||
}
|
||||
}
|
|
@ -231,6 +231,7 @@ public final class DominariaUnited extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("The Raven Man", 103, Rarity.RARE, mage.cards.t.TheRavenMan.class));
|
||||
cards.add(new SetCardInfo("The Weatherseed Treaty", 188, Rarity.UNCOMMON, mage.cards.t.TheWeatherseedTreaty.class));
|
||||
cards.add(new SetCardInfo("The World Spell", 189, Rarity.MYTHIC, mage.cards.t.TheWorldSpell.class));
|
||||
cards.add(new SetCardInfo("Thran Portal", 259, Rarity.RARE, mage.cards.t.ThranPortal.class));
|
||||
cards.add(new SetCardInfo("Threats Undetected", 185, Rarity.RARE, mage.cards.t.ThreatsUndetected.class));
|
||||
cards.add(new SetCardInfo("Thrill of Possibility", 148, Rarity.COMMON, mage.cards.t.ThrillOfPossibility.class));
|
||||
cards.add(new SetCardInfo("Tidepool Turtle", 69, Rarity.COMMON, mage.cards.t.TidepoolTurtle.class));
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
package org.mage.test.cards.single.dmu;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* {@link mage.cards.t.ThranPortal Thran Portal}
|
||||
* Land Gate
|
||||
* Thran Portal enters the battlefield tapped unless you control two or fewer other lands.
|
||||
* As Thran Portal enters the battlefield, choose a basic land type.
|
||||
* Thran Portal is the chosen type in addition to its other types.
|
||||
* Mana abilities of Thran Portal cost an additional 1 life to activate.
|
||||
*
|
||||
* @author Alex-Vasile
|
||||
*/
|
||||
public class ThranPortalTest extends CardTestPlayerBase {
|
||||
|
||||
private static final String thranPortal = "Thran Portal";
|
||||
|
||||
/**
|
||||
* Test that tapping it for mana deals damage.
|
||||
* Also tests that it comes in untapped if you control 2 of fewer lands.
|
||||
*/
|
||||
@Test
|
||||
public void dealsDamage() {
|
||||
String lightningBolt = "Lightning Bolt";
|
||||
addCard(Zone.HAND, playerA, thranPortal);
|
||||
addCard(Zone.HAND, playerA, lightningBolt);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
|
||||
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, thranPortal);
|
||||
setChoice(playerA, "Thran");
|
||||
setChoice(playerA, "Mountain");
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, lightningBolt);
|
||||
addTarget(playerA, playerB);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, 20 - 1); // 1 life for tapping it
|
||||
assertLife(playerB, 20 - 3); // 3 life from lightning bolt
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test that it gets properly seen as the land type that was chosen.
|
||||
* Also tests that it comes in tapped when you control 3 or more lands.
|
||||
*/
|
||||
@Test
|
||||
public void seenAsChoice() {
|
||||
// Whenever a Mountain enters the battlefield under your control, if you control at least five other Mountains,
|
||||
// you may have Valakut, the Molten Pinnacle deal 3 damage to any target.
|
||||
String valakut = "Valakut, the Molten Pinnacle";
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5);
|
||||
addCard(Zone.BATTLEFIELD, playerA, valakut);
|
||||
addCard(Zone.HAND, playerA, thranPortal);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
|
||||
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, thranPortal);
|
||||
setChoice(playerA, "Thran");
|
||||
setChoice(playerA, "Mountain");
|
||||
setChoice(playerA, "Yes");
|
||||
addTarget(playerA, playerB);
|
||||
|
||||
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
execute();
|
||||
assertTapped(thranPortal, true);
|
||||
assertLife(playerB, 20 - 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the mana ability gained from Chromatic Lantern also costs 1 life.
|
||||
*/
|
||||
@Test
|
||||
public void chromaticLanternCostInteraction() {
|
||||
// Lands you control have “{T}: Add one mana of any color.”
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Chromatic Lantern");
|
||||
addCard(Zone.HAND, playerA, thranPortal);
|
||||
addCard(Zone.HAND, playerA, "Academy Loremaster"); // {U}{U}
|
||||
|
||||
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, thranPortal);
|
||||
setChoice(playerA, "Thran");
|
||||
setChoice(playerA, "Mountain"); // Set mountain so that it must use the ability given by chromatic lantern to get the {U}
|
||||
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Academy Loremaster");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, 19); // Should have lost life from tapping Thran Portal to use the ability chromatic lantern gave it
|
||||
assertPermanentCount(playerA, "Academy Loremaster" , 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that Manascape Refractor copies the Thran Portal's mana abilities, but not the additional 1 life cost.
|
||||
*/
|
||||
@Test
|
||||
public void manascapeRefractorInteraction() {
|
||||
addCard(Zone.HAND, playerA, thranPortal);
|
||||
addCard(Zone.HAND, playerA, "Academy Loremaster"); // {U}{U}
|
||||
// Manascape Refractor enters the battlefield tapped.
|
||||
// Manascape Refractor has all activated abilities of all lands on the battlefield.
|
||||
// You may spend mana as though it were mana of any color to pay the activation costs of Manascape Refractor’s abilities.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Manascape Refractor");
|
||||
|
||||
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, thranPortal);
|
||||
setChoice(playerA, "Thran");
|
||||
setChoice(playerA, "Island"); // Both Thran portal and Manascape will not tap for blue
|
||||
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Academy Loremaster");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, 20 - 1); // Lost one life for Thran portal BUT NOT for Manascape Refractor
|
||||
assertPermanentCount(playerA, "Academy Loremaster" , 1);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue