Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 29 additions & 13 deletions Mage.Sets/src/mage/cards/w/WhipgrassEntangler.java
Original file line number Diff line number Diff line change
@@ -1,45 +1,45 @@

package mage.cards.w;

import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.LinkedSimpleStaticAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
import mage.abilities.effects.common.combat.CantAttackBlockUnlessPaysSourceEffect;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.common.TargetCreaturePermanent;

import java.util.UUID;

/**
*
* @author emerald000 & L_J
* @author Susucr
*/
public final class WhipgrassEntangler extends CardImpl {

public WhipgrassEntangler(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{W}");
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.CLERIC);
this.power = new MageInt(1);
this.toughness = new MageInt(3);

// {1}{W}: Until end of turn, target creature gains "This creature can't attack or block unless its controller pays {1} for each Cleric on the battlefield."
Ability abilityToGain = new SimpleStaticAbility(Zone.BATTLEFIELD, new WhipgrassEntanglerCantAttackUnlessYouPayEffect());
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD,
new GainAbilityTargetEffect(abilityToGain, Duration.EndOfTurn).setText("Until end of turn, target creature gains \"This creature can't attack or block unless its controller pays {1} for each Cleric on the battlefield.\""),
Ability gainedAbility = new LinkedSimpleStaticAbility(new WhipgrassEntanglerCantAttackUnlessYouPayEffect());
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD,
new GainAbilityTargetEffect(gainedAbility, Duration.EndOfTurn).setText("Until end of turn, target creature gains \"This creature can't attack or block unless its controller pays {1} for each Cleric on the battlefield.\""),
new ManaCostsImpl<>("{1}{W}"));
ability.addTarget(new TargetCreaturePermanent());
this.addAbility(ability);
Expand All @@ -56,9 +56,10 @@ public WhipgrassEntangler copy() {
}
}

class WhipgrassEntanglerCantAttackUnlessYouPayEffect extends CantAttackBlockUnlessPaysSourceEffect {
class WhipgrassEntanglerCantAttackUnlessYouPayEffect extends CantAttackBlockUnlessPaysSourceEffect implements LinkedSimpleStaticAbility.ChildEffect {

private static final FilterPermanent filter = new FilterPermanent("Cleric on the battlefield");
private UUID parentLinkHandshake = null;

static {
filter.add(SubType.CLERIC.getPredicate());
Expand All @@ -69,13 +70,16 @@ class WhipgrassEntanglerCantAttackUnlessYouPayEffect extends CantAttackBlockUnle
staticText = "This creature can't attack or block unless its controller pays {1} for each Cleric on the battlefield";
}

WhipgrassEntanglerCantAttackUnlessYouPayEffect(WhipgrassEntanglerCantAttackUnlessYouPayEffect effect) {
private WhipgrassEntanglerCantAttackUnlessYouPayEffect(final WhipgrassEntanglerCantAttackUnlessYouPayEffect effect) {
super(effect);
this.parentLinkHandshake = effect.parentLinkHandshake;
}

@Override
public boolean applies(GameEvent event, Ability source, Game game) {
return source.getSourceId().equals(event.getSourceId());
return source.getSourceId().equals(event.getSourceId())
&& source instanceof LinkedSimpleStaticAbility
&& ((LinkedSimpleStaticAbility) source).checkLinked(this.parentLinkHandshake);
}

@Override
Expand All @@ -95,4 +99,16 @@ public WhipgrassEntanglerCantAttackUnlessYouPayEffect copy() {
return new WhipgrassEntanglerCantAttackUnlessYouPayEffect(this);
}

@Override
public void newId() {
// No id set this way. Parent Ability takes responsability of calling manualNewId()
}

public void manualNewId() {
super.newId();
}

public void setParentLinkHandshake(UUID parentLinkHandshake) {
this.parentLinkHandshake = parentLinkHandshake;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package org.mage.test.cards.single.lgn;

import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;

public class WhipgrassEntanglerTest extends CardTestPlayerBase {

/**
* Whipgrass Entangler
* {2}{W}
* Creature — Human Cleric
* <p>
* {1}{W}: Until end of turn, target creature gains "This creature can't attack or block unless its controller pays {1} for each Cleric on the battlefield."
* <p>
* 1/3
*/
private static final String entangler = "Whipgrass Entangler";

@Test
public void gainAbilityHimselfAndPay() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, entangler);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 20);

activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}: Until end of turn, ", entangler);
attack(1, playerA, entangler);
setChoice(playerA, true); // yes to "pay {1} for Entangler to attack"

setStopAt(1, PhaseStep.END_TURN);
execute();

assertLife(playerB, 20 - 1);
assertTappedCount("Plains", true, 2 * 1 + 1 * 1);
}

@Test
public void gainAbilityTwiceAndPay() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, entangler);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 20);

activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}: Until end of turn, ", entangler);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}: Until end of turn, ", entangler);
attack(1, playerA, entangler);
setChoice(playerA, "Whipgrass Entangler"); // two of the same replacement effect to choose from.
setChoice(playerA, true); // yes to "pay {1} for Entangler to attack"
setChoice(playerA, true); // yes to "pay {1} for Entangler to attack"

setStopAt(1, PhaseStep.END_TURN);
execute();

assertLife(playerB, 20 - 1);
assertTappedCount("Plains", true, 2 * 2 + 1 * 2);
}

@Test
public void gainAbilityWithTwoClericAndPay() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, entangler);
addCard(Zone.BATTLEFIELD, playerA, "Akroma's Devoted"); // 2/4 Cleric
addCard(Zone.BATTLEFIELD, playerA, "Plains", 20);

activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}: Until end of turn, ", entangler);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}: Until end of turn, ", "Akroma's Devoted");
attack(1, playerA, entangler);
setChoice(playerA, true); // yes to "pay {2} for Entangler to attack"
attack(1, playerA, "Akroma's Devoted");
setChoice(playerA, true); // yes to "pay {2} for Akroma's Devoted to attack"

setStopAt(1, PhaseStep.END_TURN);
execute();

assertLife(playerB, 20 - 1 - 2);
assertTappedCount("Plains", true, 2 * 2 + 2 * 2);
}

@Test
public void gainAbilityFromTwoEntanglerAndPay() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Akroma's Devoted"); // 2/4 Cleric
addCard(Zone.BATTLEFIELD, playerA, entangler, 1);

// In order to make sure the ability is activated from both entanglers, we exile one and play the second one after
addCard(Zone.HAND, playerA, entangler);
addCard(Zone.HAND, playerA, "Swords to Plowshares", 1);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 20);

// Activate first entangler
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}: Until end of turn, ", "Akroma's Devoted");
// Exile first entangler
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Swords to Plowshares", entangler, true);

// Cast second entangler
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, entangler, true);
// Activate second entangler
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}: Until end of turn, ", "Akroma's Devoted");

attack(1, playerA, "Akroma's Devoted");
setChoice(playerA, "Akroma's Devoted"); // two of the same replacement effect to choose from.
setChoice(playerA, true); // yes to "pay {2} for Akroma's Devoted to attack"
setChoice(playerA, true); // yes to "pay {2} for Akroma's Devoted to attack"

setStopAt(1, PhaseStep.END_TURN);
execute();

assertLife(playerB, 20 - 2);
assertTappedCount("Plains", true, 2 * 2 + 2 * 2 + 3 + 1);
}

@Test
public void gainAbilityWithCloneAndPay() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Akroma's Devoted"); // 2/4 Cleric
addCard(Zone.BATTLEFIELD, playerA, entangler);
addCard(Zone.HAND, playerA, "Sakashima the Impostor"); // Clone with different name for easy targets

// In order to make sure the ability is activated from the clone, we exile the original one before activating Sakashima
addCard(Zone.HAND, playerA, "Swords to Plowshares", 1);
addCard(Zone.BATTLEFIELD, playerA, "Tundra", 20);

// Activate the original Entangler
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}: Until end of turn, ", "Akroma's Devoted");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
// Cast Sakashima
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sakashima the Impostor", true);
setChoice(playerA, true); // yes to clone.
setChoice(playerA, entangler); // copy entangler
// Exile original entangler
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Swords to Plowshares", entangler, true);
// Activate Sakashima.
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}: Until end of turn, ", "Akroma's Devoted");

attack(1, playerA, "Akroma's Devoted");
setChoice(playerA, "Akroma's Devoted"); // two of the same replacement effect to choose from.
setChoice(playerA, true); // yes to "pay {2} for Akroma's Devoted to attack"
setChoice(playerA, true); // yes to "pay {2} for Akroma's Devoted to attack"

setStopAt(1, PhaseStep.END_TURN);
execute();

assertLife(playerB, 20 - 2);
assertTappedCount("Tundra", true, 2 * 2 + 2 * 2 + 4 + 1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package mage.abilities.common;

import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.util.CardUtil;

import java.util.Objects;
import java.util.UUID;

/**
* Warning: please test with a lot of care when using this class for new things.
* <p>
* A static Ability linked to an Effect.
* The parent Ability does take responsability of setting the id for the child.
*
* @author Susucr
*/
public class LinkedSimpleStaticAbility extends SimpleStaticAbility {

public interface ChildEffect extends Effect {
/**
* Set the link for the child.
*/
void setParentLinkHandshake(UUID parentLinkHandshake);

/**
* The child Id should only change on copy when the parent wants it to.
*/
void manualNewId();
}


/**
* The handshake UUID between this parent ability and its child.
*/
private UUID linkedHandshake;

public LinkedSimpleStaticAbility(ChildEffect effect) {
this(Zone.BATTLEFIELD, effect);
}

public LinkedSimpleStaticAbility(Zone zone, ChildEffect effect) {
super(Zone.BATTLEFIELD, effect);
this.linkedHandshake = UUID.randomUUID();
initHandshake();
setEffectIdManually();
}

private LinkedSimpleStaticAbility(final LinkedSimpleStaticAbility effect) {
super(effect);
this.linkedHandshake = UUID.randomUUID();
initHandshake();
}

@Override
public LinkedSimpleStaticAbility copy() {
return new LinkedSimpleStaticAbility(this);
}

private void initHandshake() {
this.linkedHandshake = UUID.randomUUID();
CardUtil.castStream(this.getEffects().stream(), ChildEffect.class)
.filter(Objects::nonNull)
.forEach(e -> e.setParentLinkHandshake(linkedHandshake));
}

public void setEffectIdManually() {
CardUtil.castStream(this.getEffects().stream(), ChildEffect.class)
.filter(Objects::nonNull)
.forEach(e -> e.manualNewId());
}

public boolean checkLinked(UUID handshake) {
return linkedHandshake.equals(handshake);
}

@Override
public void newId() {
super.newId();
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;

/**
* @author LevelX2
Expand All @@ -26,7 +25,7 @@ public CantAttackBlockUnlessPaysSourceEffect(ManaCosts manaCosts, RestrictType r
staticText = "{this} can't " + restrictType.toString() + " unless you pay " + (manaCosts == null ? "" : manaCosts.getText());
}

public CantAttackBlockUnlessPaysSourceEffect(CantAttackBlockUnlessPaysSourceEffect effect) {
protected CantAttackBlockUnlessPaysSourceEffect(final CantAttackBlockUnlessPaysSourceEffect effect) {
super(effect);
}

Expand Down
Loading