From 1738f49798cbbffed156510dff98c30bc5ec51f5 Mon Sep 17 00:00:00 2001 From: Hanmac Date: Fri, 30 Dec 2016 14:50:45 +0000 Subject: [PATCH] Big Damage Rewrite Part 2: now use CombatDamageMap everywhere --- .../src/main/java/forge/game/GameEntity.java | 46 +++++++++--- .../game/ability/effects/DamageAllEffect.java | 43 ++++++----- .../ability/effects/DamageDealEffect.java | 75 ++++++++----------- .../ability/effects/DamageEachEffect.java | 27 +++---- .../game/ability/effects/FightEffect.java | 24 +++--- .../src/main/java/forge/game/card/Card.java | 43 +++-------- .../main/java/forge/game/combat/Combat.java | 64 +++++----------- .../main/java/forge/game/cost/CostDamage.java | 11 ++- .../main/java/forge/game/player/Player.java | 48 ++++-------- .../trigger/TriggerCombatDamageDoneOnce.java | 4 +- .../src/main/java/forge/player/HumanPlay.java | 6 +- 11 files changed, 175 insertions(+), 216 deletions(-) diff --git a/forge-game/src/main/java/forge/game/GameEntity.java b/forge-game/src/main/java/forge/game/GameEntity.java index cf8693cf069..64f8adf37c5 100644 --- a/forge-game/src/main/java/forge/game/GameEntity.java +++ b/forge-game/src/main/java/forge/game/GameEntity.java @@ -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 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() { diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamageAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DamageAllEffect.java index 1cf0b00a90d..3968314fbc5 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DamageAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DamageAllEffect.java @@ -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,19 +28,20 @@ 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 definedSources = AbilityUtils.getDefinedCards(sa.getHostCard(), definedStr, sa); - final List 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"); } sb.append(" ").append(dmg).append(" damage to ").append(desc); - return sb.toString(); + return sb.toString(); } @Override @@ -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 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(); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java index 0f2e92fd35e..076ae7375d2 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java @@ -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 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 map = assigningPlayer.getController().assignCombatDamage(sourceLKI, assigneeCards, dmg, null, true); for (Entry dt : map.entrySet()) { - damageSum += dt.getKey().addDamage(dt.getValue(), sourceLKI); + dt.getKey().addDamage(dt.getValue(), sourceLKI, damageMap); } - // non combat damage cause lifegain there - if (!combatDmg && damageSum > 0 && sourceLKI.hasKeyword("Lifelink")) { - 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(); } 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 combatmap = Maps.newHashMap(); + Map 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); } } } } - // non combat damage cause lifegain there - if (!combatDmg && damageSum > 0 && sourceLKI.hasKeyword("Lifelink")) { - sourceLKI.getController().gainLife(damageSum, sourceLKI); + 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 + damageMap.dealLifelinkDamage(); } } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java index c727cb41be5..e009632cfc7 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java @@ -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 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); + rememberedcard.addDamage(dmg, sourceLKI, damageMap); } } - if (damageSum > 0 && sourceLKI.hasKeyword("Lifelink")) { - sourceLKI.getController().gainLife(damageSum, sourceLKI); - } } } } + + damageMap.dealLifelinkDamage(); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java b/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java index 75735e2e456..0518a2aa759 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java @@ -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,14 +53,17 @@ 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 runParams = Maps.newHashMap(); - runParams.put("Fighter", c); - game.getTriggerHandler().runTrigger(TriggerType.Fight, runParams, false); + final Map 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); } } diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 7c9ea98edcf..624d60d405c 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -5844,12 +5844,12 @@ public class Card extends GameEntity implements Comparable { return total; } - public final void addCombatDamage(final Map map) { + public final void addCombatDamage(final Map map, CardDamageMap damageMap) { for (final Entry 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 { } if (isInPlay()) { - addDamage(map); + addDamage(map, damageMap); } } @@ -6142,33 +6142,10 @@ public class Card extends GameEntity implements Comparable { return restDamage; } - @Override - public final int replaceDamage(final int damageIn, final Card source, final boolean isCombat) { - // Replacement effects - final Map 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 sourcesMap) { + public final void addDamage(final Map sourcesMap, CardDamageMap damageMap) { for (final Entry 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 { * 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 { addReceivedDamageFromThisTurn(source, damageIn); source.addDealtDamageToThisTurn(this, damageIn); - if (isCombat) { - game.getCombat().addDealtDamageTo(source, this, damageIn); - } // Run triggers final Map runParams = Maps.newTreeMap(); @@ -6231,6 +6205,11 @@ public class Card extends GameEntity implements Comparable { // Play the Damage sound game.fireEvent(new GameEventCardDamaged(this, source, damageIn, damageType)); } + + if (damageIn > 0) { + damageMap.put(source, this, damageIn); + } + return damageIn; } diff --git a/forge-game/src/main/java/forge/game/combat/Combat.java b/forge-game/src/main/java/forge/game/combat/Combat.java index c069f47a8c2..7d212903b10 100644 --- a/forge-game/src/main/java/forge/game/combat/Combat.java +++ b/forge-game/src/main/java/forge/game/combat/Combat.java @@ -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 attackersOrderedForDamageAssignment = Maps.newHashMap(); private Map blockersOrderedForDamageAssignment = Maps.newHashMap(); private Map lkiCache = Maps.newHashMap(); - private Table 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 defMap = defendingDamageMap; - final Map wasDamaged = Maps.newHashMap(); - - for (final Entry entry : defMap.entrySet()) { + for (final Entry 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 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 runParams = Maps.newHashMap(); Map row = dealtDamageTo.row(damageSource); - + + // TODO find better way to get the sum int dealtDamage = 0; - for (Map.Entry 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); diff --git a/forge-game/src/main/java/forge/game/cost/CostDamage.java b/forge-game/src/main/java/forge/game/cost/CostDamage.java index 8ddb7818c42..ae45ff1a6c8 100644 --- a/forge-game/src/main/java/forge/game/cost/CostDamage.java +++ b/forge-game/src/main/java/forge/game/cost/CostDamage.java @@ -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; } diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index b32e1a1a74c..74617e4aafd 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -534,15 +534,12 @@ public class Player extends GameEntity implements Comparable { // 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 { 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 { 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 { 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 { return restDamage; } - @Override - public final int replaceDamage(final int damage, final Card source, final boolean isCombat) { - // Replacement effects - final Map 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 { 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); diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerCombatDamageDoneOnce.java b/forge-game/src/main/java/forge/game/trigger/TriggerCombatDamageDoneOnce.java index 16f567c2fd0..ac8304fbd4c 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerCombatDamageDoneOnce.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerCombatDamageDoneOnce.java @@ -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; /** *

@@ -53,7 +53,7 @@ public class TriggerCombatDamageDoneOnce extends Trigger { @SuppressWarnings("unchecked") @Override public final boolean performTest(final java.util.Map runParams2) { - final List srcs = (List) runParams2.get("DamageSources"); + final Set srcs = (Set) runParams2.get("DamageSources"); final GameEntity tgt = (GameEntity) runParams2.get("DamageTarget"); if (this.mapParams.containsKey("ValidSource")) { diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index 588300c220b..1eb53aeae07 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -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();