mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 19:58:00 +00:00
SHould fix duplicating mana and duplicating spells on stack
This commit is contained in:
@@ -1,11 +1,9 @@
|
|||||||
package forge.game;
|
package forge.game;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Table;
|
|
||||||
|
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
@@ -148,7 +146,7 @@ public class GameSnapshot {
|
|||||||
// toGame.getTriggerHandler().resetActiveTriggers();
|
// toGame.getTriggerHandler().resetActiveTriggers();
|
||||||
|
|
||||||
if (includeStack) {
|
if (includeStack) {
|
||||||
copyStack(fromGame, toGame);
|
copyStack(fromGame, toGame, restore);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (restore) {
|
if (restore) {
|
||||||
@@ -185,15 +183,47 @@ public class GameSnapshot {
|
|||||||
newPlayer.setMaxHandSize(origPlayer.getMaxHandSize());
|
newPlayer.setMaxHandSize(origPlayer.getMaxHandSize());
|
||||||
newPlayer.setUnlimitedHandSize(origPlayer.isUnlimitedHandSize());
|
newPlayer.setUnlimitedHandSize(origPlayer.isUnlimitedHandSize());
|
||||||
// TODO creatureAttackedThisTurn
|
// TODO creatureAttackedThisTurn
|
||||||
for (Mana m : origPlayer.getManaPool()) {
|
|
||||||
// TODO Mana pool isn't being restored properly?
|
// Copy mana pool
|
||||||
newPlayer.getManaPool().addMana(m, false);
|
copyManaPool(origPlayer, newPlayer);
|
||||||
}
|
|
||||||
newPlayer.setCommanders(origPlayer.getCommanders()); // will be fixed up below
|
newPlayer.setCommanders(origPlayer.getCommanders()); // will be fixed up below
|
||||||
}
|
}
|
||||||
|
|
||||||
private void copyStack(Game fromGame, Game toGame) {
|
private void copyManaPool(Player fromPlayer, Player toPlayer) {
|
||||||
|
Game toGame = toPlayer.getGame();
|
||||||
|
toPlayer.getManaPool().resetPool();
|
||||||
|
for (Mana m : fromPlayer.getManaPool()) {
|
||||||
|
// TODO the mana object here needs to be copied to the new game
|
||||||
|
toPlayer.getManaPool().addMana(m, false);
|
||||||
|
}
|
||||||
|
toPlayer.updateManaForView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyStack(Game fromGame, Game toGame, boolean restore) {
|
||||||
|
// Try to match the StackInstance ID. If we don't find it, generate a new stack instance that matches
|
||||||
|
// If we do find it, we may need to alter the existing stack instance
|
||||||
|
// If we find it and we're restoring, we dont need to do anything
|
||||||
|
|
||||||
|
Map<Integer, SpellAbilityStackInstance> stackIds = new HashMap();
|
||||||
|
for (SpellAbilityStackInstance toEntry : toGame.getStack()) {
|
||||||
|
stackIds.put(toEntry.getId(), toEntry);
|
||||||
|
}
|
||||||
|
|
||||||
for (SpellAbilityStackInstance origEntry : fromGame.getStack()) {
|
for (SpellAbilityStackInstance origEntry : fromGame.getStack()) {
|
||||||
|
int id = origEntry.getId();
|
||||||
|
SpellAbilityStackInstance instance = stackIds.getOrDefault(id, null);
|
||||||
|
|
||||||
|
if (instance != null) {
|
||||||
|
if (!restore) {
|
||||||
|
System.out.println("Might need to alter " + origEntry.getSpellAbility() + " on stack");
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("Adding " + origEntry.getSpellAbility() + " to stack");
|
||||||
|
|
||||||
SpellAbility origSa = origEntry.getSpellAbility();
|
SpellAbility origSa = origEntry.getSpellAbility();
|
||||||
Card origHostCard = origSa.getHostCard();
|
Card origHostCard = origSa.getHostCard();
|
||||||
Card newCard = findBy(toGame, origHostCard);
|
Card newCard = findBy(toGame, origHostCard);
|
||||||
@@ -203,10 +233,16 @@ public class GameSnapshot {
|
|||||||
newCard = createCardCopy(toGame, findBy(toGame, origHostCard.getOwner()), origHostCard);
|
newCard = createCardCopy(toGame, findBy(toGame, origHostCard.getOwner()), origHostCard);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FInd newEntry from origEntrys
|
||||||
|
|
||||||
SpellAbility newSa = null;
|
SpellAbility newSa = null;
|
||||||
if (origSa.isSpell()) {
|
if (origSa.isSpell()) {
|
||||||
newSa = findSAInCard(origSa, newCard);
|
newSa = findSAInCard(origSa, newCard);
|
||||||
|
} else {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Is the SA on the stack?
|
||||||
if (newSa != null) {
|
if (newSa != null) {
|
||||||
newSa.setActivatingPlayer(findBy(toGame, origSa.getActivatingPlayer()), true);
|
newSa.setActivatingPlayer(findBy(toGame, origSa.getActivatingPlayer()), true);
|
||||||
if (origSa.usesTargeting()) {
|
if (origSa.usesTargeting()) {
|
||||||
@@ -220,7 +256,7 @@ public class GameSnapshot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toGame.getStack().add(newSa);
|
toGame.getStack().add(newSa, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,7 +267,6 @@ public class GameSnapshot {
|
|||||||
|
|
||||||
// TODO countersAddedThisTurn
|
// TODO countersAddedThisTurn
|
||||||
|
|
||||||
// I think this is slightly wrong. I Don't think we need player maps
|
|
||||||
if (fromGame.getStartingPlayer() != null) {
|
if (fromGame.getStartingPlayer() != null) {
|
||||||
toGame.setStartingPlayer(findBy(toGame, fromGame.getStartingPlayer()));
|
toGame.setStartingPlayer(findBy(toGame, fromGame.getStartingPlayer()));
|
||||||
}
|
}
|
||||||
@@ -321,108 +356,6 @@ public class GameSnapshot {
|
|||||||
return newCard;
|
return newCard;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addCard(Game toGame, ZoneType zone, Card c) {
|
|
||||||
// Can i delete this?
|
|
||||||
final Player owner = findBy(toGame, c.getOwner());
|
|
||||||
final Card newCard = createCardCopy(toGame, owner, c);
|
|
||||||
|
|
||||||
// TODO ExiledWith
|
|
||||||
|
|
||||||
Player zoneOwner = owner;
|
|
||||||
// everything the CreatureEvaluator checks must be set here
|
|
||||||
if (zone == ZoneType.Battlefield) {
|
|
||||||
zoneOwner = findBy(toGame, c.getController());
|
|
||||||
// TODO: Controllers' list with timestamps should be copied.
|
|
||||||
newCard.setController(zoneOwner, 0);
|
|
||||||
|
|
||||||
if (c.isBattle()) {
|
|
||||||
newCard.setProtectingPlayer(findBy(toGame, c.getProtectingPlayer()));
|
|
||||||
}
|
|
||||||
|
|
||||||
newCard.setCameUnderControlSinceLastUpkeep(c.cameUnderControlSinceLastUpkeep());
|
|
||||||
|
|
||||||
newCard.setPTTable(c.getSetPTTable());
|
|
||||||
newCard.setPTCharacterDefiningTable(c.getSetPTCharacterDefiningTable());
|
|
||||||
|
|
||||||
newCard.setPTBoost(c.getPTBoostTable());
|
|
||||||
// TODO copy by map
|
|
||||||
newCard.setDamage(c.getDamage());
|
|
||||||
newCard.setDamageReceivedThisTurn(c.getDamageReceivedThisTurn());
|
|
||||||
|
|
||||||
newCard.setChangedCardColors(c.getChangedCardColorsTable());
|
|
||||||
newCard.setChangedCardColorsCharacterDefining(c.getChangedCardColorsCharacterDefiningTable());
|
|
||||||
|
|
||||||
newCard.setChangedCardTypes(c.getChangedCardTypesTable());
|
|
||||||
newCard.setChangedCardTypesCharacterDefining(c.getChangedCardTypesCharacterDefiningTable());
|
|
||||||
newCard.setChangedCardKeywords(c.getChangedCardKeywords());
|
|
||||||
newCard.setChangedCardNames(c.getChangedCardNames());
|
|
||||||
|
|
||||||
for (Table.Cell<Long, Long, List<String>> kw : c.getHiddenExtrinsicKeywordsTable().cellSet()) {
|
|
||||||
newCard.addHiddenExtrinsicKeywords(kw.getRowKey(), kw.getColumnKey(), kw.getValue());
|
|
||||||
}
|
|
||||||
newCard.updateKeywordsCache(newCard.getCurrentState());
|
|
||||||
|
|
||||||
// Is any of this really needed?
|
|
||||||
newCard.setTapped(c.isTapped());
|
|
||||||
|
|
||||||
if (c.isFaceDown()) {
|
|
||||||
newCard.turnFaceDown(c.isFaceDown());
|
|
||||||
}
|
|
||||||
newCard.setManifested(c.isManifested());
|
|
||||||
|
|
||||||
newCard.setMonstrous(c.isMonstrous());
|
|
||||||
newCard.setRenowned(c.isRenowned());
|
|
||||||
if (c.isPlaneswalker()) {
|
|
||||||
// Why is this limited to planeswalkers?
|
|
||||||
for (SpellAbility sa : c.getAllSpellAbilities()) {
|
|
||||||
int active = sa.getActivationsThisTurn();
|
|
||||||
if (sa.isPwAbility() && active > 0) {
|
|
||||||
SpellAbility newSa = findSAInCard(sa, newCard);
|
|
||||||
if (newSa != null) {
|
|
||||||
for (int i = 0; i < active; i++) {
|
|
||||||
newCard.addAbilityActivated(newSa);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newCard.setFlipped(c.isFlipped());
|
|
||||||
for (Map.Entry<Long, CardCloneStates> e : c.getCloneStates().entrySet()) {
|
|
||||||
newCard.addCloneState(e.getValue().copy(newCard, true), e.getKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<CounterType, Integer> counters = c.getCounters();
|
|
||||||
if (!counters.isEmpty()) {
|
|
||||||
newCard.setCounters(Maps.newHashMap(counters));
|
|
||||||
}
|
|
||||||
if (c.hasChosenPlayer()) {
|
|
||||||
newCard.setChosenPlayer(findBy(toGame, c.getChosenPlayer()));
|
|
||||||
}
|
|
||||||
if (c.hasChosenType()) {
|
|
||||||
newCard.setChosenType(c.getChosenType());
|
|
||||||
}
|
|
||||||
if (c.hasChosenType2()) {
|
|
||||||
newCard.setChosenType2(c.getChosenType2());
|
|
||||||
}
|
|
||||||
if (c.hasChosenColor()) {
|
|
||||||
newCard.setChosenColors(Lists.newArrayList(c.getChosenColors()));
|
|
||||||
}
|
|
||||||
if (c.hasNamedCard()) {
|
|
||||||
newCard.setNamedCards(Lists.newArrayList(c.getNamedCards()));
|
|
||||||
}
|
|
||||||
newCard.setSVars(c.getSVars());
|
|
||||||
newCard.copyChangedSVarsFrom(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (zone == ZoneType.Stack) {
|
|
||||||
toGame.getStackZone().add(newCard);
|
|
||||||
} else {
|
|
||||||
zoneOwner.getZone(zone).add(newCard);
|
|
||||||
}
|
|
||||||
// Update view?
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SpellAbility findSAInCard(SpellAbility sa, Card c) {
|
private static SpellAbility findSAInCard(SpellAbility sa, Card c) {
|
||||||
String saDesc = sa.getDescription();
|
String saDesc = sa.getDescription();
|
||||||
for (SpellAbility cardSa : c.getAllSpellAbilities()) {
|
for (SpellAbility cardSa : c.getAllSpellAbilities()) {
|
||||||
|
|||||||
@@ -114,6 +114,11 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
|
|||||||
return game.getRules().hasManaBurn() || StaticAbilityUnspentMana.hasManaBurn(owner);
|
return game.getRules().hasManaBurn() || StaticAbilityUnspentMana.hasManaBurn(owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final void resetPool() {
|
||||||
|
// This should only be used to reset the pool to empty by things like restores.
|
||||||
|
floatingMana.clear();
|
||||||
|
}
|
||||||
|
|
||||||
public final List<Mana> clearPool(boolean isEndOfPhase) {
|
public final List<Mana> clearPool(boolean isEndOfPhase) {
|
||||||
// isEndOfPhase parameter: true = end of phase, false = mana drain effect
|
// isEndOfPhase parameter: true = end of phase, false = mana drain effect
|
||||||
List<Mana> cleared = Lists.newArrayList();
|
List<Mana> cleared = Lists.newArrayList();
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import forge.util.TextUtil;
|
|||||||
*/
|
*/
|
||||||
public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
|
public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
|
||||||
private static int maxId = 0;
|
private static int maxId = 0;
|
||||||
private static int nextId() { return ++maxId; }
|
public static int nextId() { return ++maxId; }
|
||||||
|
|
||||||
// At some point I want this functioning more like Target/Target Choices
|
// At some point I want this functioning more like Target/Target Choices
|
||||||
// where the SA has an "active"
|
// where the SA has an "active"
|
||||||
@@ -65,8 +65,11 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
|
|||||||
private final StackItemView view;
|
private final StackItemView view;
|
||||||
|
|
||||||
public SpellAbilityStackInstance(final SpellAbility sa) {
|
public SpellAbilityStackInstance(final SpellAbility sa) {
|
||||||
|
this(sa, nextId());
|
||||||
|
}
|
||||||
|
public SpellAbilityStackInstance(final SpellAbility sa, int assignedId) {
|
||||||
// Base SA info
|
// Base SA info
|
||||||
id = nextId();
|
id = assignedId;
|
||||||
ability = sa;
|
ability = sa;
|
||||||
stackDescription = sa.getStackDescription();
|
stackDescription = sa.getStackDescription();
|
||||||
|
|
||||||
|
|||||||
@@ -235,9 +235,17 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final void add(SpellAbility sp) {
|
public final void add(SpellAbility sp) {
|
||||||
add(sp, null);
|
add(sp, null, SpellAbilityStackInstance.nextId());
|
||||||
}
|
}
|
||||||
|
public final void add(SpellAbility sp, int id) {
|
||||||
|
add(sp, null, id);
|
||||||
|
}
|
||||||
|
|
||||||
public final void add(SpellAbility sp, SpellAbilityStackInstance si) {
|
public final void add(SpellAbility sp, SpellAbilityStackInstance si) {
|
||||||
|
add(sp, si, si.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void add(SpellAbility sp, SpellAbilityStackInstance si, int id) {
|
||||||
final Card source = sp.getHostCard();
|
final Card source = sp.getHostCard();
|
||||||
Player activator = sp.getActivatingPlayer();
|
Player activator = sp.getActivatingPlayer();
|
||||||
|
|
||||||
@@ -321,7 +329,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (frozen && !sp.hasParam("IgnoreFreeze")) {
|
if (frozen && !sp.hasParam("IgnoreFreeze")) {
|
||||||
si = new SpellAbilityStackInstance(sp);
|
si = new SpellAbilityStackInstance(sp, id);
|
||||||
frozenStack.push(si);
|
frozenStack.push(si);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -337,7 +345,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The ability is added to stack HERE
|
// The ability is added to stack HERE
|
||||||
si = push(sp, si);
|
si = push(sp, si, id);
|
||||||
|
|
||||||
// Copied spells aren't cast per se so triggers shouldn't run for them.
|
// Copied spells aren't cast per se so triggers shouldn't run for them.
|
||||||
Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(sp.getHostCard().getController());
|
Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(sp.getHostCard().getController());
|
||||||
@@ -472,7 +480,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Push should only be used by add.
|
// Push should only be used by add.
|
||||||
private SpellAbilityStackInstance push(final SpellAbility sp, SpellAbilityStackInstance si) {
|
private SpellAbilityStackInstance push(final SpellAbility sp, SpellAbilityStackInstance si, int id) {
|
||||||
if (null == sp.getActivatingPlayer()) {
|
if (null == sp.getActivatingPlayer()) {
|
||||||
sp.setActivatingPlayer(sp.getHostCard().getController());
|
sp.setActivatingPlayer(sp.getHostCard().getController());
|
||||||
System.out.println(sp.getHostCard().getName() + " - activatingPlayer not set before adding to stack.");
|
System.out.println(sp.getHostCard().getName() + " - activatingPlayer not set before adding to stack.");
|
||||||
@@ -481,7 +489,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
|||||||
if (sp.isSpell() && sp.getMayPlay() != null) {
|
if (sp.isSpell() && sp.getMayPlay() != null) {
|
||||||
sp.getMayPlay().incMayPlayTurn();
|
sp.getMayPlay().incMayPlayTurn();
|
||||||
}
|
}
|
||||||
si = si == null ? new SpellAbilityStackInstance(sp) : si;
|
si = si == null ? new SpellAbilityStackInstance(sp, id) : si;
|
||||||
|
|
||||||
stack.addFirst(si);
|
stack.addFirst(si);
|
||||||
int stackIndex = stack.size() - 1;
|
int stackIndex = stack.size() - 1;
|
||||||
|
|||||||
@@ -166,13 +166,12 @@ public class HumanPlaySpellAbility {
|
|||||||
// We need to split up this super conditional to know WHY somethings prequisites failed.
|
// We need to split up this super conditional to know WHY somethings prequisites failed.
|
||||||
// Failing because there's no legal targets is different than failing because the player canceled paying costs
|
// Failing because there's no legal targets is different than failing because the player canceled paying costs
|
||||||
|
|
||||||
final boolean announcePrequiste = announceType() && announceValuesLikeX();
|
boolean preCostRequisites = announceType() && announceValuesLikeX();
|
||||||
final boolean abilityRestrictions = ability.checkRestrictions(human);
|
preCostRequisites &= ability.checkRestrictions(human);
|
||||||
final boolean canChooseTargets = (!mayChooseTargets || ability.setupTargets());
|
preCostRequisites &= (!mayChooseTargets || ability.setupTargets());
|
||||||
final boolean canCastTiming = ability.canCastTiming(human);
|
preCostRequisites &= ability.canCastTiming(human);
|
||||||
final boolean isLegalAfterStack = ability.isLegalAfterStack();
|
preCostRequisites &= ability.isLegalAfterStack();
|
||||||
|
|
||||||
final boolean preCostRequisites = announcePrequiste && abilityRestrictions && canChooseTargets && canCastTiming && isLegalAfterStack;
|
|
||||||
final boolean prerequisitesMet = preCostRequisites && (isFree || payment.payCost(new HumanCostDecision(controller, human, ability, ability.isTrigger())));
|
final boolean prerequisitesMet = preCostRequisites && (isFree || payment.payCost(new HumanCostDecision(controller, human, ability, ability.isTrigger())));
|
||||||
|
|
||||||
game.clearTopLibsCast(ability);
|
game.clearTopLibsCast(ability);
|
||||||
|
|||||||
Reference in New Issue
Block a user