Big Damage Rewrite Part 2: now use CombatDamageMap everywhere

This commit is contained in:
Hanmac
2016-12-30 14:50:45 +00:00
parent 4aa68c3fa6
commit 1738f49798
11 changed files with 175 additions and 216 deletions

View File

@@ -20,6 +20,7 @@ package forge.game;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardDamageMap;
import forge.game.card.CounterType;
import forge.game.event.GameEventCardAttachment;
import forge.game.event.GameEventCardAttachment.AttachMethod;
@@ -55,22 +56,51 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
getView().updateName(this);
}
public int addDamage(final int damage, final Card source) {
public int addDamage(final int damage, final Card source, final CardDamageMap damageMap) {
int damageToDo = damage;
damageToDo = replaceDamage(damageToDo, source, false);
damageToDo = replaceDamage(damageToDo, source, false, true, damageMap);
damageToDo = preventDamage(damageToDo, source, false);
return addDamageAfterPrevention(damageToDo, source, false);
return addDamageAfterPrevention(damageToDo, source, false, damageMap);
}
public int addDamageWithoutPrevention(final int damage, final Card source) {
int damageToDo = replaceDamage(damage, source, false);
return addDamageAfterPrevention(damageToDo, source, false);
public int addDamageWithoutPrevention(final int damage, final Card source, final CardDamageMap damageMap) {
int damageToDo = replaceDamage(damage, source, false, false, damageMap);
return addDamageAfterPrevention(damageToDo, source, false, damageMap);
}
public int replaceDamage(final int damage, final Card source, final boolean isCombat, final boolean prevention, final CardDamageMap damageMap) {
// Replacement effects
final Map<String, Object> repParams = Maps.newHashMap();
repParams.put("Event", "DamageDone");
repParams.put("Affected", this);
repParams.put("DamageSource", source);
repParams.put("DamageAmount", damage);
repParams.put("IsCombat", isCombat);
switch (getGame().getReplacementHandler().run(repParams)) {
case NotReplaced:
return damage;
case Updated:
int newDamage = (int) repParams.get("DamageAmount");
GameEntity newTarget = (GameEntity)repParams.get("Affected");
// check if this is still the affected card or player
if (this.equals(newTarget)) {
return newDamage;
} else {
if (prevention) {
newDamage = newTarget.preventDamage(newDamage, source, isCombat);
}
newTarget.addDamageAfterPrevention(newDamage, source, isCombat, damageMap);
}
default:
return 0;
}
}
// This function handles damage after replacement and prevention effects are applied
public abstract int addDamageAfterPrevention(final int damage, final Card source, final boolean isCombat);
public abstract int addDamageAfterPrevention(final int damage, final Card source, final boolean isCombat, CardDamageMap damageMap);
// This should be also usable by the AI to forecast an effect (so it must
// not change the game state)
@@ -80,8 +110,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
// not change the game state)
public abstract int staticReplaceDamage(final int damage, final Card source, final boolean isCombat);
public abstract int replaceDamage(final int damage, final Card source, final boolean isCombat);
public abstract int preventDamage(final int damage, final Card source, final boolean isCombat);
public int getPreventNextDamage() {

View File

@@ -1,11 +1,13 @@
package forge.game.ability.effects;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardDamageMap;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -26,12 +28,13 @@ public class DamageAllEffect extends SpellAbilityEffect {
final String damage = sa.getParam("NumDmg");
final int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
final String definedStr = sa.getParam("DamageSource");
final List<Card> definedSources = AbilityUtils.getDefinedCards(sa.getHostCard(), definedStr, sa);
final List<Card> definedSources = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DamageSource"), sa);
final Card source = definedSources.get(0);
if (source != sa.getHostCard()) {
sb.append(source.toString()).append(" deals");
if (!definedSources.isEmpty() && definedSources.get(0) != sa.getHostCard()) {
sb.append(definedSources.get(0).toString()).append(" deals");
} else if ("ParentTarget".equals(definedStr)){
sb.append("Target creature deals");
} else {
sb.append("Deals");
}
@@ -77,28 +80,30 @@ public class DamageAllEffect extends SpellAbilityEffect {
list = AbilityUtils.filterListByType(list, sa.getParam("ValidCards"), sa);
int damageSum = 0;
CardDamageMap damageMap = new CardDamageMap();
for (final Card c : list) {
int cardDamage = c.addDamage(dmg, sourceLKI);
damageSum += cardDamage;
if (cardDamage > 0 && rememberCard) {
source.addRemembered(c);
}
c.addDamage(dmg, sourceLKI, damageMap);
}
if (!players.equals("")) {
final List<Player> playerList = AbilityUtils.getDefinedPlayers(card, players, sa);
for (final Player p : playerList) {
int playerDamage = p.addDamage(dmg, sourceLKI);
damageSum += playerDamage;
if (playerDamage > 0 && rememberPlayer) {
source.addRemembered(p);
p.addDamage(dmg, sourceLKI, damageMap);
}
}
// do Remember there
if (rememberCard || rememberPlayer) {
for (GameEntity e : damageMap.row(sourceLKI).keySet()) {
if (e instanceof Card && rememberCard) {
source.addRemembered(e);
} else if (e instanceof Player && rememberPlayer) {
source.addRemembered(e);
}
}
}
if (damageSum > 0 && sourceLKI.hasKeyword("Lifelink")) {
source.getController().gainLife(damageSum, sourceLKI);
}
damageMap.dealLifelinkDamage();
}
}

View File

@@ -1,16 +1,17 @@
package forge.game.ability.effects;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardDamageMap;
import forge.game.card.CardUtil;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.Lang;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -35,10 +36,9 @@ public class DamageDealEffect extends SpellAbilityEffect {
return "";
final List<Card> definedSources = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DamageSource"), sa);
Card source = definedSources.isEmpty() ? new Card(-1, sa.getHostCard().getGame()) : definedSources.get(0);
if (source != sa.getHostCard()) {
sb.append(source.toString()).append(" deals");
if (!definedSources.isEmpty() && definedSources.get(0) != sa.getHostCard()) {
sb.append(definedSources.get(0).toString()).append(" deals");
} else {
sb.append("Deals");
}
@@ -68,6 +68,9 @@ public class DamageDealEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
final Card hostCard = sa.getHostCard();
final Game game = hostCard.getGame();
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
@@ -120,7 +123,8 @@ public class DamageDealEffect extends SpellAbilityEffect {
final Card source = definedSources.get(0);
final Card sourceLKI = sa.getHostCard().getGame().getChangeZoneLKIInfo(definedSources.get(0));
int damageSum = 0;
// make a new damage map, combat damage will be applied later into combat map
CardDamageMap damageMap = new CardDamageMap();
if (divideOnResolution) {
// Dividing Damage up to multiple targets using combat damage box
@@ -141,12 +145,15 @@ public class DamageDealEffect extends SpellAbilityEffect {
Player assigningPlayer = players.get(0);
Map<Card, Integer> map = assigningPlayer.getController().assignCombatDamage(sourceLKI, assigneeCards, dmg, null, true);
for (Entry<Card, Integer> dt : map.entrySet()) {
damageSum += dt.getKey().addDamage(dt.getValue(), sourceLKI);
dt.getKey().addDamage(dt.getValue(), sourceLKI, damageMap);
}
// transport combat damage back into combat damage map
if (combatDmg) {
game.getCombat().getDamageMap().putAll(damageMap);
} else {
// non combat damage cause lifegain there
if (!combatDmg && damageSum > 0 && sourceLKI.hasKeyword("Lifelink")) {
sourceLKI.getController().gainLife(damageSum, sourceLKI);
damageMap.dealLifelinkDamage();
}
return;
}
@@ -162,55 +169,39 @@ public class DamageDealEffect extends SpellAbilityEffect {
c.clearAssignedDamage();
}
else if (noPrevention) {
int damagePrev = c.addDamageWithoutPrevention(dmg, sourceLKI);
damageSum += damagePrev;
if (damagePrev > 0 && remember) {
source.addRemembered(c);
}
c.addDamageWithoutPrevention(dmg, sourceLKI, damageMap);
} else if (combatDmg) {
HashMap<Card, Integer> combatmap = Maps.newHashMap();
Map<Card, Integer> combatmap = Maps.newHashMap();
combatmap.put(sourceLKI, dmg);
c.addCombatDamage(combatmap);
if (remember) {
source.addRemembered(c);
}
c.addCombatDamage(combatmap, damageMap);
} else {
int damageDealt = c.addDamage(dmg, sourceLKI);
damageSum += damageDealt;
if (damageDealt > 0 && remember) {
source.addRemembered(c);
c.addDamage(dmg, sourceLKI, damageMap);
}
}
}
} else if (o instanceof Player) {
final Player p = (Player) o;
if (!targeted || p.canBeTargetedBy(sa)) {
if (noPrevention) {
int damagePrev = p.addDamageWithoutPrevention(dmg, sourceLKI);
damageSum += damagePrev;
if (damagePrev > 0 && remember) {
source.addRemembered(p);
}
p.addDamageWithoutPrevention(dmg, sourceLKI, damageMap);
} else if (combatDmg) {
p.addCombatDamage(dmg, sourceLKI);
if (remember) {
source.addRemembered(p);
}
p.addCombatDamage(dmg, sourceLKI, damageMap);
} else {
int damageDealt = p.addDamage(dmg, sourceLKI);
damageSum += damageDealt;
if (damageDealt > 0 && remember) {
source.addRemembered(p);
}
p.addDamage(dmg, sourceLKI, damageMap);
}
}
}
}
if (remember) {
source.addRemembered(damageMap.row(sourceLKI).keySet());
}
// transport combat damage back into combat damage map
if (combatDmg) {
game.getCombat().getDamageMap().putAll(damageMap);
} else {
// non combat damage cause lifegain there
if (!combatDmg && damageSum > 0 && sourceLKI.hasKeyword("Lifelink")) {
sourceLKI.getController().gainLife(damageSum, sourceLKI);
damageMap.dealLifelinkDamage();
}
}
}

View File

@@ -4,6 +4,7 @@ import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardDamageMap;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.player.Player;
@@ -69,29 +70,27 @@ public class DamageEachEffect extends SpellAbilityEffect {
final List<GameObject> tgts = getTargets(sa, "DefinedPlayers");
final boolean targeted = (sa.usesTargeting());
CardDamageMap damageMap = new CardDamageMap();
for (final Object o : tgts) {
for (final Card source : sources) {
final Card sourceLKI = source.getGame().getChangeZoneLKIInfo(source);
final int dmg = CardFactoryUtil.xCount(source, sa.getSVar("X"));
int damageSum = 0;
// System.out.println(source+" deals "+dmg+" damage to "+o.toString());
if (o instanceof Card) {
final Card c = (Card) o;
if (c.isInPlay() && (!targeted || c.canBeTargetedBy(sa))) {
damageSum += c.addDamage(dmg, sourceLKI);
c.addDamage(dmg, sourceLKI, damageMap);
}
} else if (o instanceof Player) {
final Player p = (Player) o;
if (!targeted || p.canBeTargetedBy(sa)) {
damageSum += p.addDamage(dmg, sourceLKI);
p.addDamage(dmg, sourceLKI, damageMap);
}
}
if (damageSum > 0 && sourceLKI.hasKeyword("Lifelink")) {
sourceLKI.getController().gainLife(damageSum, sourceLKI);
}
}
}
@@ -102,10 +101,7 @@ public class DamageEachEffect extends SpellAbilityEffect {
final int dmg = CardFactoryUtil.xCount(source, card.getSVar("X"));
// System.out.println(source+" deals "+dmg+" damage to "+source);
int damage = source.addDamage(dmg, sourceLKI);
if (damage > 0 && sourceLKI.hasKeyword("Lifelink")) {
sourceLKI.getController().gainLife(damage, sourceLKI);
}
source.addDamage(dmg, sourceLKI, damageMap);
}
}
if (sa.getParam("DefinedCards").equals("Remembered")) {
@@ -113,20 +109,17 @@ public class DamageEachEffect extends SpellAbilityEffect {
final int dmg = CardFactoryUtil.xCount(source, card.getSVar("X"));
final Card sourceLKI = source.getGame().getChangeZoneLKIInfo(source);
Card rememberedcard;
int damageSum = 0;
for (final Object o : sa.getHostCard().getRemembered()) {
if (o instanceof Card) {
rememberedcard = (Card) o;
Card rememberedcard = (Card) o;
// System.out.println(source + " deals " + dmg + " damage to " + rememberedcard);
damageSum += rememberedcard.addDamage(dmg, sourceLKI);
}
}
if (damageSum > 0 && sourceLKI.hasKeyword("Lifelink")) {
sourceLKI.getController().gainLife(damageSum, sourceLKI);
rememberedcard.addDamage(dmg, sourceLKI, damageMap);
}
}
}
}
}
damageMap.dealLifelinkDamage();
}
}

View File

@@ -7,11 +7,12 @@ import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardDamageMap;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FightEffect extends SpellAbilityEffect {
@@ -52,12 +53,15 @@ public class FightEffect extends SpellAbilityEffect {
}
boolean fightToughness = sa.hasParam("FightWithToughness");
CardDamageMap damageMap = new CardDamageMap();
dealDamage(fighters.get(0), fighters.get(1), fightToughness);
dealDamage(fighters.get(1), fighters.get(0), fightToughness);
dealDamage(fighters.get(0), fighters.get(1), fightToughness, damageMap);
dealDamage(fighters.get(1), fighters.get(0), fightToughness, damageMap);
damageMap.dealLifelinkDamage();
for (Card c : fighters) {
final HashMap<String, Object> runParams = Maps.newHashMap();
final Map<String, Object> runParams = Maps.newHashMap();
runParams.put("Fighter", c);
game.getTriggerHandler().runTrigger(TriggerType.Fight, runParams, false);
}
@@ -106,14 +110,10 @@ public class FightEffect extends SpellAbilityEffect {
return fighterList;
}
private void dealDamage(Card source, Card target, boolean fightToughness) {
private void dealDamage(Card source, Card target, boolean fightToughness, CardDamageMap damageMap) {
final int dmg = fightToughness ? source.getNetToughness() : source.getNetPower();
int damageDealt = target.addDamage(dmg, source);
if (damageDealt > 0 && source.hasKeyword("Lifelink")) {
source.getController().gainLife(damageDealt, source);
}
target.addDamage(dmg, source, damageMap);
}
}

View File

@@ -5844,12 +5844,12 @@ public class Card extends GameEntity implements Comparable<Card> {
return total;
}
public final void addCombatDamage(final Map<Card, Integer> map) {
public final void addCombatDamage(final Map<Card, Integer> map, CardDamageMap damageMap) {
for (final Entry<Card, Integer> entry : map.entrySet()) {
final Card source = entry.getKey();
int damageToAdd = entry.getValue();
damageToAdd = replaceDamage(damageToAdd, source, true);
damageToAdd = replaceDamage(damageToAdd, source, true, true, damageMap);
damageToAdd = preventDamage(damageToAdd, source, true);
if (damageToAdd > 0) {
@@ -5859,7 +5859,7 @@ public class Card extends GameEntity implements Comparable<Card> {
}
if (isInPlay()) {
addDamage(map);
addDamage(map, damageMap);
}
}
@@ -6142,33 +6142,10 @@ public class Card extends GameEntity implements Comparable<Card> {
return restDamage;
}
@Override
public final int replaceDamage(final int damageIn, final Card source, final boolean isCombat) {
// Replacement effects
final Map<String, Object> repParams = Maps.newHashMap();
repParams.put("Event", "DamageDone");
repParams.put("Affected", this);
repParams.put("DamageSource", source);
repParams.put("DamageAmount", damageIn);
repParams.put("IsCombat", isCombat);
switch (getGame().getReplacementHandler().run(repParams)) {
case NotReplaced:
return damageIn;
case Updated:
// check if this is still the affected card
if (this.equals(repParams.get("Affected"))) {
return (int) repParams.get("DamageAmount");
}
default:
return 0;
}
}
public final void addDamage(final Map<Card, Integer> sourcesMap) {
public final void addDamage(final Map<Card, Integer> sourcesMap, CardDamageMap damageMap) {
for (final Entry<Card, Integer> entry : sourcesMap.entrySet()) {
// damage prevention is already checked!
addDamageAfterPrevention(entry.getValue(), entry.getKey(), true);
addDamageAfterPrevention(entry.getValue(), entry.getKey(), true, damageMap);
}
}
@@ -6177,7 +6154,7 @@ public class Card extends GameEntity implements Comparable<Card> {
* applied.
*/
@Override
public final int addDamageAfterPrevention(final int damageIn, final Card source, final boolean isCombat) {
public final int addDamageAfterPrevention(final int damageIn, final Card source, final boolean isCombat, CardDamageMap damageMap) {
if (damageIn == 0) {
return 0; // Rule 119.8
@@ -6185,9 +6162,6 @@ public class Card extends GameEntity implements Comparable<Card> {
addReceivedDamageFromThisTurn(source, damageIn);
source.addDealtDamageToThisTurn(this, damageIn);
if (isCombat) {
game.getCombat().addDealtDamageTo(source, this, damageIn);
}
// Run triggers
final Map<String, Object> runParams = Maps.newTreeMap();
@@ -6231,6 +6205,11 @@ public class Card extends GameEntity implements Comparable<Card> {
// Play the Damage sound
game.fireEvent(new GameEventCardDamaged(this, source, damageIn, damageType));
}
if (damageIn > 0) {
damageMap.put(source, this, damageIn);
}
return damageIn;
}

View File

@@ -27,7 +27,6 @@ import org.apache.commons.lang3.tuple.Pair;
import com.google.common.base.Function;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -40,6 +39,7 @@ import forge.game.GameObjectMap;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardDamageMap;
import forge.game.player.Player;
import forge.game.trigger.TriggerType;
import forge.util.collect.FCollection;
@@ -68,7 +68,7 @@ public class Combat {
private Map<Card, CardCollection> attackersOrderedForDamageAssignment = Maps.newHashMap();
private Map<Card, CardCollection> blockersOrderedForDamageAssignment = Maps.newHashMap();
private Map<GameEntity, CombatLki> lkiCache = Maps.newHashMap();
private Table<Card, GameEntity, Integer> dealtDamageTo = HashBasedTable.create();
private CardDamageMap dealtDamageTo = new CardDamageMap();
// List holds creatures who have dealt 1st strike damage to disallow them deal damage on regular basis (unless they have double-strike KW)
private CardCollection combatantsThatDealtFirstStrikeDamage = new CardCollection();
@@ -716,45 +716,21 @@ public class Combat {
return assignedDamage;
}
public final void addDealtDamageTo(final Card source, final GameEntity ge, final int dmg) {
int old = dealtDamageTo.contains(source, ge) ? dealtDamageTo.get(source, ge) : 0;
dealtDamageTo.put(source, ge, dmg + old);
public final CardDamageMap getDamageMap() {
return dealtDamageTo;
}
public void dealAssignedDamage() {
playerWhoAttacks.getGame().copyLastState();
// This function handles both Regular and First Strike combat assignment
final Map<Card, Integer> defMap = defendingDamageMap;
final Map<GameEntity, CardCollection> wasDamaged = Maps.newHashMap();
for (final Entry<Card, Integer> entry : defMap.entrySet()) {
for (final Entry<Card, Integer> entry : defendingDamageMap.entrySet()) {
GameEntity defender = getDefenderByAttacker(entry.getKey());
if (defender instanceof Player) { // player
int dmg = ((Player) defender).addCombatDamage(entry.getValue(), entry.getKey());
if (dmg > 0) {
if (wasDamaged.containsKey(defender)) {
wasDamaged.get(defender).add(entry.getKey());
} else {
CardCollection l = new CardCollection();
l.add(entry.getKey());
wasDamaged.put(defender, l);
}
this.addDealtDamageTo(entry.getKey(), defender, entry.getValue());
}
((Player) defender).addCombatDamage(entry.getValue(), entry.getKey(), dealtDamageTo);
}
else if (defender instanceof Card) { // planeswalker
int dmg = ((Card) defender).getController().addCombatDamage(entry.getValue(), entry.getKey());
if (dmg > 0) {
if (wasDamaged.containsKey(defender)) {
wasDamaged.get(defender).add(entry.getKey());
}
else {
CardCollection l = new CardCollection();
l.add(entry.getKey());
wasDamaged.put(defender, l);
}
}
((Card) defender).getController().addCombatDamage(entry.getValue(), entry.getKey(), dealtDamageTo);
}
}
@@ -765,41 +741,39 @@ public class Combat {
combatants.addAll(getAllBlockers());
combatants.addAll(getDefendingPlaneswalkers());
Card c;
for (int i = 0; i < combatants.size(); i++) {
c = combatants.get(i);
for (final Card c : combatants) {
// if no assigned damage to resolve, move to next
if (c.getTotalAssignedDamage() == 0) {
continue;
}
c.addCombatDamage(c.getAssignedDamageMap());
c.addCombatDamage(c.getAssignedDamageMap(), dealtDamageTo);
c.clearAssignedDamage();
}
// Run triggers
for (final GameEntity ge : wasDamaged.keySet()) {
for (final GameEntity ge : dealtDamageTo.columnKeySet()) {
final Map<String, Object> runParams = Maps.newHashMap();
runParams.put("DamageSources", wasDamaged.get(ge));
runParams.put("DamageSources", dealtDamageTo.column(ge).keySet());
runParams.put("DamageTarget", ge);
ge.getGame().getTriggerHandler().runTrigger(TriggerType.CombatDamageDoneOnce, runParams, false);
}
// This was deeper before, but that resulted in the stack entry acting like before.
// LifeLink for Combat Damage at this place
dealtDamageTo.dealLifelinkDamage();
// when ... deals combat damage to one or more
for (final Card damageSource : dealtDamageTo.rowKeySet()) {
final Map<String, Object> runParams = Maps.newHashMap();
Map<GameEntity, Integer> row = dealtDamageTo.row(damageSource);
// TODO find better way to get the sum
int dealtDamage = 0;
for (Map.Entry<GameEntity, Integer> e : row.entrySet()) {
dealtDamage += e.getValue();
}
// LifeLink for Combat Damage at this place
if (dealtDamage > 0 && damageSource.hasKeyword("Lifelink")) {
damageSource.getController().gainLife(dealtDamage, damageSource);
for (Integer i : row.values()) {
dealtDamage += i;
}
runParams.put("DamageSource", damageSource);
runParams.put("DamageTargets", row.keySet());
runParams.put("DamageAmount", dealtDamage);

View File

@@ -18,6 +18,7 @@
package forge.game.cost;
import forge.game.card.Card;
import forge.game.card.CardDamageMap;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -60,10 +61,12 @@ public class CostDamage extends CostPart {
@Override
public boolean payAsDecided(Player payer, PaymentDecision decision, SpellAbility sa) {
final Card source = sa.getHostCard();
int dmg = payer.addDamage(decision.c, source);
if (dmg > 0 && source.hasKeyword("Lifelink")) {
source.getController().gainLife(dmg, source);
}
CardDamageMap damageMap = new CardDamageMap();
payer.addDamage(decision.c, source, damageMap);
damageMap.dealLifelinkDamage();
return decision.c > 0;
}

View File

@@ -534,15 +534,12 @@ public class Player extends GameEntity implements Comparable<Player> {
// This function handles damage after replacement and prevention effects are applied
@Override
public final int addDamageAfterPrevention(final int amount, final Card source, final boolean isCombat) {
public final int addDamageAfterPrevention(final int amount, final Card source, final boolean isCombat, CardDamageMap damageMap) {
if (amount <= 0) {
return 0;
}
//String additionalLog = "";
source.addDealtDamageToPlayerThisTurn(getName(), amount);
if (isCombat) {
game.getCombat().addDealtDamageTo(source, this, amount);
}
boolean infect = source.hasKeyword("Infect")
|| hasKeyword("All damage is dealt to you as though its source had infect.");
@@ -553,8 +550,10 @@ public class Player extends GameEntity implements Comparable<Player> {
else {
// Worship does not reduce the damage dealt but changes the effect
// of the damage
if (hasKeyword("Damage that would reduce your life total to less than 1 reduces it to 1 instead.")
&& life <= amount) {
if (hasKeyword("DamageLifeThreshold:7") && life - 7 <= amount) {
// only active if life is over 7, so no bad thing
loseLife(Math.min(amount, life - 7));
} else if (hasKeyword("DamageLifeThreshold:1") && life <= amount) {
loseLife(Math.min(amount, life - 1));
}
else {
@@ -589,6 +588,10 @@ public class Player extends GameEntity implements Comparable<Player> {
game.getTriggerHandler().runTrigger(TriggerType.DamageDone, runParams, false);
game.fireEvent(new GameEventPlayerDamaged(this, source, amount, isCombat, infect));
if (amount > 0) {
damageMap.put(source, this, amount);
}
return amount;
}
@@ -648,7 +651,9 @@ public class Player extends GameEntity implements Comparable<Player> {
public final int staticReplaceDamage(final int damage, final Card source, final boolean isCombat) {
int restDamage = damage;
if (hasKeyword("Damage that would reduce your life total to less than 1 reduces it to 1 instead.")) {
if (hasKeyword("DamageLifeThreshold:7")) {
restDamage = Math.min(restDamage, life - 7);
} else if (hasKeyword("DamageLifeThreshold:1")) {
restDamage = Math.min(restDamage, life - 1);
}
@@ -716,29 +721,6 @@ public class Player extends GameEntity implements Comparable<Player> {
return restDamage;
}
@Override
public final int replaceDamage(final int damage, final Card source, final boolean isCombat) {
// Replacement effects
final Map<String, Object> repParams = Maps.newHashMap();
repParams.put("Event", "DamageDone");
repParams.put("Affected", this);
repParams.put("DamageSource", source);
repParams.put("DamageAmount", damage);
repParams.put("IsCombat", isCombat);
switch (getGame().getReplacementHandler().run(repParams)) {
case NotReplaced:
return damage;
case Updated:
// check if this is still the affected player
if (this.equals(repParams.get("Affected"))) {
return (int) repParams.get("DamageAmount");
}
default:
return 0;
}
}
@Override
public final int preventDamage(final int damage, final Card source, final boolean isCombat) {
if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention)
@@ -885,13 +867,13 @@ public class Player extends GameEntity implements Comparable<Player> {
return Aggregates.max(getOpponents(), Accessors.FN_GET_ASSIGNED_DAMAGE);
}
public final int addCombatDamage(final int damage, final Card source) {
public final int addCombatDamage(final int damage, final Card source, CardDamageMap damageMap) {
int damageToDo = damage;
damageToDo = replaceDamage(damageToDo, source, true);
damageToDo = replaceDamage(damageToDo, source, true, true, damageMap);
damageToDo = preventDamage(damageToDo, source, true);
addDamageAfterPrevention(damageToDo, source, true); // damage prevention is already checked
damageToDo = addDamageAfterPrevention(damageToDo, source, true, damageMap); // damage prevention is already checked
if (damageToDo > 0) {
source.getDamageHistory().registerCombatDamage(this);

View File

@@ -21,7 +21,7 @@ import forge.game.GameEntity;
import forge.game.card.Card;
import forge.game.spellability.SpellAbility;
import java.util.List;
import java.util.Set;
/**
* <p>
@@ -53,7 +53,7 @@ public class TriggerCombatDamageDoneOnce extends Trigger {
@SuppressWarnings("unchecked")
@Override
public final boolean performTest(final java.util.Map<String, Object> runParams2) {
final List<Card> srcs = (List<Card>) runParams2.get("DamageSources");
final Set<Card> srcs = (Set<Card>) runParams2.get("DamageSources");
final GameEntity tgt = (GameEntity) runParams2.get("DamageTarget");
if (this.mapParams.containsKey("ValidSource")) {

View File

@@ -25,6 +25,7 @@ import forge.game.ability.effects.FlipCoinEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardDamageMap;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates.Presets;
@@ -385,8 +386,11 @@ public class HumanPlay {
if (!p.getController().confirmPayment(part, "Do you want " + source + " to deal " + amount + " damage to you?")) {
return false;
}
CardDamageMap damageMap = new CardDamageMap();
p.addDamage(amount, source);
p.addDamage(amount, source, damageMap);
damageMap.dealLifelinkDamage();
}
else if (part instanceof CostPutCounter) {
CounterType counterType = ((CostPutCounter) part).getCounter();