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.CardCollectionView;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
|
import forge.game.card.CardUtil;
|
||||||
import forge.game.card.CardView;
|
import forge.game.card.CardView;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
@@ -71,6 +72,7 @@ import forge.util.Aggregates;
|
|||||||
import forge.util.collect.FCollection;
|
import forge.util.collect.FCollection;
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
import forge.util.Visitor;
|
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.
|
* 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;
|
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<>();
|
private final GameEntityCache<SpellAbility, SpellAbilityView> spabCache = new GameEntityCache<>();
|
||||||
public SpellAbility getSpellAbility(final SpellAbilityView view) {
|
public SpellAbility getSpellAbility(final SpellAbilityView view) {
|
||||||
return spabCache.get(view);
|
return spabCache.get(view);
|
||||||
@@ -729,4 +749,5 @@ public class Game {
|
|||||||
}
|
}
|
||||||
return rarities;
|
return rarities;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,8 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Card changeZone(final Zone zoneFrom, Zone zoneTo, final Card c, Integer position) {
|
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))) {
|
if (c.isCopiedSpell() || (c.isImmutable() && zoneTo.is(ZoneType.Exile))) {
|
||||||
// Remove Effect from command immediately, this is essential when some replacement
|
// Remove Effect from command immediately, this is essential when some replacement
|
||||||
// effects happen during the resolving of a spellability ("the next time ..." effect)
|
// 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) {
|
public void resolve(SpellAbility sa) {
|
||||||
final List<Card> definedSources = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DamageSource"), sa);
|
final List<Card> definedSources = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DamageSource"), sa);
|
||||||
final Card card = definedSources.get(0);
|
final Card card = definedSources.get(0);
|
||||||
|
final Card sourceLKI = card.getGame().getChangeZoneLKIInfo(card);
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = sa.getActivatingPlayer().getGame();
|
final Game game = sa.getActivatingPlayer().getGame();
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ public class DamageAllEffect extends SpellAbilityEffect {
|
|||||||
list = AbilityUtils.filterListByType(list, sa.getParam("ValidCards"), sa);
|
list = AbilityUtils.filterListByType(list, sa.getParam("ValidCards"), sa);
|
||||||
|
|
||||||
for (final Card c : list) {
|
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);
|
source.addRemembered(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,7 +83,7 @@ public class DamageAllEffect extends SpellAbilityEffect {
|
|||||||
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) {
|
||||||
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);
|
source.addRemembered(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ public class DamageDealEffect extends SpellAbilityEffect {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final Card source = definedSources.get(0);
|
final Card source = definedSources.get(0);
|
||||||
|
final Card sourceLKI = sa.getHostCard().getGame().getChangeZoneLKIInfo(definedSources.get(0));
|
||||||
|
|
||||||
if (divideOnResolution) {
|
if (divideOnResolution) {
|
||||||
// Dividing Damage up to multiple targets using combat damage box
|
// Dividing Damage up to multiple targets using combat damage box
|
||||||
@@ -135,9 +136,9 @@ public class DamageDealEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Player assigningPlayer = players.get(0);
|
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()) {
|
for (Entry<Card, Integer> dt : map.entrySet()) {
|
||||||
dt.getKey().addDamage(dt.getValue(), source);
|
dt.getKey().addDamage(dt.getValue(), sourceLKI);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -154,18 +155,18 @@ public class DamageDealEffect extends SpellAbilityEffect {
|
|||||||
c.clearAssignedDamage();
|
c.clearAssignedDamage();
|
||||||
}
|
}
|
||||||
else if (noPrevention) {
|
else if (noPrevention) {
|
||||||
if (c.addDamageWithoutPrevention(dmg, source) && remember) {
|
if (c.addDamageWithoutPrevention(dmg, sourceLKI) && remember) {
|
||||||
source.addRemembered(c);
|
source.addRemembered(c);
|
||||||
}
|
}
|
||||||
} else if (combatDmg) {
|
} else if (combatDmg) {
|
||||||
HashMap<Card, Integer> combatmap = new HashMap<Card, Integer>();
|
HashMap<Card, Integer> combatmap = new HashMap<Card, Integer>();
|
||||||
combatmap.put(source, dmg);
|
combatmap.put(sourceLKI, dmg);
|
||||||
c.addCombatDamage(combatmap);
|
c.addCombatDamage(combatmap);
|
||||||
if (remember) {
|
if (remember) {
|
||||||
source.addRemembered(c);
|
source.addRemembered(c);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (c.addDamage(dmg, source) && remember) {
|
if (c.addDamage(dmg, sourceLKI) && remember) {
|
||||||
source.addRemembered(c);
|
source.addRemembered(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,16 +176,16 @@ public class DamageDealEffect extends SpellAbilityEffect {
|
|||||||
final Player p = (Player) o;
|
final Player p = (Player) o;
|
||||||
if (!targeted || p.canBeTargetedBy(sa)) {
|
if (!targeted || p.canBeTargetedBy(sa)) {
|
||||||
if (noPrevention) {
|
if (noPrevention) {
|
||||||
if (p.addDamageWithoutPrevention(dmg, source) && remember) {
|
if (p.addDamageWithoutPrevention(dmg, sourceLKI) && remember) {
|
||||||
source.addRemembered(p);
|
source.addRemembered(p);
|
||||||
}
|
}
|
||||||
} else if (combatDmg) {
|
} else if (combatDmg) {
|
||||||
p.addCombatDamage(dmg, source);
|
p.addCombatDamage(dmg, sourceLKI);
|
||||||
if (remember) {
|
if (remember) {
|
||||||
source.addRemembered(p);
|
source.addRemembered(p);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (p.addDamage(dmg, source) && remember) {
|
if (p.addDamage(dmg, sourceLKI) && remember) {
|
||||||
source.addRemembered(p);
|
source.addRemembered(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,18 +72,20 @@ public class DamageEachEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
for (final Object o : tgts) {
|
for (final Object o : tgts) {
|
||||||
for (final Card source : sources) {
|
for (final Card source : sources) {
|
||||||
|
final Card sourceLKI = source.getGame().getChangeZoneLKIInfo(source);
|
||||||
|
|
||||||
final int dmg = CardFactoryUtil.xCount(source, sa.getSVar("X"));
|
final int dmg = CardFactoryUtil.xCount(source, sa.getSVar("X"));
|
||||||
// System.out.println(source+" deals "+dmg+" damage to "+o.toString());
|
// System.out.println(source+" deals "+dmg+" damage to "+o.toString());
|
||||||
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, source);
|
c.addDamage(dmg, sourceLKI);
|
||||||
}
|
}
|
||||||
|
|
||||||
} 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, source);
|
p.addDamage(dmg, sourceLKI);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,20 +94,24 @@ public class DamageEachEffect extends SpellAbilityEffect {
|
|||||||
if (sa.hasParam("DefinedCards")) {
|
if (sa.hasParam("DefinedCards")) {
|
||||||
if (sa.getParam("DefinedCards").equals("Self")) {
|
if (sa.getParam("DefinedCards").equals("Self")) {
|
||||||
for (final Card source : sources) {
|
for (final Card source : sources) {
|
||||||
|
final Card sourceLKI = source.getGame().getChangeZoneLKIInfo(source);
|
||||||
|
|
||||||
final int dmg = CardFactoryUtil.xCount(source, card.getSVar("X"));
|
final int dmg = CardFactoryUtil.xCount(source, card.getSVar("X"));
|
||||||
// System.out.println(source+" deals "+dmg+" damage to "+source);
|
// System.out.println(source+" deals "+dmg+" damage to "+source);
|
||||||
source.addDamage(dmg, source);
|
source.addDamage(dmg, sourceLKI);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sa.getParam("DefinedCards").equals("Remembered")) {
|
if (sa.getParam("DefinedCards").equals("Remembered")) {
|
||||||
for (final Card source : sources) {
|
for (final Card source : sources) {
|
||||||
final int dmg = CardFactoryUtil.xCount(source, card.getSVar("X"));
|
final int dmg = CardFactoryUtil.xCount(source, card.getSVar("X"));
|
||||||
|
final Card sourceLKI = source.getGame().getChangeZoneLKIInfo(source);
|
||||||
|
|
||||||
Card rememberedcard;
|
Card rememberedcard;
|
||||||
for (final Object o : sa.getHostCard().getRemembered()) {
|
for (final Object o : sa.getHostCard().getRemembered()) {
|
||||||
if (o instanceof Card) {
|
if (o instanceof Card) {
|
||||||
rememberedcard = (Card) o;
|
rememberedcard = (Card) o;
|
||||||
// System.out.println(source + " deals " + dmg + " damage to " + rememberedcard);
|
// 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.addImprintedCard(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newCopy.setChangedCardColors(in.getChangedCardColors());
|
||||||
|
newCopy.setChangedCardKeywords(in.getChangedCardKeywords());
|
||||||
|
newCopy.setChangedCardTypes(in.getChangedCardTypes());
|
||||||
|
|
||||||
return newCopy;
|
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)) {
|
if (source.hasStartOfKeyword("Haunt") && !source.isCreature() && game.getZoneOf(source).is(ZoneType.Graveyard)) {
|
||||||
handleHauntForNonPermanents(sa);
|
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) {
|
private void handleHauntForNonPermanents(final SpellAbility sa) {
|
||||||
|
|||||||
Reference in New Issue
Block a user