mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 12:48:00 +00:00
- 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:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user