Deal damage use damage map

This commit is contained in:
Alumi
2021-05-16 06:51:38 +00:00
committed by Hans Mackowiak
parent f996c90653
commit 419a513d8f
21 changed files with 161 additions and 273 deletions

View File

@@ -47,6 +47,7 @@ import forge.game.ability.effects.AttachEffect;
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.CardFactory; import forge.game.card.CardFactory;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
@@ -61,6 +62,7 @@ import forge.game.event.GameEventCardTapped;
import forge.game.event.GameEventFlipCoin; import forge.game.event.GameEventFlipCoin;
import forge.game.event.GameEventGameStarted; import forge.game.event.GameEventGameStarted;
import forge.game.event.GameEventScry; import forge.game.event.GameEventScry;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordInterface;
import forge.game.mulligan.MulliganService; import forge.game.mulligan.MulliganService;
import forge.game.player.GameLossReason; import forge.game.player.GameLossReason;
@@ -2125,4 +2127,51 @@ public class GameAction {
} }
} }
} }
public void dealDamage(final boolean isCombat, final CardDamageMap damageMap, final CardDamageMap preventMap,
final GameEntityCounterTable counterTable, final SpellAbility cause) {
CardDamageMap replaceDamageMap = new CardDamageMap(damageMap);
// Run replacement effect for each entity dealt damage
// TODO: List all possible replacement effects and run them in APNAP order.
// TODO: To handle "Prevented this way" and abilities like "Phantom Nomad", should buffer the replaced SA
// and only run them after all prevention and redirection effects are processed.
for (Map.Entry<GameEntity, Map<Card, Integer>> et : damageMap.columnMap().entrySet()) {
final GameEntity ge = et.getKey();
if (isCombat && ge instanceof Card) {
((Card) ge).clearAssignedDamage();
}
for (Map.Entry<Card, Integer> e : et.getValue().entrySet()) {
if (e.getValue() > 0) {
ge.replaceDamage(e.getValue(), e.getKey(), isCombat, replaceDamageMap, preventMap, counterTable, cause);
}
}
}
// Actually deal damage according to replaced damage map
for (Map.Entry<Card, Map<GameEntity, Integer>> et : replaceDamageMap.rowMap().entrySet()) {
final Card sourceLKI = et.getKey();
int sum = 0;
for (Map.Entry<GameEntity, Integer> e : et.getValue().entrySet()) {
if (e.getValue() <= 0) {
continue;
}
sum += e.getValue();
e.getKey().addDamageAfterPrevention(e.getValue(), sourceLKI, isCombat, counterTable);
}
if (sourceLKI.hasKeyword(Keyword.LIFELINK)) {
sourceLKI.getController().gainLife(sum, sourceLKI, cause);
}
}
preventMap.triggerPreventDamage(isCombat);
preventMap.clear();
replaceDamageMap.triggerDamageDoneOnce(isCombat, game);
damageMap.clear();
replaceDamageMap.clear();
counterTable.triggerCountersPutAll(game);
counterTable.clear();
}
} }

View File

@@ -67,49 +67,10 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
getView().updateName(this); getView().updateName(this);
} }
public final int addDamage(final int damage, final Card source, boolean isCombat, boolean noPrevention, // final Iterable<Card> source
public void replaceDamage(final int damage, final Card source, final boolean isCombat,
final CardDamageMap damageMap, final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) { final CardDamageMap damageMap, final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
int damageToDo = replaceDamage(damage, source, isCombat, !noPrevention, damageMap, preventMap, counterTable, cause); boolean prevention = source.canDamagePrevented(isCombat) && (cause == null || !cause.hasParam("NoPrevention"));
if (damageToDo <= 0) {
return 0;
}
if (isCombat) {
source.getDamageHistory().registerCombatDamage(this);
return addCombatDamageBase(damageToDo, source, damageMap, counterTable);
} else {
return addDamageAfterPrevention(damageToDo, source, false, damageMap, counterTable);
}
}
public int addDamage(final int damage, final Card source, final CardDamageMap damageMap,
final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
int damageToDo = replaceDamage(damage, source, false, true, damageMap, preventMap, counterTable, cause);
return addDamageAfterPrevention(damageToDo, source, false, damageMap, counterTable);
}
public final int addCombatDamage(final int damage, final Card source, final CardDamageMap damageMap,
final CardDamageMap preventMap, GameEntityCounterTable counterTable) {
int damageToDo = replaceDamage(damage, source, true, true, damageMap, preventMap, counterTable, null);
if (damageToDo > 0) {
source.getDamageHistory().registerCombatDamage(this);
}
// damage prevention is already checked
return addCombatDamageBase(damageToDo, source, damageMap, counterTable);
}
protected int addCombatDamageBase(final int damage, final Card source, CardDamageMap damageMap, GameEntityCounterTable counterTable) {
return addDamageAfterPrevention(damage, source, true, damageMap, counterTable);
}
public int replaceDamage(final int damage, final Card source, final boolean isCombat, boolean prevention,
final CardDamageMap damageMap, final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
if (!source.canDamagePrevented(isCombat)) {
prevention = false;
}
int restDamage = damage;
// Replacement effects // Replacement effects
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this); final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
@@ -132,22 +93,19 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
GameEntity newTarget = (GameEntity) repParams.get(AbilityKey.Affected); GameEntity newTarget = (GameEntity) repParams.get(AbilityKey.Affected);
// check if this is still the affected card or player // check if this is still the affected card or player
if (this.equals(newTarget)) { if (this.equals(newTarget)) {
restDamage = newDamage; damageMap.put(source, this, newDamage - damage);
} else { } else {
newDamage = newTarget.replaceDamage(newDamage, source, isCombat, prevention, damageMap, preventMap, counterTable, cause); damageMap.remove(source, this);
newTarget.addDamageAfterPrevention(newDamage, source, isCombat, damageMap, counterTable); damageMap.put(source, newTarget, newDamage);
restDamage = 0;
} }
break; break;
default: default:
restDamage = 0; damageMap.remove(source, this);
} }
return restDamage;
} }
// 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, CardDamageMap damageMap, GameEntityCounterTable counterTable); public abstract int addDamageAfterPrevention(final int damage, final Card source, final boolean isCombat, GameEntityCounterTable counterTable);
// 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)

View File

@@ -20,6 +20,13 @@ public class GameEntityCounterTable extends ForwardingTable<Optional<Player>, Ga
private Table<Optional<Player>, GameEntity, Map<CounterType, Integer>> dataMap = HashBasedTable.create(); private Table<Optional<Player>, GameEntity, Map<CounterType, Integer>> dataMap = HashBasedTable.create();
public GameEntityCounterTable() {
}
public GameEntityCounterTable(Table<Optional<Player>, GameEntity, Map<CounterType, Integer>> counterTable) {
putAll(counterTable);
}
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.google.common.collect.ForwardingTable#delegate() * @see com.google.common.collect.ForwardingTable#delegate()

View File

@@ -89,26 +89,31 @@ public class DamageAllEffect extends DamageBaseEffect {
boolean usedDamageMap = true; boolean usedDamageMap = true;
CardDamageMap damageMap = sa.getDamageMap(); CardDamageMap damageMap = sa.getDamageMap();
CardDamageMap preventMap = sa.getPreventMap(); CardDamageMap preventMap = sa.getPreventMap();
GameEntityCounterTable counterTable = new GameEntityCounterTable(); GameEntityCounterTable counterTable = sa.getCounterTable();
if (damageMap == null) { if (damageMap == null) {
// make a new damage map // make a new damage map
damageMap = new CardDamageMap(); damageMap = new CardDamageMap();
preventMap = new CardDamageMap(); preventMap = new CardDamageMap();
counterTable = new GameEntityCounterTable();
usedDamageMap = false; usedDamageMap = false;
} }
for (final Card c : list) { for (final Card c : list) {
c.addDamage(dmg, sourceLKI, damageMap, preventMap, counterTable, sa); damageMap.put(sourceLKI, c, dmg);
} }
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) {
p.addDamage(dmg, sourceLKI, damageMap, preventMap, counterTable, sa); damageMap.put(sourceLKI, p, dmg);
} }
} }
if (!usedDamageMap) {
game.getAction().dealDamage(false, damageMap, preventMap, counterTable, sa);
}
// do Remember there // do Remember there
if (rememberCard || rememberPlayer) { if (rememberCard || rememberPlayer) {
for (GameEntity e : damageMap.row(sourceLKI).keySet()) { for (GameEntity e : damageMap.row(sourceLKI).keySet()) {
@@ -120,16 +125,6 @@ public class DamageAllEffect extends DamageBaseEffect {
} }
} }
if (!usedDamageMap) {
preventMap.triggerPreventDamage(false);
damageMap.triggerDamageDoneOnce(false, game, sa);
preventMap.clear();
damageMap.clear();
}
counterTable.triggerCountersPutAll(game);
replaceDying(sa); replaceDying(sa);
} }
} }

View File

@@ -145,7 +145,6 @@ public class DamageDealEffect extends DamageBaseEffect {
final String damage = sa.getParam("NumDmg"); final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(hostCard, damage, sa); int dmg = AbilityUtils.calculateAmount(hostCard, damage, sa);
final boolean noPrevention = sa.hasParam("NoPrevention");
final boolean removeDamage = sa.hasParam("Remove"); final boolean removeDamage = sa.hasParam("Remove");
final boolean divideOnResolution = sa.hasParam("DividerOnResolution"); final boolean divideOnResolution = sa.hasParam("DividerOnResolution");
@@ -172,17 +171,19 @@ public class DamageDealEffect extends DamageBaseEffect {
boolean usedDamageMap = true; boolean usedDamageMap = true;
CardDamageMap damageMap = sa.getDamageMap(); CardDamageMap damageMap = sa.getDamageMap();
CardDamageMap preventMap = sa.getPreventMap(); CardDamageMap preventMap = sa.getPreventMap();
GameEntityCounterTable counterTable = new GameEntityCounterTable(); GameEntityCounterTable counterTable = sa.getCounterTable();
if (damageMap == null) { if (damageMap == null) {
// make a new damage map // make a new damage map
damageMap = new CardDamageMap(); damageMap = new CardDamageMap();
preventMap = new CardDamageMap(); preventMap = new CardDamageMap();
counterTable = new GameEntityCounterTable();
usedDamageMap = false; usedDamageMap = false;
} }
if (sa.hasParam("DamageMap")) { if (sa.hasParam("DamageMap")) {
sa.setDamageMap(damageMap); sa.setDamageMap(damageMap);
sa.setPreventMap(preventMap); sa.setPreventMap(preventMap);
sa.setCounterTable(counterTable);
usedDamageMap = true; usedDamageMap = true;
} }
@@ -208,19 +209,12 @@ public class DamageDealEffect extends DamageBaseEffect {
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()) {
dt.getKey().addDamage(dt.getValue(), sourceLKI, damageMap, preventMap, counterTable, sa); damageMap.put(sourceLKI, dt.getKey(), dt.getValue());
} }
if (!usedDamageMap) { if (!usedDamageMap) {
preventMap.triggerPreventDamage(false); game.getAction().dealDamage(false, damageMap, preventMap, counterTable, sa);
// non combat damage cause lifegain there
damageMap.triggerDamageDoneOnce(false, game, sa);
preventMap.clear();
damageMap.clear();
} }
counterTable.triggerCountersPutAll(game);
replaceDying(sa); replaceDying(sa);
return; return;
} }
@@ -244,18 +238,18 @@ public class DamageDealEffect extends DamageBaseEffect {
continue; continue;
} }
if (!sa.usesTargeting() || gc.canBeTargetedBy(sa)) { if (!sa.usesTargeting() || gc.canBeTargetedBy(sa)) {
internalDamageDeal(sa, sourceLKI, gc, dmg, damageMap, preventMap, counterTable); internalDamageDeal(sa, sourceLKI, gc, dmg, damageMap);
} }
} else if (o instanceof Player) { } else if (o instanceof Player) {
final Player p = (Player) o; final Player p = (Player) o;
if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) { if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) {
p.addDamage(dmg, sourceLKI, false, noPrevention, damageMap, preventMap, counterTable, sa); damageMap.put(sourceLKI, p, dmg);
} }
} }
} }
for (final Card unTgtC : untargetedCards) { for (final Card unTgtC : untargetedCards) {
if (unTgtC.isInPlay()) { if (unTgtC.isInPlay()) {
internalDamageDeal(sa, sourceLKI, unTgtC, dmg, damageMap, preventMap, counterTable); internalDamageDeal(sa, sourceLKI, unTgtC, dmg, damageMap);
} }
} }
@@ -264,21 +258,14 @@ public class DamageDealEffect extends DamageBaseEffect {
} }
} }
if (!usedDamageMap) { if (!usedDamageMap) {
preventMap.triggerPreventDamage(false); game.getAction().dealDamage(false, damageMap, preventMap, counterTable, sa);
// non combat damage cause lifegain there
damageMap.triggerDamageDoneOnce(false, game, sa);
preventMap.clear();
damageMap.clear();
} }
counterTable.triggerCountersPutAll(game);
replaceDying(sa); replaceDying(sa);
} }
protected void internalDamageDeal(SpellAbility sa, Card sourceLKI, Card c, int dmg, CardDamageMap damageMap, CardDamageMap preventMap, GameEntityCounterTable counterTable) { protected void internalDamageDeal(SpellAbility sa, Card sourceLKI, Card c, int dmg, CardDamageMap damageMap) {
final Card hostCard = sa.getHostCard(); final Card hostCard = sa.getHostCard();
final Player activationPlayer = sa.getActivatingPlayer(); final Player activationPlayer = sa.getActivatingPlayer();
final boolean noPrevention = sa.hasParam("NoPrevention");
if (sa.hasParam("Remove")) { if (sa.hasParam("Remove")) {
c.setDamage(0); c.setDamage(0);
@@ -293,17 +280,17 @@ public class DamageDealEffect extends DamageBaseEffect {
} }
int dmgToTarget = Math.min(lethal, dmg); int dmgToTarget = Math.min(lethal, dmg);
c.addDamage(dmgToTarget, sourceLKI, false, noPrevention, damageMap, preventMap, counterTable, sa); damageMap.put(sourceLKI, c, dmgToTarget);
List<GameEntity> list = Lists.newArrayList(); List<GameEntity> list = Lists.newArrayList();
list.addAll(AbilityUtils.getDefinedCards(hostCard, sa.getParam("ExcessDamage"), sa)); list.addAll(AbilityUtils.getDefinedCards(hostCard, sa.getParam("ExcessDamage"), sa));
list.addAll(AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("ExcessDamage"), sa)); list.addAll(AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("ExcessDamage"), sa));
if (!list.isEmpty()) { if (!list.isEmpty()) {
list.get(0).addDamage(dmg - dmgToTarget, sourceLKI, false, noPrevention, damageMap, preventMap, counterTable, sa); damageMap.put(sourceLKI, list.get(0), dmg - dmgToTarget);
} }
} else { } else {
c.addDamage(dmg, sourceLKI, false, noPrevention, damageMap, preventMap, counterTable, sa); damageMap.put(sourceLKI, c, dmg);
} }
} }
} }

View File

@@ -75,12 +75,13 @@ public class DamageEachEffect extends DamageBaseEffect {
boolean usedDamageMap = true; boolean usedDamageMap = true;
CardDamageMap damageMap = sa.getDamageMap(); CardDamageMap damageMap = sa.getDamageMap();
CardDamageMap preventMap = sa.getPreventMap(); CardDamageMap preventMap = sa.getPreventMap();
GameEntityCounterTable counterTable = new GameEntityCounterTable(); GameEntityCounterTable counterTable = sa.getCounterTable();
if (damageMap == null) { if (damageMap == null) {
// make a new damage map // make a new damage map
damageMap = new CardDamageMap(); damageMap = new CardDamageMap();
preventMap = new CardDamageMap(); preventMap = new CardDamageMap();
counterTable = new GameEntityCounterTable();
usedDamageMap = false; usedDamageMap = false;
} }
@@ -95,13 +96,13 @@ public class DamageEachEffect extends DamageBaseEffect {
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))) {
c.addDamage(dmg, sourceLKI, damageMap, preventMap, counterTable, sa); damageMap.put(sourceLKI, c, dmg);
} }
} 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)) {
p.addDamage(dmg, sourceLKI, damageMap, preventMap, counterTable, sa); damageMap.put(sourceLKI, p, dmg);
} }
} }
} }
@@ -113,8 +114,7 @@ public class DamageEachEffect extends DamageBaseEffect {
final Card sourceLKI = game.getChangeZoneLKIInfo(source); final Card sourceLKI = game.getChangeZoneLKIInfo(source);
final int dmg = AbilityUtils.calculateAmount(source, "X", sa); final int dmg = AbilityUtils.calculateAmount(source, "X", sa);
// System.out.println(source+" deals "+dmg+" damage to "+source); damageMap.put(sourceLKI, source, dmg);
source.addDamage(dmg, sourceLKI, damageMap, preventMap, counterTable, sa);
} }
} }
if (sa.getParam("DefinedCards").equals("Remembered")) { if (sa.getParam("DefinedCards").equals("Remembered")) {
@@ -125,8 +125,7 @@ public class DamageEachEffect extends DamageBaseEffect {
for (final Object o : sa.getHostCard().getRemembered()) { for (final Object o : sa.getHostCard().getRemembered()) {
if (o instanceof Card) { if (o instanceof Card) {
Card rememberedcard = (Card) o; Card rememberedcard = (Card) o;
// System.out.println(source + " deals " + dmg + " damage to " + rememberedcard); damageMap.put(sourceLKI, rememberedcard, dmg);
rememberedcard.addDamage(dmg, sourceLKI, damageMap, preventMap, counterTable, sa);
} }
} }
} }
@@ -134,15 +133,9 @@ public class DamageEachEffect extends DamageBaseEffect {
} }
if (!usedDamageMap) { if (!usedDamageMap) {
preventMap.triggerPreventDamage(false); game.getAction().dealDamage(false, damageMap, preventMap, counterTable, sa);
damageMap.triggerDamageDoneOnce(false, game, sa);
preventMap.clear();
damageMap.clear();
} }
counterTable.triggerCountersPutAll(game);
replaceDying(sa); replaceDying(sa);
} }
} }

View File

@@ -1,5 +1,6 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import forge.game.GameEntityCounterTable;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.CardDamageMap; import forge.game.card.CardDamageMap;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -18,16 +19,9 @@ public class DamageResolveEffect extends SpellAbilityEffect {
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
CardDamageMap damageMap = sa.getDamageMap(); CardDamageMap damageMap = sa.getDamageMap();
CardDamageMap preventMap = sa.getPreventMap(); CardDamageMap preventMap = sa.getPreventMap();
GameEntityCounterTable counterTable = sa.getCounterTable();
if (preventMap != null) { sa.getHostCard().getGame().getAction().dealDamage(false, damageMap, preventMap, counterTable, sa);
preventMap.triggerPreventDamage(false);
preventMap.clear();
}
// non combat damage cause lifegain there
if (damageMap != null) {
damageMap.triggerDamageDoneOnce(false, sa.getHostCard().getGame(), sa);
damageMap.clear();
}
} }
/* (non-Javadoc) /* (non-Javadoc)

View File

@@ -146,12 +146,13 @@ public class FightEffect extends DamageBaseEffect {
boolean usedDamageMap = true; boolean usedDamageMap = true;
CardDamageMap damageMap = sa.getDamageMap(); CardDamageMap damageMap = sa.getDamageMap();
CardDamageMap preventMap = sa.getPreventMap(); CardDamageMap preventMap = sa.getPreventMap();
GameEntityCounterTable counterTable = new GameEntityCounterTable(); GameEntityCounterTable counterTable = sa.getCounterTable();
if (damageMap == null) { if (damageMap == null) {
// make a new damage map // make a new damage map
damageMap = new CardDamageMap(); damageMap = new CardDamageMap();
preventMap = new CardDamageMap(); preventMap = new CardDamageMap();
counterTable = new GameEntityCounterTable();
usedDamageMap = false; usedDamageMap = false;
} }
@@ -163,22 +164,17 @@ public class FightEffect extends DamageBaseEffect {
final int dmg1 = fightToughness ? fighterA.getNetToughness() : fighterA.getNetPower(); final int dmg1 = fightToughness ? fighterA.getNetToughness() : fighterA.getNetPower();
if (fighterA.equals(fighterB)) { if (fighterA.equals(fighterB)) {
fighterA.addDamage(dmg1 * 2, fighterA, damageMap, preventMap, counterTable, sa); damageMap.put(fighterA, fighterA, dmg1 * 2);
} else { } else {
final int dmg2 = fightToughness ? fighterB.getNetToughness() : fighterB.getNetPower(); final int dmg2 = fightToughness ? fighterB.getNetToughness() : fighterB.getNetPower();
fighterB.addDamage(dmg1, fighterA, damageMap, preventMap, counterTable, sa); damageMap.put(fighterA, fighterB, dmg1);
fighterA.addDamage(dmg2, fighterB, damageMap, preventMap, counterTable, sa); damageMap.put(fighterB, fighterA, dmg2);
} }
if (!usedDamageMap) { if (!usedDamageMap) {
preventMap.triggerPreventDamage(false); sa.getHostCard().getGame().getAction().dealDamage(false, damageMap, preventMap, counterTable, sa);
damageMap.triggerDamageDoneOnce(false, fighterA.getGame(), sa);
preventMap.clear();
damageMap.clear();
} }
counterTable.triggerCountersPutAll(sa.getHostCard().getGame());
replaceDying(sa); replaceDying(sa);
} }

View File

@@ -7,6 +7,7 @@ import com.google.common.collect.Lists;
import forge.GameCommand; import forge.GameCommand;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntityCounterTable;
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;
@@ -86,6 +87,7 @@ public class RepeatEachEffect extends SpellAbilityEffect {
if (sa.hasParam("DamageMap")) { if (sa.hasParam("DamageMap")) {
sa.setDamageMap(new CardDamageMap()); sa.setDamageMap(new CardDamageMap());
sa.setPreventMap(new CardDamageMap()); sa.setPreventMap(new CardDamageMap());
sa.setCounterTable(new GameEntityCounterTable());
} }
if (sa.hasParam("ChangeZoneTable")) { if (sa.hasParam("ChangeZoneTable")) {
sa.setChangeZoneTable(new CardZoneTable()); sa.setChangeZoneTable(new CardZoneTable());
@@ -166,11 +168,7 @@ public class RepeatEachEffect extends SpellAbilityEffect {
} }
if(sa.hasParam("DamageMap")) { if(sa.hasParam("DamageMap")) {
sa.getPreventMap().triggerPreventDamage(false); game.getAction().dealDamage(false, sa.getDamageMap(), sa.getPreventMap(), sa.getCounterTable(), sa);
sa.setPreventMap(null);
// non combat damage cause lifegain there
sa.getDamageMap().triggerDamageDoneOnce(false, game, sa);
sa.setDamageMap(null);
} }
if (sa.hasParam("ChangeZoneTable")) { if (sa.hasParam("ChangeZoneTable")) {
sa.getChangeZoneTable().triggerChangesZoneAll(game, sa); sa.getChangeZoneTable().triggerChangesZoneAll(game, sa);

View File

@@ -60,17 +60,18 @@ public class ReplaceDamageEffect extends SpellAbilityEffect {
// Set PreventedDamage SVar // Set PreventedDamage SVar
card.setSVar("PreventedDamage", "Number$" + n); card.setSVar("PreventedDamage", "Number$" + n);
Card sourceLKI = (Card) sa.getReplacingObject(AbilityKey.Source);
GameEntity target = (GameEntity) sa.getReplacingObject(AbilityKey.Target);
// Set prevent map entry // Set prevent map entry
CardDamageMap preventMap = (CardDamageMap) originalParams.get(AbilityKey.PreventMap); CardDamageMap preventMap = (CardDamageMap) originalParams.get(AbilityKey.PreventMap);
Card source = (Card) sa.getReplacingObject(AbilityKey.Source); preventMap.put(sourceLKI, target, n);
GameEntity target = (GameEntity) sa.getReplacingObject(AbilityKey.Target);
preventMap.put(source, target, n);
// Following codes are commented out since DamagePrevented trigger is currently not used by any Card. // Following codes are commented out since DamagePrevented trigger is currently not used by any Card.
// final Map<AbilityKey, Object> runParams = AbilityKey.newMap(); // final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
// runParams.put(AbilityKey.DamageTarget, target); // runParams.put(AbilityKey.DamageTarget, target);
// runParams.put(AbilityKey.DamageAmount, dmg); // runParams.put(AbilityKey.DamageAmount, dmg);
// runParams.put(AbilityKey.DamageSource, source); // runParams.put(AbilityKey.DamageSource, sourceLKI);
// runParams.put(AbilityKey.IsCombatDamage, originalParams.get(AbilityKey.IsCombat)); // runParams.put(AbilityKey.IsCombatDamage, originalParams.get(AbilityKey.IsCombat));
// game.getTriggerHandler().runTrigger(TriggerType.DamagePrevented, runParams, false); // game.getTriggerHandler().runTrigger(TriggerType.DamagePrevented, runParams, false);
} }

View File

@@ -58,18 +58,16 @@ public class ReplaceSplitDamageEffect extends SpellAbilityEffect {
} }
Card sourceLKI = (Card) sa.getReplacingObject(AbilityKey.Source); Card sourceLKI = (Card) sa.getReplacingObject(AbilityKey.Source);
GameEntity target = (GameEntity) sa.getReplacingObject(AbilityKey.Target);
GameEntity obj = (GameEntity) list.get(0);
boolean isCombat = (Boolean) originalParams.get(AbilityKey.IsCombat);
CardDamageMap damageMap = (CardDamageMap) originalParams.get(AbilityKey.DamageMap); CardDamageMap damageMap = (CardDamageMap) originalParams.get(AbilityKey.DamageMap);
CardDamageMap preventMap = (CardDamageMap) originalParams.get(AbilityKey.PreventMap); CardDamageMap preventMap = (CardDamageMap) originalParams.get(AbilityKey.PreventMap);
GameEntityCounterTable counterTable = (GameEntityCounterTable) originalParams.get(AbilityKey.CounterTable); GameEntityCounterTable counterTable = (GameEntityCounterTable) originalParams.get(AbilityKey.CounterTable);
SpellAbility cause = (SpellAbility) originalParams.get(AbilityKey.Cause); SpellAbility cause = (SpellAbility) originalParams.get(AbilityKey.Cause);
damageMap.put(sourceLKI, obj, n);
boolean isCombat = (Boolean) originalParams.get(AbilityKey.IsCombat); obj.replaceDamage(n, sourceLKI, isCombat, damageMap, preventMap, counterTable, cause);
boolean noPrevention = (Boolean) originalParams.get(AbilityKey.NoPreventDamage);
GameEntity obj = (GameEntity) list.get(0);
obj.addDamage(n, sourceLKI, isCombat, noPrevention, damageMap, preventMap, counterTable, cause);
} }
// no damage for original target anymore // no damage for original target anymore

View File

@@ -5136,24 +5136,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return total; return total;
} }
public final void addCombatDamage(final Map<Card, Integer> map, final CardDamageMap damageMap, final CardDamageMap preventMap, GameEntityCounterTable counterTable) {
for (final Entry<Card, Integer> entry : map.entrySet()) {
addCombatDamage(entry.getValue(), entry.getKey(), damageMap, preventMap, counterTable);
}
}
/*
* (non-Javadoc)
* @see forge.game.GameEntity#addCombatDamageBase(int, forge.game.card.Card, forge.game.card.CardDamageMap, forge.game.GameEntityCounterTable)
*/
@Override
protected int addCombatDamageBase(final int damage, final Card source, CardDamageMap damageMap, GameEntityCounterTable counterTable) {
if (isInPlay()) {
return super.addCombatDamageBase(damage, source, damageMap, counterTable);
}
return 0;
}
public final boolean canDamagePrevented(final boolean isCombat) { public final boolean canDamagePrevented(final boolean isCombat) {
CardCollection list = new CardCollection(getGame().getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)); CardCollection list = new CardCollection(getGame().getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES));
list.add(this); list.add(this);
@@ -5246,19 +5228,12 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return restDamage; return restDamage;
} }
public final void addDamage(final Map<Card, Integer> sourcesMap, CardDamageMap damageMap, GameEntityCounterTable counterTable) {
for (final Entry<Card, Integer> entry : sourcesMap.entrySet()) {
// damage prevention is already checked!
addDamageAfterPrevention(entry.getValue(), entry.getKey(), true, damageMap, counterTable);
}
}
/** /**
* This function handles damage after replacement and prevention effects are * This function handles damage after replacement and prevention effects are
* applied. * applied.
*/ */
@Override @Override
public final int addDamageAfterPrevention(final int damageIn, final Card source, final boolean isCombat, CardDamageMap damageMap, GameEntityCounterTable counterTable) { public final int addDamageAfterPrevention(final int damageIn, final Card source, final boolean isCombat, GameEntityCounterTable counterTable) {
if (damageIn <= 0) { if (damageIn <= 0) {
return 0; // Rule 119.8 return 0; // Rule 119.8
@@ -5330,8 +5305,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
game.fireEvent(new GameEventCardDamaged(this, source, damageIn, damageType)); game.fireEvent(new GameEventCardDamaged(this, source, damageIn, damageType));
} }
damageMap.put(source, this, damageIn);
if (excess > 0) { if (excess > 0) {
// Run triggers // Run triggers
runParams = AbilityKey.newMap(); runParams = AbilityKey.newMap();

View File

@@ -18,8 +18,6 @@ import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameObjectPredicates; import forge.game.GameObjectPredicates;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.keyword.Keyword;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> { public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
@@ -50,7 +48,7 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
} }
} }
public void triggerDamageDoneOnce(boolean isCombat, final Game game, final SpellAbility sa) { public void triggerDamageDoneOnce(boolean isCombat, final Game game) {
// Source -> Targets // Source -> Targets
for (Map.Entry<Card, Map<GameEntity, Integer>> e : rowMap().entrySet()) { for (Map.Entry<Card, Map<GameEntity, Integer>> e : rowMap().entrySet()) {
final Card sourceLKI = e.getKey(); final Card sourceLKI = e.getKey();
@@ -65,10 +63,6 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
runParams.put(AbilityKey.IsCombatDamage, isCombat); runParams.put(AbilityKey.IsCombatDamage, isCombat);
game.getTriggerHandler().runTrigger(TriggerType.DamageDealtOnce, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.DamageDealtOnce, runParams, false);
if (sourceLKI.hasKeyword(Keyword.LIFELINK)) {
sourceLKI.getController().gainLife(sum, sourceLKI, sa);
}
} }
} }
// Targets -> Source // Targets -> Source

View File

@@ -73,13 +73,10 @@ public class Combat {
private final Multimap<GameEntity, AttackingBand> attackedByBands = Multimaps.synchronizedMultimap(ArrayListMultimap.create()); private final Multimap<GameEntity, AttackingBand> attackedByBands = Multimaps.synchronizedMultimap(ArrayListMultimap.create());
private final Multimap<AttackingBand, Card> blockedBands = Multimaps.synchronizedMultimap(ArrayListMultimap.create()); private final Multimap<AttackingBand, Card> blockedBands = Multimaps.synchronizedMultimap(ArrayListMultimap.create());
private final Map<Card, Integer> defendingDamageMap = Maps.newHashMap();
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 CardDamageMap dealtDamageTo = new CardDamageMap(); private CardDamageMap damageMap = new CardDamageMap();
private boolean dividedToPlayer = false;
// 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();
@@ -113,9 +110,6 @@ public class Combat {
for (Entry<AttackingBand, Card> entry : combat.blockedBands.entries()) { for (Entry<AttackingBand, Card> entry : combat.blockedBands.entries()) {
blockedBands.put(bandsMap.get(entry.getKey()), map.map(entry.getValue())); blockedBands.put(bandsMap.get(entry.getKey()), map.map(entry.getValue()));
} }
for (Entry<Card, Integer> entry : combat.defendingDamageMap.entrySet()) {
defendingDamageMap.put(map.map(entry.getKey()), entry.getValue());
}
for (Entry<Card, CardCollection> entry : combat.attackersOrderedForDamageAssignment.entrySet()) { for (Entry<Card, CardCollection> entry : combat.attackersOrderedForDamageAssignment.entrySet()) {
attackersOrderedForDamageAssignment.put(map.map(entry.getKey()), map.mapCollection(entry.getValue())); attackersOrderedForDamageAssignment.put(map.map(entry.getKey()), map.mapCollection(entry.getValue()));
@@ -124,8 +118,8 @@ public class Combat {
blockersOrderedForDamageAssignment.put(map.map(entry.getKey()), map.mapCollection(entry.getValue())); 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... // Note: Doesn't currently set up lkiCache, since it's just a cache and not strictly needed...
for (Table.Cell<Card, GameEntity, Integer> entry : combat.dealtDamageTo.cellSet()) { for (Table.Cell<Card, GameEntity, Integer> entry : combat.damageMap.cellSet()) {
dealtDamageTo.put(map.map(entry.getRowKey()), map.map(entry.getColumnKey()), entry.getValue()); damageMap.put(map.map(entry.getRowKey()), map.map(entry.getColumnKey()), entry.getValue());
} }
attackConstraints = new AttackConstraints(this); attackConstraints = new AttackConstraints(this);
@@ -170,7 +164,6 @@ public class Combat {
attackableEntries.clear(); attackableEntries.clear();
attackedByBands.clear(); attackedByBands.clear();
blockedBands.clear(); blockedBands.clear();
defendingDamageMap.clear();
attackersOrderedForDamageAssignment.clear(); attackersOrderedForDamageAssignment.clear();
blockersOrderedForDamageAssignment.clear(); blockersOrderedForDamageAssignment.clear();
lkiCache.clear(); lkiCache.clear();
@@ -728,6 +721,7 @@ public class Combat {
Map<Card, Integer> map = assigningPlayer.getController().assignCombatDamage(blocker, attackers, damage, null, assigningPlayer != blocker.getController()); Map<Card, Integer> map = assigningPlayer.getController().assignCombatDamage(blocker, attackers, damage, null, assigningPlayer != blocker.getController());
for (Entry<Card, Integer> dt : map.entrySet()) { for (Entry<Card, Integer> dt : map.entrySet()) {
dt.getKey().addAssignedDamage(dt.getValue(), blocker); dt.getKey().addAssignedDamage(dt.getValue(), blocker);
damageMap.put(blocker, dt.getKey(), dt.getValue());
} }
} }
} }
@@ -736,7 +730,6 @@ public class Combat {
private final boolean assignAttackersDamage(boolean firstStrikeDamage) { private final boolean assignAttackersDamage(boolean firstStrikeDamage) {
// Assign damage by Attackers // Assign damage by Attackers
defendingDamageMap.clear(); // this should really happen in deal damage
CardCollection orderedBlockers = null; CardCollection orderedBlockers = null;
final CardCollection attackers = getAttackers(); final CardCollection attackers = getAttackers();
boolean assignedDamage = false; boolean assignedDamage = false;
@@ -784,20 +777,19 @@ public class Combat {
} }
} }
assignedDamage = true; assignedDamage = true;
GameEntity defender = getDefenderByAttacker(band);
// If the Attacker is unblocked, or it's a trampler and has 0 blockers, deal damage to defender // If the Attacker is unblocked, or it's a trampler and has 0 blockers, deal damage to defender
if (orderedBlockers == null || orderedBlockers.isEmpty()) { if (orderedBlockers == null || orderedBlockers.isEmpty()) {
if (trampler || !band.isBlocked()) { // this is called after declare blockers, no worries 'bout nulls in isBlocked if (trampler || !band.isBlocked()) { // this is called after declare blockers, no worries 'bout nulls in isBlocked
addDefendingDamage(damageDealt, attacker); damageMap.put(attacker, defender, damageDealt);
} // No damage happens if blocked but no blockers left } // No damage happens if blocked but no blockers left
} }
else { else {
GameEntity defender = getDefenderByAttacker(band);
Player assigningPlayer = getAttackingPlayer(); Player assigningPlayer = getAttackingPlayer();
// Defensive Formation is very similar to Banding with Blockers // Defensive Formation is very similar to Banding with Blockers
// It allows the defending player to assign damage instead of the attacking player // It allows the defending player to assign damage instead of the attacking player
if (defender instanceof Card && divideCombatDamageAsChoose) { if (defender instanceof Card && divideCombatDamageAsChoose) {
defender = getDefenderPlayerByAttacker(attacker); defender = getDefenderPlayerByAttacker(attacker);
dividedToPlayer = true;
} }
if (defender instanceof Player && defender.hasKeyword("You assign combat damage of each creature attacking you.")) { if (defender instanceof Player && defender.hasKeyword("You assign combat damage of each creature attacking you.")) {
assigningPlayer = (Player)defender; assigningPlayer = (Player)defender;
@@ -810,10 +802,15 @@ public class Combat {
damageDealt, defender, divideCombatDamageAsChoose || getAttackingPlayer() != assigningPlayer); damageDealt, defender, divideCombatDamageAsChoose || getAttackingPlayer() != assigningPlayer);
for (Entry<Card, Integer> dt : map.entrySet()) { for (Entry<Card, Integer> dt : map.entrySet()) {
if (dt.getKey() == null) { if (dt.getKey() == null) {
if (dt.getValue() > 0) if (dt.getValue() > 0) {
addDefendingDamage(dt.getValue(), attacker); if (defender instanceof Card) {
((Card) defender).addAssignedDamage(dt.getValue(), attacker);
}
damageMap.put(attacker, defender, dt.getValue());
}
} else { } else {
dt.getKey().addAssignedDamage(dt.getValue(), attacker); dt.getKey().addAssignedDamage(dt.getValue(), attacker);
damageMap.put(attacker, dt.getKey(), dt.getValue());
} }
} }
} // if !hasFirstStrike ... } // if !hasFirstStrike ...
@@ -833,24 +830,6 @@ public class Combat {
return !firstStrikeDamage && !combatantsThatDealtFirstStrikeDamage.contains(combatant); return !firstStrikeDamage && !combatantsThatDealtFirstStrikeDamage.contains(combatant);
} }
// Damage to whatever was protected there.
private final void addDefendingDamage(final int n, final Card source) {
final GameEntity ge = getDefenderByAttacker(source);
if (ge instanceof Card && !dividedToPlayer) {
final Card planeswalker = (Card) ge;
planeswalker.addAssignedDamage(n, source);
return;
}
if (!defendingDamageMap.containsKey(source)) {
defendingDamageMap.put(source, n);
}
else {
defendingDamageMap.put(source, defendingDamageMap.get(source) + n);
}
}
public final boolean assignCombatDamage(boolean firstStrikeDamage) { public final boolean assignCombatDamage(boolean firstStrikeDamage) {
boolean assignedDamage = assignAttackersDamage(firstStrikeDamage); boolean assignedDamage = assignAttackersDamage(firstStrikeDamage);
assignedDamage |= assignBlockersDamage(firstStrikeDamage); assignedDamage |= assignBlockersDamage(firstStrikeDamage);
@@ -862,7 +841,7 @@ public class Combat {
} }
public final CardDamageMap getDamageMap() { public final CardDamageMap getDamageMap() {
return dealtDamageTo; return damageMap;
} }
public void dealAssignedDamage() { public void dealAssignedDamage() {
@@ -872,49 +851,7 @@ public class Combat {
CardDamageMap preventMap = new CardDamageMap(); CardDamageMap preventMap = new CardDamageMap();
GameEntityCounterTable counterTable = new GameEntityCounterTable(); GameEntityCounterTable counterTable = new GameEntityCounterTable();
// This function handles both Regular and First Strike combat assignment game.getAction().dealDamage(true, damageMap, preventMap, counterTable, null);
for (final Entry<Card, Integer> entry : defendingDamageMap.entrySet()) {
GameEntity defender = getDefenderByAttacker(entry.getKey());
if (dividedToPlayer) {
defender = getDefenderPlayerByAttacker(entry.getKey());
}
if (defender instanceof Player) { // player
defender.addCombatDamage(entry.getValue(), entry.getKey(), dealtDamageTo, preventMap, counterTable);
}
else if (defender instanceof Card) { // planeswalker
((Card) defender).getController().addCombatDamage(entry.getValue(), entry.getKey(), dealtDamageTo, preventMap, counterTable);
}
}
// this can be much better below here...
final CardCollection combatants = new CardCollection();
combatants.addAll(getAttackers());
combatants.addAll(getAllBlockers());
combatants.addAll(getDefendingPlaneswalkers());
combatants.addAll(getDefendersCreatures());
for (final Card c : combatants) {
// if no assigned damage to resolve, move to next
if (c.getTotalAssignedDamage() == 0) {
continue;
}
c.addCombatDamage(c.getAssignedDamageMap(), dealtDamageTo, preventMap, counterTable);
c.clearAssignedDamage();
}
preventMap.triggerPreventDamage(true);
preventMap.clear();
// This was deeper before, but that resulted in the stack entry acting like before.
// Run the trigger to deal combat damage once
// LifeLink for Combat Damage at this place
dealtDamageTo.triggerDamageDoneOnce(true, game, null);
dealtDamageTo.clear();
counterTable.triggerCountersPutAll(game);
counterTable.clear();
// copy last state again for dying replacement effects // copy last state again for dying replacement effects
game.copyLastState(); game.copyLastState();

View File

@@ -71,15 +71,9 @@ public class CostDamage extends CostPart {
CardDamageMap preventMap = new CardDamageMap(); CardDamageMap preventMap = new CardDamageMap();
GameEntityCounterTable table = new GameEntityCounterTable(); GameEntityCounterTable table = new GameEntityCounterTable();
payer.addDamage(decision.c, source, damageMap, preventMap, table, sa); damageMap.put(source, payer, decision.c);
source.getGame().getAction().dealDamage(false, damageMap, preventMap, table, sa);
preventMap.triggerPreventDamage(false);
damageMap.triggerDamageDoneOnce(false, source.getGame(), sa);
table.triggerCountersPutAll(payer.getGame());
preventMap.clear();
damageMap.clear();
table.clear();
return decision.c > 0; return decision.c > 0;
} }

View File

@@ -68,7 +68,6 @@ import forge.game.ability.effects.DetachedCardEffect;
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.CardFactoryUtil; import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
@@ -636,7 +635,7 @@ 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, CardDamageMap damageMap, GameEntityCounterTable counterTable) { public final int addDamageAfterPrevention(final int amount, final Card source, final boolean isCombat, GameEntityCounterTable counterTable) {
if (amount <= 0) { if (amount <= 0) {
return 0; return 0;
} }
@@ -703,9 +702,6 @@ public class Player extends GameEntity implements Comparable<Player> {
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;
} }

View File

@@ -282,18 +282,19 @@ public class ReplacementHandler {
} }
private void putPreventMapEntry(final Map<AbilityKey, Object> runParams) { private void putPreventMapEntry(final Map<AbilityKey, Object> runParams) {
// Set prevent map entry Card sourceLKI = (Card) runParams.get(AbilityKey.DamageSource);
CardDamageMap preventMap = (CardDamageMap) runParams.get(AbilityKey.PreventMap);
Card source = (Card) runParams.get(AbilityKey.DamageSource);
GameEntity target = (GameEntity) runParams.get(AbilityKey.Affected); GameEntity target = (GameEntity) runParams.get(AbilityKey.Affected);
Integer damage = (Integer) runParams.get(AbilityKey.DamageAmount); Integer damage = (Integer) runParams.get(AbilityKey.DamageAmount);
preventMap.put(source, target, damage);
// Set prevent map entry
CardDamageMap preventMap = (CardDamageMap) runParams.get(AbilityKey.PreventMap);
preventMap.put(sourceLKI, target, damage);
// Following codes are commented out since DamagePrevented trigger is currently not used by any Card. // Following codes are commented out since DamagePrevented trigger is currently not used by any Card.
// final Map<AbilityKey, Object> trigParams = AbilityKey.newMap(); // final Map<AbilityKey, Object> trigParams = AbilityKey.newMap();
// trigParams.put(AbilityKey.DamageTarget, target); // trigParams.put(AbilityKey.DamageTarget, target);
// trigParams.put(AbilityKey.DamageAmount, damage); // trigParams.put(AbilityKey.DamageAmount, damage);
// trigParams.put(AbilityKey.DamageSource, source); // trigParams.put(AbilityKey.DamageSource, sourceLKI);
// trigParams.put(AbilityKey.IsCombatDamage, runParams.get(AbilityKey.IsCombat)); // trigParams.put(AbilityKey.IsCombatDamage, runParams.get(AbilityKey.IsCombat));
// game.getTriggerHandler().runTrigger(TriggerType.DamagePrevented, trigParams, false); // game.getTriggerHandler().runTrigger(TriggerType.DamagePrevented, trigParams, false);
} }

View File

@@ -45,6 +45,7 @@ import forge.game.ForgeScript;
import forge.game.Game; import forge.game.Game;
import forge.game.GameActionUtil; import forge.game.GameActionUtil;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameEntityCounterTable;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.IHasSVars; import forge.game.IHasSVars;
import forge.game.IIdentifiable; import forge.game.IIdentifiable;
@@ -186,6 +187,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
private CardDamageMap damageMap = null; private CardDamageMap damageMap = null;
private CardDamageMap preventMap = null; private CardDamageMap preventMap = null;
private GameEntityCounterTable counterTable = null;
private CardZoneTable changeZoneTable = null; private CardZoneTable changeZoneTable = null;
public CardCollection getLastStateBattlefield() { public CardCollection getLastStateBattlefield() {
@@ -1047,6 +1049,9 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
if (preventMap != null) { if (preventMap != null) {
clone.preventMap = new CardDamageMap(preventMap); clone.preventMap = new CardDamageMap(preventMap);
} }
if (counterTable != null) {
clone.counterTable = new GameEntityCounterTable(counterTable);
}
if (changeZoneTable != null) { if (changeZoneTable != null) {
clone.changeZoneTable = new CardZoneTable(); clone.changeZoneTable = new CardZoneTable();
clone.changeZoneTable.putAll(changeZoneTable); clone.changeZoneTable.putAll(changeZoneTable);
@@ -2231,6 +2236,15 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return null; return null;
} }
public GameEntityCounterTable getCounterTable() {
if (counterTable != null) {
return counterTable;
} else if (getParent() != null) {
return getParent().getCounterTable();
}
return null;
}
public CardZoneTable getChangeZoneTable() { public CardZoneTable getChangeZoneTable() {
if (changeZoneTable != null) { if (changeZoneTable != null) {
return changeZoneTable; return changeZoneTable;
@@ -2246,6 +2260,9 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public void setPreventMap(final CardDamageMap map) { public void setPreventMap(final CardDamageMap map) {
preventMap = map; preventMap = map;
} }
public void setCounterTable(final GameEntityCounterTable table) {
counterTable = table;
}
public void setChangeZoneTable(final CardZoneTable table) { public void setChangeZoneTable(final CardZoneTable table) {
changeZoneTable = table; changeZoneTable = table;
} }

View File

@@ -6,7 +6,7 @@ T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ CoinFlip | TriggerDescription$
T:Mode$ Blocks | ValidCard$ Card.Self | Execute$ CoinFlip | Secondary$ True | TriggerDescription$ Whenever CARDNAME attacks or blocks, flip a coin. If you lose the flip, the next time it would deal combat damage this turn, it deals that damage to you instead. T:Mode$ Blocks | ValidCard$ Card.Self | Execute$ CoinFlip | Secondary$ True | TriggerDescription$ Whenever CARDNAME attacks or blocks, flip a coin. If you lose the flip, the next time it would deal combat damage this turn, it deals that damage to you instead.
SVar:CoinFlip:DB$ FlipACoin | LoseSubAbility$ CreateEffect SVar:CoinFlip:DB$ FlipACoin | LoseSubAbility$ CreateEffect
SVar:CreateEffect:DB$ Effect | Name$ Goblin Psychopath Effect | ReplacementEffects$ EventDamageDone SVar:CreateEffect:DB$ Effect | Name$ Goblin Psychopath Effect | ReplacementEffects$ EventDamageDone
SVar:EventDamageDone:Event$ DamageDone | ValidSource$ Card.EffectSource | ReplaceWith$ DamageYou | IsCombat$ True | Description$ The next time EFFECTSOURCE would deal combat damage this turn, it deals that damage to you instead. SVar:EventDamageDone:Event$ DamageDone | ValidSource$ Card.EffectSource | DamageTarget$ You | ReplaceWith$ DamageYou | IsCombat$ True | Description$ The next time EFFECTSOURCE would deal combat damage this turn, it deals that damage to you instead.
SVar:DamageYou:DB$ ReplaceEffect | VarName$ Affected | VarValue$ You | VarType$ Player | SubAbility$ ExileEffect SVar:DamageYou:DB$ ReplaceEffect | VarName$ Affected | VarValue$ You | VarType$ Player | SubAbility$ ExileEffect
SVar:ExileEffect:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile SVar:ExileEffect:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile
SVar:PsychoX:ReplaceCount$DamageAmount SVar:PsychoX:ReplaceCount$DamageAmount

View File

@@ -3,7 +3,7 @@ ManaCost:2 W W
Types:Enchantment Types:Enchantment
K:ETBReplacement:Other:ChooseColor K:ETBReplacement:Other:ChooseColor
SVar:ChooseColor:DB$ ChooseColor | Defined$ You | AILogic$ MostProminentInHumanDeck | SpellDescription$ As CARDNAME enters the battlefield, choose a color. SVar:ChooseColor:DB$ ChooseColor | Defined$ You | AILogic$ MostProminentInHumanDeck | SpellDescription$ As CARDNAME enters the battlefield, choose a color.
R:Event$ DamageDone | ValidTarget$ You | ActiveZones$ Battlefield | ValidSource$ Instant.ChosenColor,Sorcery.ChosenColor | ReplaceWith$ HarshDmg | Description$ If an instant or sorcery spell of the chosen color would deal damage to you, it deals that damage to its controller instead. R:Event$ DamageDone | ValidTarget$ You | ActiveZones$ Battlefield | ValidSource$ Instant.ChosenColor,Sorcery.ChosenColor | DamageTarget$ ReplacedSourceController | ReplaceWith$ HarshDmg | Description$ If an instant or sorcery spell of the chosen color would deal damage to you, it deals that damage to its controller instead.
SVar:HarshDmg:DB$ ReplaceEffect | VarName$ Affected | VarValue$ ReplacedSourceController | VarType$ Player SVar:HarshDmg:DB$ ReplaceEffect | VarName$ Affected | VarValue$ ReplacedSourceController | VarType$ Player
AI:RemoveDeck:Random AI:RemoveDeck:Random
SVar:Picture:http://www.wizards.com/global/images/magic/general/harsh_judgment.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/harsh_judgment.jpg

View File

@@ -3,7 +3,7 @@ ManaCost:2 W
Types:Creature Human Rebel Types:Creature Human Rebel
PT:1/3 PT:1/3
A:AB$ Effect | Cost$ 2 W | ValidTgts$ Creature.attacking | TgtPrompt$ Select target attacking creature | IsCurse$ True | ReplacementEffects$ DamageShielded | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | AILogic$ Fog | SpellDescription$ The next time target attacking creature would deal combat damage to CARDNAME this turn, that creature deals that damage to itself instead. A:AB$ Effect | Cost$ 2 W | ValidTgts$ Creature.attacking | TgtPrompt$ Select target attacking creature | IsCurse$ True | ReplacementEffects$ DamageShielded | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | AILogic$ Fog | SpellDescription$ The next time target attacking creature would deal combat damage to CARDNAME this turn, that creature deals that damage to itself instead.
SVar:DamageShielded:Event$ DamageDone | IsCombat$ True | ValidSource$ Card.IsRemembered | ValidTarget$ Card.EffectSource | ReplaceWith$ DmgSelf | Description$ The next time the targeted attacking creature would deal combat damage to EFFECTSOURCE this turn, that creature deals that damage to itself instead. SVar:DamageShielded:Event$ DamageDone | IsCombat$ True | ValidSource$ Card.IsRemembered | ValidTarget$ Card.EffectSource | ReplaceWith$ DmgSelf | DamageTarget$ Remembered | Description$ The next time the targeted attacking creature would deal combat damage to EFFECTSOURCE this turn, that creature deals that damage to itself instead.
SVar:DmgSelf:DB$ ReplaceEffect | VarName$ Affected | VarValue$ Remembered | VarType$ Card | SubAbility$ ExileEffect SVar:DmgSelf:DB$ ReplaceEffect | VarName$ Affected | VarValue$ Remembered | VarType$ Card | SubAbility$ ExileEffect
SVar:ExileEffect:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile SVar:ExileEffect:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile
AI:RemoveDeck:All AI:RemoveDeck:All