- Highly experimental: implement an in-between-zone-change LKI information saving mechanism that allows the game to store and retrieve information about the card prior to its latest zone change (currently this is preserved as long as stack is resolving and is cleared whenever the stack is empty after resolution).

- Currently this is used by damage-dealing abilities (Damage*Effect). This fixes e.g. Nylea's Bow + sacrificing a creature equipped with Mortarpod to deal 1 damage to opponent's creature (the damage dealt has to be lethal (deathtouch) according to the LKI information about the sacrificed card).
- This is a rather intrusive and experimental change that is likely to affect something in an unwanted way, please test and feel free to improve.
This commit is contained in:
Agetian
2015-10-10 18:15:59 +00:00
parent c55a4a49ba
commit 589cc37e11
7 changed files with 54 additions and 14 deletions

View File

@@ -42,6 +42,7 @@ import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardUtil;
import forge.game.card.CardView;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
@@ -71,6 +72,7 @@ import forge.util.Aggregates;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView;
import forge.util.Visitor;
import java.util.HashMap;
/**
* Represents the state of a <i>single game</i>, a new instance is created for each game.
@@ -142,6 +144,24 @@ public class Game {
return list;
}
// methods that deal with saving, retrieving and clearing LKI information about cards on zone change
private final HashMap<Integer, Card> changeZoneLKIInfo = new HashMap<>();
public final void setChangeZoneLKIInfo(Card c) {
if (c == null) {
return;
}
changeZoneLKIInfo.put(c.getId(), CardUtil.getLKICopy(c));
}
public final Card getChangeZoneLKIInfo(Card c) {
if (c == null) {
return null;
}
return changeZoneLKIInfo.containsKey(c.getId()) ? changeZoneLKIInfo.get(c.getId()) : c;
}
public final void clearChangeZoneLKIInfo() {
changeZoneLKIInfo.clear();
}
private final GameEntityCache<SpellAbility, SpellAbilityView> spabCache = new GameEntityCache<>();
public SpellAbility getSpellAbility(final SpellAbilityView view) {
return spabCache.get(view);
@@ -729,4 +749,5 @@ public class Game {
}
return rarities;
}
}

View File

@@ -105,6 +105,8 @@ public class GameAction {
}
public Card changeZone(final Zone zoneFrom, Zone zoneTo, final Card c, Integer position) {
game.setChangeZoneLKIInfo(c);
if (c.isCopiedSpell() || (c.isImmutable() && zoneTo.is(ZoneType.Exile))) {
// Remove Effect from command immediately, this is essential when some replacement
// effects happen during the resolving of a spellability ("the next time ..." effect)

View File

@@ -45,6 +45,7 @@ public class DamageAllEffect extends SpellAbilityEffect {
public void resolve(SpellAbility sa) {
final List<Card> definedSources = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DamageSource"), sa);
final Card card = definedSources.get(0);
final Card sourceLKI = card.getGame().getChangeZoneLKIInfo(card);
final Card source = sa.getHostCard();
final Game game = sa.getActivatingPlayer().getGame();
@@ -74,7 +75,7 @@ public class DamageAllEffect extends SpellAbilityEffect {
list = AbilityUtils.filterListByType(list, sa.getParam("ValidCards"), sa);
for (final Card c : list) {
if (c.addDamage(dmg, card) && (sa.hasParam("RememberDamaged") || sa.hasParam("RememberDamagedCreature"))) {
if (c.addDamage(dmg, sourceLKI) && (sa.hasParam("RememberDamaged") || sa.hasParam("RememberDamagedCreature"))) {
source.addRemembered(c);
}
}
@@ -82,7 +83,7 @@ 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, card) && (sa.hasParam("RememberDamaged") || sa.hasParam("RememberDamagedPlayer"))) {
if (p.addDamage(dmg, sourceLKI) && (sa.hasParam("RememberDamaged") || sa.hasParam("RememberDamagedPlayer"))) {
source.addRemembered(p);
}
}

View File

@@ -117,6 +117,7 @@ public class DamageDealEffect extends SpellAbilityEffect {
return;
}
final Card source = definedSources.get(0);
final Card sourceLKI = sa.getHostCard().getGame().getChangeZoneLKIInfo(definedSources.get(0));
if (divideOnResolution) {
// Dividing Damage up to multiple targets using combat damage box
@@ -135,9 +136,9 @@ public class DamageDealEffect extends SpellAbilityEffect {
}
Player assigningPlayer = players.get(0);
Map<Card, Integer> map = assigningPlayer.getController().assignCombatDamage(source, assigneeCards, dmg, null, true);
Map<Card, Integer> map = assigningPlayer.getController().assignCombatDamage(sourceLKI, assigneeCards, dmg, null, true);
for (Entry<Card, Integer> dt : map.entrySet()) {
dt.getKey().addDamage(dt.getValue(), source);
dt.getKey().addDamage(dt.getValue(), sourceLKI);
}
return;
@@ -154,18 +155,18 @@ public class DamageDealEffect extends SpellAbilityEffect {
c.clearAssignedDamage();
}
else if (noPrevention) {
if (c.addDamageWithoutPrevention(dmg, source) && remember) {
if (c.addDamageWithoutPrevention(dmg, sourceLKI) && remember) {
source.addRemembered(c);
}
} else if (combatDmg) {
HashMap<Card, Integer> combatmap = new HashMap<Card, Integer>();
combatmap.put(source, dmg);
combatmap.put(sourceLKI, dmg);
c.addCombatDamage(combatmap);
if (remember) {
source.addRemembered(c);
}
} else {
if (c.addDamage(dmg, source) && remember) {
if (c.addDamage(dmg, sourceLKI) && remember) {
source.addRemembered(c);
}
}
@@ -175,16 +176,16 @@ public class DamageDealEffect extends SpellAbilityEffect {
final Player p = (Player) o;
if (!targeted || p.canBeTargetedBy(sa)) {
if (noPrevention) {
if (p.addDamageWithoutPrevention(dmg, source) && remember) {
if (p.addDamageWithoutPrevention(dmg, sourceLKI) && remember) {
source.addRemembered(p);
}
} else if (combatDmg) {
p.addCombatDamage(dmg, source);
p.addCombatDamage(dmg, sourceLKI);
if (remember) {
source.addRemembered(p);
}
} else {
if (p.addDamage(dmg, source) && remember) {
if (p.addDamage(dmg, sourceLKI) && remember) {
source.addRemembered(p);
}
}

View File

@@ -72,18 +72,20 @@ public class DamageEachEffect extends SpellAbilityEffect {
for (final Object o : tgts) {
for (final Card source : sources) {
final Card sourceLKI = source.getGame().getChangeZoneLKIInfo(source);
final int dmg = CardFactoryUtil.xCount(source, sa.getSVar("X"));
// 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, source);
c.addDamage(dmg, sourceLKI);
}
} else if (o instanceof Player) {
final Player p = (Player) o;
if (!targeted || p.canBeTargetedBy(sa)) {
p.addDamage(dmg, source);
p.addDamage(dmg, sourceLKI);
}
}
}
@@ -92,20 +94,24 @@ public class DamageEachEffect extends SpellAbilityEffect {
if (sa.hasParam("DefinedCards")) {
if (sa.getParam("DefinedCards").equals("Self")) {
for (final Card source : sources) {
final Card sourceLKI = source.getGame().getChangeZoneLKIInfo(source);
final int dmg = CardFactoryUtil.xCount(source, card.getSVar("X"));
// System.out.println(source+" deals "+dmg+" damage to "+source);
source.addDamage(dmg, source);
source.addDamage(dmg, sourceLKI);
}
}
if (sa.getParam("DefinedCards").equals("Remembered")) {
for (final Card source : sources) {
final int dmg = CardFactoryUtil.xCount(source, card.getSVar("X"));
final Card sourceLKI = source.getGame().getChangeZoneLKIInfo(source);
Card rememberedcard;
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, source);
rememberedcard.addDamage(dmg, sourceLKI);
}
}
}

View File

@@ -290,6 +290,10 @@ public final class CardUtil {
newCopy.addImprintedCard(o);
}
newCopy.setChangedCardColors(in.getChangedCardColors());
newCopy.setChangedCardKeywords(in.getChangedCardKeywords());
newCopy.setChangedCardTypes(in.getChangedCardTypes());
return newCopy;
}

View File

@@ -512,6 +512,11 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
if (source.hasStartOfKeyword("Haunt") && !source.isCreature() && game.getZoneOf(source).is(ZoneType.Graveyard)) {
handleHauntForNonPermanents(sa);
}
if (isEmpty()) {
// FIXME: assuming that if the stack is empty, no reason to hold on to old LKI data (everything is a new object). Is this correct?
game.clearChangeZoneLKIInfo();
}
}
private void handleHauntForNonPermanents(final SpellAbility sa) {