mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 02:30:08 -08:00
[TMP] Implement Phyrexian Splicer (part of #5379)
This commit is contained in:
parent
fb63fe0318
commit
f55bc2c4fc
5 changed files with 291 additions and 2 deletions
|
|
@ -25,12 +25,12 @@ public final class AkromaAngelOfWrath extends CardImpl {
|
|||
this.power = new MageInt(6);
|
||||
this.toughness = new MageInt(6);
|
||||
|
||||
// Flying, first strike, vigilance, trample, haste, protection from black and from red
|
||||
this.addAbility(FlyingAbility.getInstance());
|
||||
this.addAbility(FirstStrikeAbility.getInstance());
|
||||
this.addAbility(VigilanceAbility.getInstance());
|
||||
this.addAbility(TrampleAbility.getInstance());
|
||||
this.addAbility(HasteAbility.getInstance());
|
||||
// protection from black and from red
|
||||
this.addAbility(ProtectionAbility.from(ObjectColor.BLACK, ObjectColor.RED));
|
||||
}
|
||||
|
||||
|
|
|
|||
242
Mage.Sets/src/mage/cards/p/PhyrexianSplicer.java
Normal file
242
Mage.Sets/src/mage/cards/p/PhyrexianSplicer.java
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
package mage.cards.p;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.CostImpl;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.ContinuousEffect;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
|
||||
import mage.abilities.effects.common.continuous.LoseAbilityTargetEffect;
|
||||
import mage.abilities.hint.Hint;
|
||||
import mage.abilities.keyword.FirstStrikeAbility;
|
||||
import mage.abilities.keyword.FlyingAbility;
|
||||
import mage.abilities.keyword.ShadowAbility;
|
||||
import mage.abilities.keyword.TrampleAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.choices.Choice;
|
||||
import mage.choices.ChoiceImpl;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.filter.predicate.mageobject.AbilityPredicate;
|
||||
import mage.filter.predicate.other.AnotherTargetPredicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.stack.StackAbility;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author JayDi85
|
||||
*/
|
||||
public final class PhyrexianSplicer extends CardImpl {
|
||||
|
||||
static final FilterCreaturePermanent filterLose = new FilterCreaturePermanent("creature with the chosen ability");
|
||||
private static final FilterCreaturePermanent filterGain = new FilterCreaturePermanent("another target creature");
|
||||
|
||||
static {
|
||||
filterLose.add(Predicates.or(
|
||||
new AbilityPredicate(FlyingAbility.class),
|
||||
new AbilityPredicate(FirstStrikeAbility.class),
|
||||
new AbilityPredicate(TrampleAbility.class),
|
||||
new AbilityPredicate(ShadowAbility.class)
|
||||
));
|
||||
filterLose.add(new AnotherTargetPredicate(1));
|
||||
|
||||
filterGain.add(new AnotherTargetPredicate(2));
|
||||
}
|
||||
|
||||
public PhyrexianSplicer(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}");
|
||||
|
||||
// {2}, {T}, Choose flying, first strike, trample, or shadow: Until end of turn, target creature with the chosen ability loses it and another target creature gains it.
|
||||
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new PhyrexianSplicerEffect(), new ManaCostsImpl<>("{2}"));
|
||||
ability.addCost(new TapSourceCost());
|
||||
ability.addCost(new PhyrexianSplicerChooseCost());
|
||||
ability.addTarget(new TargetPermanent(filterLose).withChooseHint("to lose ability").setTargetTag(1));
|
||||
ability.addTarget(new TargetPermanent(filterGain).withChooseHint("to gain ability").setTargetTag(2));
|
||||
ability.addHint(PhyrexianSplicerCardHint.instance);
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
private PhyrexianSplicer(final PhyrexianSplicer card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PhyrexianSplicer copy() {
|
||||
return new PhyrexianSplicer(this);
|
||||
}
|
||||
}
|
||||
|
||||
class PhyrexianSplicerEffect extends OneShotEffect {
|
||||
|
||||
PhyrexianSplicerEffect() {
|
||||
super(Outcome.LoseAbility);
|
||||
this.staticText = "Until end of turn, target creature with the chosen ability loses it and another target creature gains it.";
|
||||
}
|
||||
|
||||
private PhyrexianSplicerEffect(final PhyrexianSplicerEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PhyrexianSplicerEffect copy() {
|
||||
return new PhyrexianSplicerEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Ability loseAbility = findChosenAbility(source);
|
||||
if (loseAbility == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the target which is having the ability removed does not have that ability during the resolution of this
|
||||
// effect, then this effect still grants the chosen ability. The reason is that the second target is still
|
||||
// legal even if the first one is not.
|
||||
// (2004-10-04)
|
||||
|
||||
Permanent targetLose = game.getPermanent(source.getTargets().get(0).getFirstTarget());
|
||||
Permanent targetGain = game.getPermanent(source.getTargets().get(1).getFirstTarget());
|
||||
if (targetGain == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// lose
|
||||
if (targetLose != null) {
|
||||
ContinuousEffect effect = new LoseAbilityTargetEffect(loseAbility, Duration.EndOfTurn);
|
||||
effect.setTargetPointer(new FixedTarget(targetLose, game));
|
||||
game.addEffect(effect, source);
|
||||
}
|
||||
|
||||
// gain
|
||||
ContinuousEffect effect = new GainAbilityTargetEffect(loseAbility, Duration.EndOfTurn);
|
||||
effect.setTargetPointer(new FixedTarget(targetGain, game));
|
||||
game.addEffect(effect, source);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static Ability findChosenAbility(Ability source) {
|
||||
return CardUtil
|
||||
.castStream(source.getCosts().stream(), PhyrexianSplicerChooseCost.class)
|
||||
.map(PhyrexianSplicerChooseCost::getTargetedAbility)
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
class PhyrexianSplicerChooseCost extends CostImpl {
|
||||
|
||||
private static final Map<String, Ability> allChoices = new LinkedHashMap<>();
|
||||
|
||||
static {
|
||||
allChoices.put("Flying", FlyingAbility.getInstance());
|
||||
allChoices.put("First Strike", FirstStrikeAbility.getInstance());
|
||||
allChoices.put("Trample", TrampleAbility.getInstance());
|
||||
allChoices.put("Shadow", ShadowAbility.getInstance());
|
||||
}
|
||||
|
||||
Ability targetedAbility = null;
|
||||
|
||||
public PhyrexianSplicerChooseCost() {
|
||||
this.text = "Choose flying, first strike, trample, or shadow";
|
||||
}
|
||||
|
||||
private PhyrexianSplicerChooseCost(final PhyrexianSplicerChooseCost cost) {
|
||||
super(cost);
|
||||
this.targetedAbility = cost.targetedAbility == null ? null : cost.targetedAbility.copy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
|
||||
this.paid = false;
|
||||
this.targetedAbility = null;
|
||||
|
||||
Permanent losePermanent = game.getPermanent(source.getTargets().get(0).getFirstTarget());
|
||||
Permanent gainPermanent = game.getPermanent(source.getTargets().get(1).getFirstTarget());
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (losePermanent == null || gainPermanent == null || controller == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// choose ability to lose
|
||||
Set<String> choices = allChoices.entrySet().stream()
|
||||
.filter(entry -> losePermanent.hasAbility(entry.getValue(), game))
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
|
||||
Ability chosenAbility;
|
||||
if (choices.size() == 1) {
|
||||
chosenAbility = allChoices.getOrDefault(choices.stream().findFirst().orElse(null), null);
|
||||
} else {
|
||||
Choice choice = new ChoiceImpl(true);
|
||||
choice.setMessage("Choose ability to remove from " + losePermanent.getLogName() + " to " + gainPermanent.getLogName());
|
||||
choice.setChoices(choices);
|
||||
controller.choose(Outcome.LoseAbility, choice, game);
|
||||
chosenAbility = allChoices.getOrDefault(choice.getChoice(), null);
|
||||
}
|
||||
if (chosenAbility == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// all fine
|
||||
this.targetedAbility = chosenAbility;
|
||||
paid = true;
|
||||
|
||||
// additional logs
|
||||
game.informPlayers(controller.getLogName() + " chosen ability to lose and gain: "
|
||||
+ CardUtil.getTextWithFirstCharUpperCase(chosenAbility.getRule()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PhyrexianSplicerChooseCost copy() {
|
||||
return new PhyrexianSplicerChooseCost(this);
|
||||
}
|
||||
|
||||
Ability getTargetedAbility() {
|
||||
return this.targetedAbility;
|
||||
}
|
||||
}
|
||||
|
||||
enum PhyrexianSplicerCardHint implements Hint {
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public String getText(Game game, Ability ability) {
|
||||
// works on stack only
|
||||
if (ability instanceof StackAbility) {
|
||||
Ability loseAbility = PhyrexianSplicerEffect.findChosenAbility(((StackAbility) ability).getStackAbility());
|
||||
if (loseAbility != null) {
|
||||
return String.format("Chosen ability to lose and gain: " + CardUtil.getTextWithFirstCharUpperCase(loseAbility.getRule()));
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public PhyrexianSplicerCardHint copy() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
@ -232,6 +232,7 @@ public final class Tempest extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Perish", 147, Rarity.UNCOMMON, mage.cards.p.Perish.class));
|
||||
cards.add(new SetCardInfo("Phyrexian Grimoire", 301, Rarity.RARE, mage.cards.p.PhyrexianGrimoire.class));
|
||||
cards.add(new SetCardInfo("Phyrexian Hulk", 302, Rarity.UNCOMMON, mage.cards.p.PhyrexianHulk.class));
|
||||
cards.add(new SetCardInfo("Phyrexian Splicer", 303, Rarity.UNCOMMON, mage.cards.p.PhyrexianSplicer.class));
|
||||
cards.add(new SetCardInfo("Pincher Beetles", 244, Rarity.COMMON, mage.cards.p.PincherBeetles.class));
|
||||
cards.add(new SetCardInfo("Pine Barrens", 321, Rarity.RARE, mage.cards.p.PineBarrens.class));
|
||||
cards.add(new SetCardInfo("Pit Imp", 148, Rarity.COMMON, mage.cards.p.PitImp.class));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
package org.mage.test.cards.single.tmp;
|
||||
|
||||
import mage.abilities.keyword.TrampleAbility;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class PhyrexianSplicerTest extends CardTestPlayerBase {
|
||||
|
||||
@Test
|
||||
public void test_Normal() {
|
||||
// {2}, {T}, Choose flying, first strike, trample, or shadow: Until end of turn, target creature with the
|
||||
// chosen ability loses it and another target creature gains it.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Phyrexian Splicer", 1); // {2}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
|
||||
//
|
||||
// Flying, first strike, vigilance, trample, haste, protection from black and from red
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Akroma, Angel of Wrath");
|
||||
// Shadow
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Augur il-Vec");
|
||||
|
||||
checkAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Wrath", TrampleAbility.class, true);
|
||||
checkAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Augur il-Vec", TrampleAbility.class, false);
|
||||
|
||||
// move trample from one to another
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, {T}, Choose");
|
||||
addTarget(playerA, "Akroma, Angel of Wrath"); // loose
|
||||
addTarget(playerA, "Augur il-Vec"); // gain
|
||||
setChoice(playerA, "Trample");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
checkAbility("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Wrath", TrampleAbility.class, false);
|
||||
checkAbility("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Augur il-Vec", TrampleAbility.class, true);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
}
|
||||
}
|
||||
|
|
@ -1125,7 +1125,7 @@ public class TestPlayer implements Player {
|
|||
}
|
||||
|
||||
private Permanent findPermanentWithAssert(PlayerAction action, Game game, Player player, String cardName) {
|
||||
for (Permanent perm : game.getBattlefield().getAllPermanents()) {
|
||||
for (Permanent perm : game.getBattlefield().getAllActivePermanents(player.getId())) {
|
||||
// need by controller
|
||||
if (!perm.getControllerId().equals(player.getId())) {
|
||||
continue;
|
||||
|
|
@ -1139,6 +1139,9 @@ public class TestPlayer implements Player {
|
|||
// all fine
|
||||
return perm;
|
||||
}
|
||||
printStart(game, "Permanents of " + player.getName());
|
||||
printPermanents(game, game.getBattlefield().getAllActivePermanents(player.getId()), this);
|
||||
printEnd();
|
||||
Assert.fail(action.getActionName() + " - can't find permanent to check: " + cardName);
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue