Fix Lifelink so it triggers per source instead of per recipient in combat. (#6272)

* Fix Lifelink so it triggers once per source instead of once per recipient in combat.

* Undo an import collapse.

* Add more tests for lifelink.
This commit is contained in:
Samuel Sandeen 2020-02-08 21:31:50 -05:00 committed by GitHub
parent fb82303b85
commit 29ce4b1ad4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 187 additions and 4 deletions

View file

@ -0,0 +1,161 @@
package org.mage.test.combat;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
public class LifelinkInCombatTest extends CardTestPlayerBase {
@Test
public void testOneBlockerTrample() {
// 4/4 Trample Lifelink
addCard(Zone.BATTLEFIELD, playerA, "Elderwood Scion");
// Whenever you gain life, put a +1/+1 counter on target creature or enchantment you control.
addCard(Zone.BATTLEFIELD, playerA, "Heliod, Sun-Crowned");
// 2/2
addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears");
attack(1, playerA, "Elderwood Scion");
block(1, playerB, "Grizzly Bears", "Elderwood Scion");
setChoice(playerA, "X=2"); // Damage to Grizzly Bears
addTarget(playerA, "Elderwood Scion");
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, "Elderwood Scion", 5, 5);
assertLife(playerA, 24);
assertLife(playerB, 18);
}
@Test
public void testTwoBlockers() {
// 4/4 Lifelink
addCard(Zone.BATTLEFIELD, playerA, "Brion Stoutarm");
// Whenever you gain life, put a +1/+1 counter on target creature or enchantment you control.
addCard(Zone.BATTLEFIELD, playerA, "Heliod, Sun-Crowned");
// 1/1
addCard(Zone.BATTLEFIELD, playerB, "Boros Recruit", 2);
addCard(Zone.BATTLEFIELD, playerB, "Suntail Hawk", 1);
attack(1, playerA, "Brion Stoutarm");
block(1, playerB, "Boros Recruit", "Brion Stoutarm");
block(1, playerB, "Suntail Hawk", "Brion Stoutarm");
setChoice(playerA, "X=1"); // Damage assignment
setChoice(playerA, "X=1"); // Damage assignment
addTarget(playerA, "Brion Stoutarm");
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, "Brion Stoutarm", 5, 5);
assertLife(playerA, 24);
assertLife(playerB, 20);
}
@Test
public void testTwoBlockersTrample() {
// 4/4 Trample Lifelink
addCard(Zone.BATTLEFIELD, playerA, "Elderwood Scion");
addCard(Zone.BATTLEFIELD, playerA, "Heliod, Sun-Crowned");
// 1/1
addCard(Zone.BATTLEFIELD, playerB, "Boros Recruit", 1);
addCard(Zone.BATTLEFIELD, playerB, "Suntail Hawk", 1);
attack(1, playerA, "Elderwood Scion");
block(1, playerB, "Boros Recruit", "Elderwood Scion");
block(1, playerB, "Suntail Hawk", "Elderwood Scion");
setChoice(playerA, "X=1"); // Damage assignment
setChoice(playerA, "X=1"); // Damage assignment
addTarget(playerA, "Elderwood Scion");
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, "Elderwood Scion", 5, 5);
assertLife(playerA, 24);
assertLife(playerB, 18);
}
@Test
public void testDamagePrevented() {
// 2/2 Lifelink
addCard(Zone.BATTLEFIELD, playerA, "Ajani's Sunstriker");
// Whenever you gain life, put a +1/+1 counter on target creature or enchantment you control.
addCard(Zone.BATTLEFIELD, playerA, "Heliod, Sun-Crowned");
// 1/1 Protection from creatures
addCard(Zone.BATTLEFIELD, playerB, "Beloved Chaplain");
attack(1, playerA, "Ajani's Sunstriker");
block(1, playerB, "Beloved Chaplain", "Ajani's Sunstriker");
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, "Ajani's Sunstriker", 2, 2);
assertLife(playerA, 20);
assertLife(playerB, 20);
}
@Test
public void testSomeDamagePreventedTrample() {
// 4/4 Lifelink Trample
addCard(Zone.BATTLEFIELD, playerA, "Elderwood Scion");
// Whenever you gain life, put a +1/+1 counter on target creature or enchantment you control.
addCard(Zone.BATTLEFIELD, playerA, "Heliod, Sun-Crowned");
// 1/1 Protection from creatures
addCard(Zone.BATTLEFIELD, playerB, "Beloved Chaplain");
attack(1, playerA, "Elderwood Scion");
block(1, playerB, "Beloved Chaplain", "Elderwood Scion");
setChoice(playerA, "X=1");
addTarget(playerA, "Elderwood Scion");
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, "Elderwood Scion", 5, 5);
assertLife(playerA, 23);
assertLife(playerB, 17);
}
@Test
public void testPlayerDamagePrevented() {
// 4/4 Lifelink Trample
addCard(Zone.BATTLEFIELD, playerA, "Elderwood Scion");
// Whenever you gain life, put a +1/+1 counter on target creature or enchantment you control.
addCard(Zone.BATTLEFIELD, playerA, "Heliod, Sun-Crowned");
// 3/3
addCard(Zone.HAND, playerB, "Seht's Tiger");
addCard(Zone.BATTLEFIELD, playerB, "Plains", 4);
castSpell(1, PhaseStep.UPKEEP, playerB, "Seht's Tiger");
setChoice(playerB, "Green");
attack(1, playerA, "Elderwood Scion");
block(1, playerB, "Seht's Tiger", "Elderwood Scion");
setChoice(playerA, "X=3");
addTarget(playerA, "Elderwood Scion");
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, "Elderwood Scion", 5, 5);
assertLife(playerA, 23);
assertLife(playerB, 20);
}
}

View file

@ -132,6 +132,8 @@ public interface Permanent extends Card, Controllable {
*/
int markDamage(int damage, UUID sourceId, Game game, boolean preventable, boolean combat);
void markLifelink(int damage);
int applyDamage(Game game);
void removeAllDamage(Game game);

View file

@ -97,6 +97,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
protected List<UUID> bandedCards = new ArrayList<>();
protected Counters counters;
protected List<MarkedDamageInfo> markedDamage;
protected int markedLifelink;
protected int timesLoyaltyUsed = 0;
protected Map<String, String> info;
protected int createOrder;
@ -133,6 +134,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
this.blocking = permanent.blocking;
this.maxBlocks = permanent.maxBlocks;
this.deathtouched = permanent.deathtouched;
this.markedLifelink = permanent.markedLifelink;
for (Map.Entry<String, List<UUID>> entry : permanent.connectedCards.entrySet()) {
this.connectedCards.put(entry.getKey(), entry.getValue());
@ -886,8 +888,12 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
}
if (source != null && sourceAbilities != null) {
if (sourceAbilities.containsKey(LifelinkAbility.getInstance().getId())) {
Player player = game.getPlayer(sourceControllerId);
player.gainLife(damageDone, game, sourceId);
if (markDamage) {
game.getPermanent(sourceId).markLifelink(damageDone);
} else {
Player player = game.getPlayer(sourceControllerId);
player.gainLife(damageDone, game, sourceId);
}
}
if (sourceAbilities.containsKey(DeathtouchAbility.getInstance().getId())) {
deathtouched = true;
@ -912,6 +918,11 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
return damageDone;
}
@Override
public void markLifelink(int damage) {
markedLifelink += damage;
}
@Override
public int markDamage(int damageAmount, UUID sourceId, Game game, boolean preventable, boolean combat) {
return damage(damageAmount, sourceId, game, preventable, combat, true, null);
@ -919,6 +930,11 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
@Override
public int applyDamage(Game game) {
if (markedLifelink > 0) {
Player player = game.getPlayer(this.getControllerId());
player.gainLife(markedLifelink, game, this.getId());
markedLifelink = 0;
}
if (markedDamage == null) {
return 0;
}

View file

@ -2161,8 +2161,12 @@ public abstract class PlayerImpl implements Player, Serializable {
}
}
if (sourceAbilities != null && sourceAbilities.containsKey(LifelinkAbility.getInstance().getId())) {
Player player = game.getPlayer(sourceControllerId);
player.gainLife(actualDamage, game, sourceId);
if (combatDamage) {
game.getPermanent(sourceId).markLifelink(actualDamage);
} else {
Player player = game.getPlayer(sourceControllerId);
player.gainLife(actualDamage, game, sourceId);
}
}
// Unstable ability - Earl of Squirrel
if (sourceAbilities != null && sourceAbilities.containsKey(SquirrellinkAbility.getInstance().getId())) {