Refactor Damage/CombatDamage/Lifelink, now it works as it should with DamageRedirection/Prevention/Multiplication

This commit is contained in:
Hanmac
2016-12-29 11:33:25 +00:00
parent 068b9b4cc0
commit 0426896c01
8 changed files with 130 additions and 72 deletions

View File

@@ -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<Player> 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);
}
}
}

View File

@@ -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<Card, Integer> map = assigningPlayer.getController().assignCombatDamage(sourceLKI, assigneeCards, dmg, null, true);
for (Entry<Card, Integer> 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<Card, Integer> combatmap = new HashMap<Card, Integer>();
HashMap<Card, Integer> 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);
}
}
}

View File

@@ -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,12 +114,16 @@ 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);
}
}
}

View File

@@ -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<Card> 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<String, Object> runParams = new HashMap<String, Object>();
final HashMap<String, Object> 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<Card> getFighters(SpellAbility sa) {
final List<Card> fighterList = new ArrayList<Card>();
final List<Card> fighterList = Lists.newArrayList();
Card fighter1 = null;
Card fighter2 = null;
final TargetRestrictions tgt = sa.getTargetRestrictions();
List<Card> tgts = null;
if (tgt != null) {
if (sa.usesTargeting()) {
tgts = Lists.newArrayList(sa.getTargets().getTargetCards());
if (tgts.size() > 0) {
fighter1 = tgts.get(0);
@@ -105,4 +106,14 @@ 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);
}
}
}

View File

@@ -6174,19 +6174,16 @@ public class Card extends GameEntity implements Comparable<Card> {
* 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<Card> {
// Play the Damage sound
game.fireEvent(new GameEventCardDamaged(this, source, damageIn, damageType));
}
return true;
return damageIn;
}
public final String getSetCode() {

View File

@@ -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<GameEntity, AttackingBand> attackedByBands = Multimaps.synchronizedMultimap(ArrayListMultimap.<GameEntity, AttackingBand>create());
private final Multimap<AttackingBand, Card> blockedBands = Multimaps.synchronizedMultimap(ArrayListMultimap.<AttackingBand, Card>create());
private final HashMap<Card, Integer> defendingDamageMap = new HashMap<Card, Integer>();
private final Map<Card, Integer> defendingDamageMap = Maps.newHashMap();
private Map<Card, CardCollection> attackersOrderedForDamageAssignment = new HashMap<Card, CardCollection>();
private Map<Card, CardCollection> blockersOrderedForDamageAssignment = new HashMap<Card, CardCollection>();
private Map<GameEntity, CombatLki> lkiCache = new HashMap<GameEntity, CombatLki>();
private Multimap<Card, GameEntity> dealtDamageTo = HashMultimap.create();
private Map<Card, CardCollection> attackersOrderedForDamageAssignment = Maps.newHashMap();
private Map<Card, CardCollection> blockersOrderedForDamageAssignment = Maps.newHashMap();
private Map<GameEntity, CombatLki> lkiCache = Maps.newHashMap();
private Table<Card, GameEntity, Integer> dealtDamageTo = HashBasedTable.create();
private Multimap<Card, GameEntity> 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<Card, GameEntity> entry : combat.dealtDamageTo.entries()) {
dealtDamageTo.put(map.map(entry.getKey()), map.map(entry.getValue()));
for (Table.Cell<Card, GameEntity, Integer> entry : combat.dealtDamageTo.cellSet()) {
dealtDamageTo.put(map.map(entry.getRowKey()), map.map(entry.getColumnKey()), entry.getValue());
}
for (Entry<Card, GameEntity> 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<String, Object> runParams = new HashMap<String, Object>();
final Map<String, Object> 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<Card, Integer> defMap = defendingDamageMap;
final HashMap<GameEntity, CardCollection> wasDamaged = new HashMap<GameEntity, CardCollection>();
final Map<Card, Integer> damageDealtThisCombat = new HashMap<Card, Integer>();
final Map<Card, Integer> defMap = defendingDamageMap;
final Map<GameEntity, CardCollection> wasDamaged = Maps.newHashMap();
for (final Entry<Card, Integer> 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<Card, Integer> assignedDamageMap = c.getAssignedDamageMap();
final HashMap<Card, Integer> damageMap = new HashMap<Card, Integer>();
final Map<Card, Integer> damageMap = Maps.newHashMap();
for (final Entry<Card, Integer> 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<String, Object> runParams = new HashMap<String, Object>();
final Map<String, Object> 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<String, Object> runParams = new HashMap<String, Object>();
int dealtDamage = damageDealtThisCombat.containsKey(damageSource) ? damageDealtThisCombat.get(damageSource) : 0;
for (final Card damageSource : dealtDamageTo.rowKeySet()) {
final Map<String, Object> runParams = Maps.newHashMap();
Map<GameEntity, Integer> row = dealtDamageTo.row(damageSource);
int dealtDamage = 0;
for (Map.Entry<GameEntity, Integer> 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();
}

View File

@@ -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

View File

@@ -534,14 +534,14 @@ public class Player extends GameEntity implements Comparable<Player> {
// 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<Player> {
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<Player> {
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<Player> {
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);
@@ -898,9 +895,8 @@ public class Player extends GameEntity implements Comparable<Player> {
if (damageToDo > 0) {
source.getDamageHistory().registerCombatDamage(this);
return true;
}
return false;
return damageToDo;
}
public final boolean canReceiveCounters(final CounterType type) {