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 6bc41965a70..52f52b6c2cc 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 @@ -52,6 +52,9 @@ public class DamageAllEffect extends SpellAbilityEffect { final String damage = sa.getParam("NumDmg"); final int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa); + final boolean rememberCard = sa.hasParam("RememberDamaged") || sa.hasParam("RememberDamagedCreature"); + final boolean rememberPlayer = sa.hasParam("RememberDamaged") || sa.hasParam("RememberDamagedPlayer"); + Player targetPlayer = sa.getTargets().getFirstTargetedPlayer(); String players = ""; @@ -74,8 +77,11 @@ public class DamageAllEffect extends SpellAbilityEffect { list = AbilityUtils.filterListByType(list, sa.getParam("ValidCards"), sa); + int damageSum = 0; for (final Card c : list) { - if (c.addDamage(dmg, sourceLKI) && (sa.hasParam("RememberDamaged") || sa.hasParam("RememberDamagedCreature"))) { + int cardDamage = c.addDamage(dmg, sourceLKI); + damageSum += cardDamage; + if (cardDamage > 0 && rememberCard) { source.addRemembered(c); } } @@ -83,10 +89,16 @@ public class DamageAllEffect extends SpellAbilityEffect { if (!players.equals("")) { final List playerList = AbilityUtils.getDefinedPlayers(card, players, sa); for (final Player p : playerList) { - if (p.addDamage(dmg, sourceLKI) && (sa.hasParam("RememberDamaged") || sa.hasParam("RememberDamagedPlayer"))) { + int playerDamage = p.addDamage(dmg, sourceLKI); + damageSum += playerDamage; + if (playerDamage > 0 && rememberPlayer) { source.addRemembered(p); } } } + + if (damageSum > 0 && source.hasKeyword("Lifelink")) { + source.getController().gainLife(damageSum, source); + } } } 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 0c2bb86aafb..0f2e92fd35e 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 @@ -16,6 +16,7 @@ import java.util.Map; import java.util.Map.Entry; import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; public class DamageDealEffect extends SpellAbilityEffect { @@ -119,6 +120,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; + if (divideOnResolution) { // Dividing Damage up to multiple targets using combat damage box // Currently only used for Master of the Wild Hunt @@ -138,9 +141,13 @@ 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()) { - dt.getKey().addDamage(dt.getValue(), sourceLKI); + damageSum += dt.getKey().addDamage(dt.getValue(), sourceLKI); + } + + // non combat damage cause lifegain there + if (!combatDmg && damageSum > 0 && sourceLKI.hasKeyword("Lifelink")) { + sourceLKI.getController().gainLife(damageSum, sourceLKI); } - return; } @@ -155,18 +162,22 @@ public class DamageDealEffect extends SpellAbilityEffect { c.clearAssignedDamage(); } else if (noPrevention) { - if (c.addDamageWithoutPrevention(dmg, sourceLKI) && remember) { + int damagePrev = c.addDamageWithoutPrevention(dmg, sourceLKI); + damageSum += damagePrev; + if (damagePrev > 0 && remember) { source.addRemembered(c); } } else if (combatDmg) { - HashMap combatmap = new HashMap(); + HashMap combatmap = Maps.newHashMap(); combatmap.put(sourceLKI, dmg); c.addCombatDamage(combatmap); if (remember) { source.addRemembered(c); } } else { - if (c.addDamage(dmg, sourceLKI) && remember) { + int damageDealt = c.addDamage(dmg, sourceLKI); + damageSum += damageDealt; + if (damageDealt > 0 && remember) { source.addRemembered(c); } } @@ -176,7 +187,9 @@ public class DamageDealEffect extends SpellAbilityEffect { final Player p = (Player) o; if (!targeted || p.canBeTargetedBy(sa)) { if (noPrevention) { - if (p.addDamageWithoutPrevention(dmg, sourceLKI) && remember) { + int damagePrev = p.addDamageWithoutPrevention(dmg, sourceLKI); + damageSum += damagePrev; + if (damagePrev > 0 && remember) { source.addRemembered(p); } } else if (combatDmg) { @@ -185,12 +198,19 @@ public class DamageDealEffect extends SpellAbilityEffect { source.addRemembered(p); } } else { - if (p.addDamage(dmg, sourceLKI) && remember) { + int damageDealt = p.addDamage(dmg, sourceLKI); + damageSum += damageDealt; + if (damageDealt > 0 && remember) { source.addRemembered(p); } } } } } + + // non combat damage cause lifegain there + if (!combatDmg && damageSum > 0 && sourceLKI.hasKeyword("Lifelink")) { + sourceLKI.getController().gainLife(damageSum, sourceLKI); + } } } 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 7e4a1c16342..c727cb41be5 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 @@ -75,19 +75,23 @@ public class DamageEachEffect extends SpellAbilityEffect { 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))) { - c.addDamage(dmg, sourceLKI); + damageSum += c.addDamage(dmg, sourceLKI); } } else if (o instanceof Player) { final Player p = (Player) o; if (!targeted || p.canBeTargetedBy(sa)) { - p.addDamage(dmg, sourceLKI); + damageSum += p.addDamage(dmg, sourceLKI); } } + if (damageSum > 0 && sourceLKI.hasKeyword("Lifelink")) { + sourceLKI.getController().gainLife(damageSum, sourceLKI); + } } } @@ -98,7 +102,10 @@ public class DamageEachEffect extends SpellAbilityEffect { final int dmg = CardFactoryUtil.xCount(source, card.getSVar("X")); // System.out.println(source+" deals "+dmg+" damage to "+source); - source.addDamage(dmg, sourceLKI); + int damage = source.addDamage(dmg, sourceLKI); + if (damage > 0 && sourceLKI.hasKeyword("Lifelink")) { + sourceLKI.getController().gainLife(damage, sourceLKI); + } } } if (sa.getParam("DefinedCards").equals("Remembered")) { @@ -107,13 +114,17 @@ public class DamageEachEffect extends SpellAbilityEffect { 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; // System.out.println(source + " deals " + dmg + " damage to " + rememberedcard); - rememberedcard.addDamage(dmg, sourceLKI); + damageSum += rememberedcard.addDamage(dmg, sourceLKI); } } + if (damageSum > 0 && sourceLKI.hasKeyword("Lifelink")) { + sourceLKI.getController().gainLife(damageSum, sourceLKI); + } } } } 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 60e18838aaa..75735e2e456 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 @@ -1,15 +1,15 @@ package forge.game.ability.effects; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import forge.game.Game; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; import forge.game.trigger.TriggerType; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -37,6 +37,7 @@ public class FightEffect extends SpellAbilityEffect { public void resolve(SpellAbility sa) { final Card host = sa.getHostCard(); List fighters = getFighters(sa); + final Game game = sa.getActivatingPlayer().getGame(); if (fighters.size() < 2 || !fighters.get(0).isInPlay() || !fighters.get(1).isInPlay()) { @@ -51,25 +52,25 @@ public class FightEffect extends SpellAbilityEffect { } boolean fightToughness = sa.hasParam("FightWithToughness"); - final int dmg1 = fightToughness ? fighters.get(0).getNetToughness() : fighters.get(0).getNetPower(); - final int dmg2 = fightToughness ? fighters.get(1).getNetToughness() : fighters.get(1).getNetPower(); - fighters.get(1).addDamage(dmg1, fighters.get(0)); - fighters.get(0).addDamage(dmg2, fighters.get(1)); + + dealDamage(fighters.get(0), fighters.get(1), fightToughness); + dealDamage(fighters.get(1), fighters.get(0), fightToughness); + for (Card c : fighters) { - final HashMap runParams = new HashMap(); + final HashMap runParams = Maps.newHashMap(); runParams.put("Fighter", c); - sa.getActivatingPlayer().getGame().getTriggerHandler().runTrigger(TriggerType.Fight, runParams, false); + game.getTriggerHandler().runTrigger(TriggerType.Fight, runParams, false); } } private static List getFighters(SpellAbility sa) { - final List fighterList = new ArrayList(); + final List fighterList = Lists.newArrayList(); Card fighter1 = null; Card fighter2 = null; - final TargetRestrictions tgt = sa.getTargetRestrictions(); + List tgts = null; - if (tgt != null) { + if (sa.usesTargeting()) { tgts = Lists.newArrayList(sa.getTargets().getTargetCards()); if (tgts.size() > 0) { fighter1 = tgts.get(0); @@ -104,5 +105,15 @@ public class FightEffect extends SpellAbilityEffect { return fighterList; } + + private void dealDamage(Card source, Card target, boolean fightToughness) { + 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); + } + } } 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 1543ce1cee8..e09cadb5231 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -6174,19 +6174,16 @@ public class Card extends GameEntity implements Comparable { * applied. */ @Override - public final boolean addDamageAfterPrevention(final int damageIn, final Card source, final boolean isCombat) { + public final int addDamageAfterPrevention(final int damageIn, final Card source, final boolean isCombat) { if (damageIn == 0) { - return false; // Rule 119.8 + return 0; // Rule 119.8 } addReceivedDamageFromThisTurn(source, damageIn); source.addDealtDamageToThisTurn(this, damageIn); if (isCombat) { - game.getCombat().addDealtDamageTo(source, this); - } else if (source.hasKeyword("Lifelink")) { - // LifeLink not for Combat Damage at this place - source.getController().gainLife(damageIn, source); + game.getCombat().addDealtDamageTo(source, this, damageIn); } // Run triggers @@ -6231,7 +6228,7 @@ public class Card extends GameEntity implements Comparable { // Play the Damage sound game.fireEvent(new GameEventCardDamaged(this, source, damageIn, damageType)); } - return true; + return damageIn; } public final String getSetCode() { 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 c1365235ce3..0305f69ccee 100644 --- a/forge-game/src/main/java/forge/game/combat/Combat.java +++ b/forge-game/src/main/java/forge/game/combat/Combat.java @@ -19,7 +19,6 @@ package forge.game.combat; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -28,12 +27,15 @@ 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.HashMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; +import com.google.common.collect.Table; + import forge.game.GameEntity; import forge.game.GameObjectMap; import forge.game.card.Card; @@ -62,12 +64,12 @@ public class Combat { private final Multimap attackedByBands = Multimaps.synchronizedMultimap(ArrayListMultimap.create()); private final Multimap blockedBands = Multimaps.synchronizedMultimap(ArrayListMultimap.create()); - private final HashMap defendingDamageMap = new HashMap(); + private final Map defendingDamageMap = Maps.newHashMap(); - private Map attackersOrderedForDamageAssignment = new HashMap(); - private Map blockersOrderedForDamageAssignment = new HashMap(); - private Map lkiCache = new HashMap(); - private Multimap dealtDamageTo = HashMultimap.create(); + private Map attackersOrderedForDamageAssignment = Maps.newHashMap(); + private Map blockersOrderedForDamageAssignment = Maps.newHashMap(); + private Map lkiCache = Maps.newHashMap(); + private Table dealtDamageTo = HashBasedTable.create(); private Multimap dealtDamageToThisCombat = HashMultimap.create(); // List holds creatures who have dealt 1st strike damage to disallow them deal damage on regular basis (unless they have double-strike KW) @@ -115,8 +117,8 @@ public class Combat { blockersOrderedForDamageAssignment.put(map.map(entry.getKey()), map.mapCollection(entry.getValue())); } // Note: Doesn't currently set up lkiCache, since it's just a cache and not strictly needed... - for (Entry entry : combat.dealtDamageTo.entries()) { - dealtDamageTo.put(map.map(entry.getKey()), map.map(entry.getValue())); + for (Table.Cell entry : combat.dealtDamageTo.cellSet()) { + dealtDamageTo.put(map.map(entry.getRowKey()), map.map(entry.getColumnKey()), entry.getValue()); } for (Entry entry : combat.dealtDamageToThisCombat.entries()) { dealtDamageToThisCombat.put(map.map(entry.getKey()), map.map(entry.getValue())); @@ -574,7 +576,7 @@ public class Combat { if (!isBlocked) { for (Card attacker : ab.getAttackers()) { // Run Unblocked Trigger - final HashMap runParams = new HashMap(); + final Map runParams = Maps.newHashMap(); runParams.put("Attacker", attacker); runParams.put("Defender",getDefenderByAttacker(attacker)); runParams.put("DefendingPlayer", getDefenderPlayerByAttacker(attacker)); @@ -720,22 +722,23 @@ public class Combat { return assignedDamage; } - public final void addDealtDamageTo(final Card source, final GameEntity ge) { - dealtDamageTo.put(source, ge); + 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 void dealAssignedDamage() { playerWhoAttacks.getGame().copyLastState(); // This function handles both Regular and First Strike combat assignment - final HashMap defMap = defendingDamageMap; - final HashMap wasDamaged = new HashMap(); - final Map damageDealtThisCombat = new HashMap(); + final Map defMap = defendingDamageMap; + final Map wasDamaged = Maps.newHashMap(); for (final Entry entry : defMap.entrySet()) { GameEntity defender = getDefenderByAttacker(entry.getKey()); if (defender instanceof Player) { // player - if (((Player) defender).addCombatDamage(entry.getValue(), entry.getKey())) { + int dmg = ((Player) defender).addCombatDamage(entry.getValue(), entry.getKey()); + if (dmg > 0) { if (wasDamaged.containsKey(defender)) { wasDamaged.get(defender).add(entry.getKey()); } else { @@ -743,11 +746,12 @@ public class Combat { l.add(entry.getKey()); wasDamaged.put(defender, l); } - damageDealtThisCombat.put(entry.getKey(), entry.getValue()); + this.addDealtDamageTo(entry.getKey(), defender, entry.getValue()); } } else if (defender instanceof Card) { // planeswalker - if (((Card) defender).getController().addCombatDamage(entry.getValue(), entry.getKey())) { + int dmg = ((Card) defender).getController().addCombatDamage(entry.getValue(), entry.getKey()); + if (dmg > 0) { if (wasDamaged.containsKey(defender)) { wasDamaged.get(defender).add(entry.getKey()); } @@ -777,18 +781,14 @@ public class Combat { } final Map assignedDamageMap = c.getAssignedDamageMap(); - final HashMap damageMap = new HashMap(); + final Map damageMap = Maps.newHashMap(); for (final Entry entry : assignedDamageMap.entrySet()) { final Card crd = entry.getKey(); c.getDamageHistory().registerCombatDamage(crd); damageMap.put(crd, entry.getValue()); if (entry.getValue() > 0) { - if (damageDealtThisCombat.containsKey(crd)) { - damageDealtThisCombat.put(crd, damageDealtThisCombat.get(crd) + entry.getValue()); - } else { - damageDealtThisCombat.put(crd, entry.getValue()); - } + this.addDealtDamageTo(crd, c, entry.getValue()); } } c.addCombatDamage(damageMap); @@ -799,7 +799,7 @@ public class Combat { // Run triggers for (final GameEntity ge : wasDamaged.keySet()) { - final HashMap runParams = new HashMap(); + final Map runParams = Maps.newHashMap(); runParams.put("DamageSources", wasDamaged.get(ge)); runParams.put("DamageTarget", ge); ge.getGame().getTriggerHandler().runTrigger(TriggerType.CombatDamageDoneOnce, runParams, false); @@ -807,19 +807,24 @@ public class Combat { // This was deeper before, but that resulted in the stack entry acting like before. // when ... deals combat damage to one or more - for (final Card damageSource : dealtDamageTo.keySet()) { - final HashMap runParams = new HashMap(); - int dealtDamage = damageDealtThisCombat.containsKey(damageSource) ? damageDealtThisCombat.get(damageSource) : 0; + for (final Card damageSource : dealtDamageTo.rowKeySet()) { + final Map runParams = Maps.newHashMap(); + Map row = dealtDamageTo.row(damageSource); + + int dealtDamage = 0; + for (Map.Entry e : row.entrySet()) { + dealtDamage += e.getValue(); + dealtDamageToThisCombat.put(damageSource, e.getKey()); + } // LifeLink for Combat Damage at this place if (dealtDamage > 0 && damageSource.hasKeyword("Lifelink")) { damageSource.getController().gainLife(dealtDamage, damageSource); } runParams.put("DamageSource", damageSource); - runParams.put("DamageTargets", dealtDamageTo.get(damageSource)); + runParams.put("DamageTargets", row.keySet()); runParams.put("DamageAmount", dealtDamage); damageSource.getGame().getTriggerHandler().runTrigger(TriggerType.DealtCombatDamageOnce, runParams, false); } - dealtDamageToThisCombat.putAll(dealtDamageTo); dealtDamageTo.clear(); } 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 48fe6e28902..8ddb7818c42 100644 --- a/forge-game/src/main/java/forge/game/cost/CostDamage.java +++ b/forge-game/src/main/java/forge/game/cost/CostDamage.java @@ -17,6 +17,7 @@ */ package forge.game.cost; +import forge.game.card.Card; import forge.game.player.Player; import forge.game.spellability.SpellAbility; @@ -58,7 +59,12 @@ public class CostDamage extends CostPart { @Override public boolean payAsDecided(Player payer, PaymentDecision decision, SpellAbility sa) { - return payer.addDamage(decision.c, sa.getHostCard()); + final Card source = sa.getHostCard(); + int dmg = payer.addDamage(decision.c, source); + if (dmg > 0 && source.hasKeyword("Lifelink")) { + source.getController().gainLife(dmg, source); + } + return decision.c > 0; } @Override 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 a41043ce649..b32e1a1a74c 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -534,14 +534,14 @@ public class Player extends GameEntity implements Comparable { // This function handles damage after replacement and prevention effects are applied @Override - public final boolean addDamageAfterPrevention(final int amount, final Card source, final boolean isCombat) { + public final int addDamageAfterPrevention(final int amount, final Card source, final boolean isCombat) { if (amount <= 0) { - return false; + return 0; } //String additionalLog = ""; source.addDealtDamageToPlayerThisTurn(getName(), amount); if (isCombat) { - game.getCombat().addDealtDamageTo(source, this); + game.getCombat().addDealtDamageTo(source, this, amount); } boolean infect = source.hasKeyword("Infect") @@ -576,9 +576,6 @@ public class Player extends GameEntity implements Comparable { for (final String type : source.getType()) { source.getController().addProwlType(type); } - } else if (source.hasKeyword("Lifelink")) { - // LifeLink not for Combat Damage at this place - source.getController().gainLife(amount, source); } // Run triggers @@ -592,7 +589,7 @@ public class Player extends GameEntity implements Comparable { game.getTriggerHandler().runTrigger(TriggerType.DamageDone, runParams, false); game.fireEvent(new GameEventPlayerDamaged(this, source, amount, isCombat, infect)); - return true; + return amount; } // This should be also usable by the AI to forecast an effect (so it must not change the game state) @@ -888,7 +885,7 @@ public class Player extends GameEntity implements Comparable { return Aggregates.max(getOpponents(), Accessors.FN_GET_ASSIGNED_DAMAGE); } - public final boolean addCombatDamage(final int damage, final Card source) { + public final int addCombatDamage(final int damage, final Card source) { int damageToDo = damage; damageToDo = replaceDamage(damageToDo, source, true); @@ -897,10 +894,9 @@ public class Player extends GameEntity implements Comparable { addDamageAfterPrevention(damageToDo, source, true); // damage prevention is already checked if (damageToDo > 0) { - source.getDamageHistory().registerCombatDamage(this); - return true; + source.getDamageHistory().registerCombatDamage(this); } - return false; + return damageToDo; } public final boolean canReceiveCounters(final CounterType type) {