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.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardDamageMap;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.event.GameEventCardAttachment; import forge.game.event.GameEventCardAttachment;
import forge.game.event.GameEventCardAttachment.AttachMethod; import forge.game.event.GameEventCardAttachment.AttachMethod;
@@ -55,22 +56,51 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
getView().updateName(this); 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; int damageToDo = damage;
damageToDo = replaceDamage(damageToDo, source, false); damageToDo = replaceDamage(damageToDo, source, false, true, damageMap);
damageToDo = preventDamage(damageToDo, source, false); 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) { public int addDamageWithoutPrevention(final int damage, final Card source, final CardDamageMap damageMap) {
int damageToDo = replaceDamage(damage, source, false); int damageToDo = replaceDamage(damage, source, false, false, damageMap);
return addDamageAfterPrevention(damageToDo, source, false); 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 // 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 // This should be also usable by the AI to forecast an effect (so it must
// not change the game state) // not change the game state)
@@ -80,8 +110,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
// not change the game state) // not change the game state)
public abstract int staticReplaceDamage(final int damage, final Card source, final boolean isCombat); 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 abstract int preventDamage(final int damage, final Card source, final boolean isCombat);
public int getPreventNextDamage() { public int getPreventNextDamage() {

View File

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

View File

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

View File

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

View File

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

View File

@@ -5844,12 +5844,12 @@ public class Card extends GameEntity implements Comparable<Card> {
return total; 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()) { for (final Entry<Card, Integer> entry : map.entrySet()) {
final Card source = entry.getKey(); final Card source = entry.getKey();
int damageToAdd = entry.getValue(); int damageToAdd = entry.getValue();
damageToAdd = replaceDamage(damageToAdd, source, true); damageToAdd = replaceDamage(damageToAdd, source, true, true, damageMap);
damageToAdd = preventDamage(damageToAdd, source, true); damageToAdd = preventDamage(damageToAdd, source, true);
if (damageToAdd > 0) { if (damageToAdd > 0) {
@@ -5859,7 +5859,7 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
if (isInPlay()) { if (isInPlay()) {
addDamage(map); addDamage(map, damageMap);
} }
} }
@@ -6142,33 +6142,10 @@ public class Card extends GameEntity implements Comparable<Card> {
return restDamage; return restDamage;
} }
@Override public final void addDamage(final Map<Card, Integer> sourcesMap, CardDamageMap damageMap) {
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) {
for (final Entry<Card, Integer> entry : sourcesMap.entrySet()) { for (final Entry<Card, Integer> entry : sourcesMap.entrySet()) {
// damage prevention is already checked! // 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. * applied.
*/ */
@Override @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) { if (damageIn == 0) {
return 0; // Rule 119.8 return 0; // Rule 119.8
@@ -6185,9 +6162,6 @@ public class Card extends GameEntity implements Comparable<Card> {
addReceivedDamageFromThisTurn(source, damageIn); addReceivedDamageFromThisTurn(source, damageIn);
source.addDealtDamageToThisTurn(this, damageIn); source.addDealtDamageToThisTurn(this, damageIn);
if (isCombat) {
game.getCombat().addDealtDamageTo(source, this, damageIn);
}
// Run triggers // Run triggers
final Map<String, Object> runParams = Maps.newTreeMap(); final Map<String, Object> runParams = Maps.newTreeMap();
@@ -6231,6 +6205,11 @@ public class Card extends GameEntity implements Comparable<Card> {
// Play the Damage sound // Play the Damage sound
game.fireEvent(new GameEventCardDamaged(this, source, damageIn, damageType)); game.fireEvent(new GameEventCardDamaged(this, source, damageIn, damageType));
} }
if (damageIn > 0) {
damageMap.put(source, this, damageIn);
}
return 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.base.Function;
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
@@ -40,6 +39,7 @@ import forge.game.GameObjectMap;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardDamageMap;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
import forge.util.collect.FCollection; import forge.util.collect.FCollection;
@@ -68,7 +68,7 @@ public class Combat {
private Map<Card, CardCollection> attackersOrderedForDamageAssignment = Maps.newHashMap(); private Map<Card, CardCollection> attackersOrderedForDamageAssignment = Maps.newHashMap();
private Map<Card, CardCollection> blockersOrderedForDamageAssignment = Maps.newHashMap(); private Map<Card, CardCollection> blockersOrderedForDamageAssignment = Maps.newHashMap();
private Map<GameEntity, CombatLki> lkiCache = 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) // 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(); private CardCollection combatantsThatDealtFirstStrikeDamage = new CardCollection();
@@ -716,45 +716,21 @@ public class Combat {
return assignedDamage; return assignedDamage;
} }
public final void addDealtDamageTo(final Card source, final GameEntity ge, final int dmg) { public final CardDamageMap getDamageMap() {
int old = dealtDamageTo.contains(source, ge) ? dealtDamageTo.get(source, ge) : 0; return dealtDamageTo;
dealtDamageTo.put(source, ge, dmg + old);
} }
public void dealAssignedDamage() { public void dealAssignedDamage() {
playerWhoAttacks.getGame().copyLastState(); playerWhoAttacks.getGame().copyLastState();
// This function handles both Regular and First Strike combat assignment // This function handles both Regular and First Strike combat assignment
final Map<Card, Integer> defMap = defendingDamageMap; for (final Entry<Card, Integer> entry : defendingDamageMap.entrySet()) {
final Map<GameEntity, CardCollection> wasDamaged = Maps.newHashMap();
for (final Entry<Card, Integer> entry : defMap.entrySet()) {
GameEntity defender = getDefenderByAttacker(entry.getKey()); GameEntity defender = getDefenderByAttacker(entry.getKey());
if (defender instanceof Player) { // player if (defender instanceof Player) { // player
int dmg = ((Player) defender).addCombatDamage(entry.getValue(), entry.getKey()); ((Player) defender).addCombatDamage(entry.getValue(), entry.getKey(), dealtDamageTo);
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());
}
} }
else if (defender instanceof Card) { // planeswalker else if (defender instanceof Card) { // planeswalker
int dmg = ((Card) defender).getController().addCombatDamage(entry.getValue(), entry.getKey()); ((Card) defender).getController().addCombatDamage(entry.getValue(), entry.getKey(), dealtDamageTo);
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);
}
}
} }
} }
@@ -765,41 +741,39 @@ public class Combat {
combatants.addAll(getAllBlockers()); combatants.addAll(getAllBlockers());
combatants.addAll(getDefendingPlaneswalkers()); combatants.addAll(getDefendingPlaneswalkers());
Card c; for (final Card c : combatants) {
for (int i = 0; i < combatants.size(); i++) {
c = combatants.get(i);
// if no assigned damage to resolve, move to next // if no assigned damage to resolve, move to next
if (c.getTotalAssignedDamage() == 0) { if (c.getTotalAssignedDamage() == 0) {
continue; continue;
} }
c.addCombatDamage(c.getAssignedDamageMap()); c.addCombatDamage(c.getAssignedDamageMap(), dealtDamageTo);
c.clearAssignedDamage(); c.clearAssignedDamage();
} }
// Run triggers // Run triggers
for (final GameEntity ge : wasDamaged.keySet()) { for (final GameEntity ge : dealtDamageTo.columnKeySet()) {
final Map<String, Object> runParams = Maps.newHashMap(); final Map<String, Object> runParams = Maps.newHashMap();
runParams.put("DamageSources", wasDamaged.get(ge)); runParams.put("DamageSources", dealtDamageTo.column(ge).keySet());
runParams.put("DamageTarget", ge); runParams.put("DamageTarget", ge);
ge.getGame().getTriggerHandler().runTrigger(TriggerType.CombatDamageDoneOnce, runParams, false); ge.getGame().getTriggerHandler().runTrigger(TriggerType.CombatDamageDoneOnce, runParams, false);
} }
// This was deeper before, but that resulted in the stack entry acting like before. // 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 // when ... deals combat damage to one or more
for (final Card damageSource : dealtDamageTo.rowKeySet()) { for (final Card damageSource : dealtDamageTo.rowKeySet()) {
final Map<String, Object> runParams = Maps.newHashMap(); final Map<String, Object> runParams = Maps.newHashMap();
Map<GameEntity, Integer> row = dealtDamageTo.row(damageSource); Map<GameEntity, Integer> row = dealtDamageTo.row(damageSource);
// TODO find better way to get the sum
int dealtDamage = 0; int dealtDamage = 0;
for (Map.Entry<GameEntity, Integer> e : row.entrySet()) { for (Integer i : row.values()) {
dealtDamage += e.getValue(); dealtDamage += i;
}
// LifeLink for Combat Damage at this place
if (dealtDamage > 0 && damageSource.hasKeyword("Lifelink")) {
damageSource.getController().gainLife(dealtDamage, damageSource);
} }
runParams.put("DamageSource", damageSource); runParams.put("DamageSource", damageSource);
runParams.put("DamageTargets", row.keySet()); runParams.put("DamageTargets", row.keySet());
runParams.put("DamageAmount", dealtDamage); runParams.put("DamageAmount", dealtDamage);

View File

@@ -18,6 +18,7 @@
package forge.game.cost; package forge.game.cost;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardDamageMap;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -60,10 +61,12 @@ public class CostDamage extends CostPart {
@Override @Override
public boolean payAsDecided(Player payer, PaymentDecision decision, SpellAbility sa) { public boolean payAsDecided(Player payer, PaymentDecision decision, SpellAbility sa) {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
int dmg = payer.addDamage(decision.c, source); CardDamageMap damageMap = new CardDamageMap();
if (dmg > 0 && source.hasKeyword("Lifelink")) {
source.getController().gainLife(dmg, source); payer.addDamage(decision.c, source, damageMap);
}
damageMap.dealLifelinkDamage();
return decision.c > 0; 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 // This function handles damage after replacement and prevention effects are applied
@Override @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) { if (amount <= 0) {
return 0; return 0;
} }
//String additionalLog = ""; //String additionalLog = "";
source.addDealtDamageToPlayerThisTurn(getName(), amount); source.addDealtDamageToPlayerThisTurn(getName(), amount);
if (isCombat) {
game.getCombat().addDealtDamageTo(source, this, amount);
}
boolean infect = source.hasKeyword("Infect") boolean infect = source.hasKeyword("Infect")
|| hasKeyword("All damage is dealt to you as though its source had 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 { else {
// Worship does not reduce the damage dealt but changes the effect // Worship does not reduce the damage dealt but changes the effect
// of the damage // of the damage
if (hasKeyword("Damage that would reduce your life total to less than 1 reduces it to 1 instead.") if (hasKeyword("DamageLifeThreshold:7") && life - 7 <= amount) {
&& life <= 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)); loseLife(Math.min(amount, life - 1));
} }
else { else {
@@ -589,6 +588,10 @@ public class Player extends GameEntity implements Comparable<Player> {
game.getTriggerHandler().runTrigger(TriggerType.DamageDone, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.DamageDone, runParams, false);
game.fireEvent(new GameEventPlayerDamaged(this, source, amount, isCombat, infect)); game.fireEvent(new GameEventPlayerDamaged(this, source, amount, isCombat, infect));
if (amount > 0) {
damageMap.put(source, this, amount);
}
return 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) { public final int staticReplaceDamage(final int damage, final Card source, final boolean isCombat) {
int restDamage = damage; 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); restDamage = Math.min(restDamage, life - 1);
} }
@@ -716,29 +721,6 @@ public class Player extends GameEntity implements Comparable<Player> {
return restDamage; 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 @Override
public final int preventDamage(final int damage, final Card source, final boolean isCombat) { public final int preventDamage(final int damage, final Card source, final boolean isCombat) {
if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention) 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); 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; int damageToDo = damage;
damageToDo = replaceDamage(damageToDo, source, true); damageToDo = replaceDamage(damageToDo, source, true, true, damageMap);
damageToDo = preventDamage(damageToDo, source, true); 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) { if (damageToDo > 0) {
source.getDamageHistory().registerCombatDamage(this); source.getDamageHistory().registerCombatDamage(this);

View File

@@ -21,7 +21,7 @@ import forge.game.GameEntity;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import java.util.List; import java.util.Set;
/** /**
* <p> * <p>
@@ -53,7 +53,7 @@ public class TriggerCombatDamageDoneOnce extends Trigger {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public final boolean performTest(final java.util.Map<String, Object> runParams2) { 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"); final GameEntity tgt = (GameEntity) runParams2.get("DamageTarget");
if (this.mapParams.containsKey("ValidSource")) { 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.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardDamageMap;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates.Presets; 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?")) { if (!p.getController().confirmPayment(part, "Do you want " + source + " to deal " + amount + " damage to you?")) {
return false; return false;
} }
CardDamageMap damageMap = new CardDamageMap();
p.addDamage(amount, source); p.addDamage(amount, source, damageMap);
damageMap.dealLifelinkDamage();
} }
else if (part instanceof CostPutCounter) { else if (part instanceof CostPutCounter) {
CounterType counterType = ((CostPutCounter) part).getCounter(); CounterType counterType = ((CostPutCounter) part).getCounter();