forked from External/mage
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:
parent
fb82303b85
commit
29ce4b1ad4
4 changed files with 187 additions and 4 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue