Skip to content

Commit

Permalink
Merge pull request #12893 from magefree/implement-tmp-phyrexian-splicer
Browse files Browse the repository at this point in the history
[TMP] Implement Phyrexian Splicer (part of #5379)
  • Loading branch information
JayDi85 authored Sep 20, 2024
2 parents fb63fe0 + f55bc2c commit ff02322
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Mage.Sets/src/mage/cards/a/AkromaAngelOfWrath.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ public AkromaAngelOfWrath(UUID ownerId, CardSetInfo setInfo) {
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));
}

Expand Down
242 changes: 242 additions & 0 deletions Mage.Sets/src/mage/cards/p/PhyrexianSplicer.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
1 change: 1 addition & 0 deletions Mage.Sets/src/mage/sets/Tempest.java
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ private Tempest() {
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));
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1125,7 +1125,7 @@ private void tryToPlayPriority(Game game) {
}

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;
Expand All @@ -1139,6 +1139,9 @@ private Permanent findPermanentWithAssert(PlayerAction action, Game game, 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;
}
Expand Down

0 comments on commit ff02322

Please sign in to comment.