[LCI] Implement The Skullspore Nexus (#11327)

This commit is contained in:
Susucre 2023-10-21 15:26:38 +02:00 committed by GitHub
parent 59929d2860
commit a37fc0589a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 283 additions and 50 deletions

View file

@ -1,25 +1,24 @@
package mage.cards.t;
import mage.MageInt;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.dynamicvalue.common.GreatestPowerAmongControlledCreaturesValue;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.cost.CostModificationEffectImpl;
import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.mana.SimpleManaAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.constants.CardType;
import mage.constants.SetTargetPointer;
import mage.constants.SuperType;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.util.CardUtil;
import java.util.UUID;
@ -34,7 +33,9 @@ public final class TheGreatHenge extends CardImpl {
this.supertype.add(SuperType.LEGENDARY);
// This spell costs {X} less to cast, where X is the greatest power among creatures you control.
this.addAbility(new SimpleStaticAbility(Zone.ALL, new TheGreatHengeCostReductionEffect()));
this.addAbility(new SimpleStaticAbility(
Zone.ALL, new SpellCostReductionSourceEffect(GreatestPowerAmongControlledCreaturesValue.instance)
).setRuleAtTheTop(true).addHint(GreatestPowerAmongControlledCreaturesValue.getHint()));
// {T}: Add {G}{G}. You gain 2 life.
Ability ability = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.GreenMana(2), new TapSourceCost());
@ -59,42 +60,4 @@ public final class TheGreatHenge extends CardImpl {
public TheGreatHenge copy() {
return new TheGreatHenge(this);
}
}
class TheGreatHengeCostReductionEffect extends CostModificationEffectImpl {
TheGreatHengeCostReductionEffect() {
super(Duration.WhileOnStack, Outcome.Benefit, CostModificationType.REDUCE_COST);
staticText = "This spell costs {X} less to cast, where X is the greatest power among creatures you control";
}
private TheGreatHengeCostReductionEffect(final TheGreatHengeCostReductionEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source, Ability abilityToModify) {
int reductionAmount = game.getBattlefield()
.getAllActivePermanents(
StaticFilters.FILTER_PERMANENT_CREATURE, abilityToModify.getControllerId(), game
).stream()
.map(Permanent::getPower)
.mapToInt(MageInt::getValue)
.max()
.orElse(0);
CardUtil.reduceCost(abilityToModify, Math.max(0, reductionAmount));
return true;
}
@Override
public boolean applies(Ability abilityToModify, Ability source, Game game) {
return abilityToModify instanceof SpellAbility
&& abilityToModify.getSourceId().equals(source.getSourceId())
&& game.getCard(abilityToModify.getSourceId()) != null;
}
@Override
public TheGreatHengeCostReductionEffect copy() {
return new TheGreatHengeCostReductionEffect(this);
}
}
}

View file

@ -0,0 +1,167 @@
package mage.cards.t;
import mage.MageItem;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.dynamicvalue.common.GreatestPowerAmongControlledCreaturesValue;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeGroupEvent;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.FungusDinosaurToken;
import mage.target.common.TargetCreaturePermanent;
import mage.target.targetpointer.FixedTarget;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* @author Susucr
*/
public final class TheSkullsporeNexus extends CardImpl {
public TheSkullsporeNexus(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{6}{G}{G}");
this.supertype.add(SuperType.LEGENDARY);
// This spell costs {X} less to cast, where X is the greatest power among creatures you control.
this.addAbility(new SimpleStaticAbility(
Zone.ALL, new SpellCostReductionSourceEffect(GreatestPowerAmongControlledCreaturesValue.instance)
).setRuleAtTheTop(true).addHint(GreatestPowerAmongControlledCreaturesValue.getHint()));
// Whenever one or more nontoken creatures you control die, create a green Fungus Dinosaur creature token with base power and toughness each equal to the total power of those creatures.
this.addAbility(new TheSkullsporeNexusTrigger());
// {2}, {T}: Double target creature's power until end of turn.
Ability ability = new SimpleActivatedAbility(
new TheSkullsporeNexusDoubleEffect(),
new GenericManaCost(2)
);
ability.addCost(new TapSourceCost());
ability.addTarget(new TargetCreaturePermanent());
this.addAbility(ability);
}
private TheSkullsporeNexus(final TheSkullsporeNexus card) {
super(card);
}
@Override
public TheSkullsporeNexus copy() {
return new TheSkullsporeNexus(this);
}
}
class TheSkullsporeNexusTrigger extends TriggeredAbilityImpl {
TheSkullsporeNexusTrigger() {
super(Zone.BATTLEFIELD, null, false);
}
private TheSkullsporeNexusTrigger(final TheSkullsporeNexusTrigger ability) {
super(ability);
}
@Override
public TheSkullsporeNexusTrigger copy() {
return new TheSkullsporeNexusTrigger(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ZONE_CHANGE_GROUP;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
ZoneChangeGroupEvent zEvent = (ZoneChangeGroupEvent) event;
if (!zEvent.isDiesEvent()) {
return false;
}
List<Permanent> permanents = zEvent.getCards().stream()
.map(MageItem::getId)
.map(game::getPermanentOrLKIBattlefield)
.filter(Objects::nonNull)
.filter(p -> p.isCreature(game) && p.isControlledBy(getControllerId()))
.collect(Collectors.toList());
if (permanents.isEmpty()) {
return false;
}
int amount = permanents.stream().mapToInt(p -> p.getPower().getValue()).sum();
this.getEffects().clear();
Effect effect = new CreateTokenEffect(new FungusDinosaurToken(amount));
effect.setValue(infoKey, amount);
this.getEffects().add(effect);
return true;
}
private static final String infoKey = "totalpower";
@Override
public String getRule() {
// this trigger might want to expose extra info on the stack
String triggeredInfo = "";
if (!this.getEffects().isEmpty()) {
triggeredInfo += "<br><br><i>Total power: " + this.getEffects().get(0).getValue(infoKey) + "</i>";
}
return "Whenever one or more nontoken creatures you control die, "
+ "create a green Fungus Dinosaur creature token with base power and toughness "
+ "each equal to the total power of those creatures." + triggeredInfo;
}
@Override
public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game);
}
}
class TheSkullsporeNexusDoubleEffect extends OneShotEffect {
TheSkullsporeNexusDoubleEffect() {
super(Outcome.Benefit);
staticText = "double target creature's power until end of turn";
}
private TheSkullsporeNexusDoubleEffect(final TheSkullsporeNexusDoubleEffect effect) {
super(effect);
}
@Override
public TheSkullsporeNexusDoubleEffect copy() {
return new TheSkullsporeNexusDoubleEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getFirstTarget());
if (permanent == null) {
return false;
}
ContinuousEffect boost = new BoostTargetEffect(permanent.getPower().getValue(), 0, Duration.EndOfTurn)
.setTargetPointer(new FixedTarget(permanent, game));
game.addEffect(boost, source);
return true;
}
}

View file

@ -30,5 +30,6 @@ public final class LostCavernsOfIxalan extends ExpansionSet {
cards.add(new SetCardInfo("Plains", 287, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS));
cards.add(new SetCardInfo("Swamp", 289, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS));
cards.add(new SetCardInfo("Temple of Power", 317, Rarity.MYTHIC, mage.cards.t.TempleOfPower.class));
cards.add(new SetCardInfo("The Skullspore Nexus", 212, Rarity.MYTHIC, mage.cards.t.TheSkullsporeNexus.class));
}
}

View file

@ -0,0 +1,68 @@
package org.mage.test.cards.single.lci;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author Susucr
*/
public class TheSkullsporeNexusTest extends CardTestPlayerBase {
/**
* {@link mage.cards.t.TheSkullsporeNexus} <br>
* The Skullspore Nexus {6}{G}{G} <br>
* Legendary Artifact <br>
* This spell costs {X} less to cast, where X is the greatest power among creatures you control. <br>
* Whenever one or more nontoken creatures you control die, create a green Fungus Dinosaur creature token with base power and toughness each equal to the total power of those creatures. <br>
* {2}, {T}: Double target creatures power until end of turn. <br>
*/
private static final String nexus = "The Skullspore Nexus";
@Test
public void test_trigger() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, nexus);
addCard(Zone.BATTLEFIELD, playerA, "Butcher Ghoul"); // 1/1 undying
addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears"); // 2/2 doesn't count as not controlled
addCard(Zone.HAND, playerA, "Grave Titan"); // 6/6, etb with 2 2/2 tokens
addCard(Zone.HAND, playerA, "Damnation", 2); // destroy all creatures.
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 6 + 4 * 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grave Titan", true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Damnation", true);
setChoice(playerA, "Whenever one or more nontoken creatures you control die"); // ordering triggers
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
// Only 2 creatures do count: 6 from Titan + 1 from Ghoul
checkPT("after first damnation", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Fungus Dinosaur Token", 7, 7);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Damnation", true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
// only undying ghoul counts (for 2)
assertPowerToughness(playerA, "Fungus Dinosaur Token", 2, 2);
}
@Test
public void test_activation() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, nexus);
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); // 2/2
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, {T}: Double", "Grizzly Bears");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertPowerToughness(playerA, "Grizzly Bears", 4, 2);
assertTappedCount("Swamp", true, 2);
assertTapped(nexus, true);
}
}

View file

@ -1,11 +1,12 @@
package mage.game.events;
import mage.abilities.Ability;
import mage.cards.Card;
import mage.constants.Zone;
import mage.game.permanent.PermanentToken;
import java.util.Set;
import java.util.UUID;
import mage.abilities.Ability;
/**
* @author LevelX2
@ -18,7 +19,7 @@ public class ZoneChangeGroupEvent extends GameEvent {
private final Set<PermanentToken> tokens;
/* added this */ Ability source;
public ZoneChangeGroupEvent(Set<Card> cards, Set<PermanentToken> tokens, UUID sourceId, Ability source, UUID playerId, Zone fromZone, Zone toZone) {
public ZoneChangeGroupEvent(Set<Card> cards, Set<PermanentToken> tokens, UUID sourceId, Ability source, UUID playerId, Zone fromZone, Zone toZone) {
super(GameEvent.EventType.ZONE_CHANGE_GROUP, null, null, playerId);
this.fromZone = fromZone;
this.toZone = toZone;
@ -35,6 +36,10 @@ public class ZoneChangeGroupEvent extends GameEvent {
return toZone;
}
public boolean isDiesEvent() {
return (toZone == Zone.GRAVEYARD && fromZone == Zone.BATTLEFIELD);
}
public Set<Card> getCards() {
return cards;
}
@ -42,7 +47,7 @@ public class ZoneChangeGroupEvent extends GameEvent {
public Set<PermanentToken> getTokens() {
return tokens;
}
public Ability getSource() {
return source;
}

View file

@ -0,0 +1,29 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SubType;
/**
* @author Susucr
*/
public final class FungusDinosaurToken extends TokenImpl {
public FungusDinosaurToken(int xValue) {
super("Fungus Dinosaur Token", "X/X green Fungus Dinosaur creature token");
cardType.add(CardType.CREATURE);
subtype.add(SubType.FUNGUS);
subtype.add(SubType.DINOSAUR);
color.setGreen(true);
power = new MageInt(xValue);
toughness = new MageInt(xValue);
}
private FungusDinosaurToken(final FungusDinosaurToken token) {
super(token);
}
public FungusDinosaurToken copy() {
return new FungusDinosaurToken(this);
}
}