* Fixed some problems with State.values using mutable objects.

This commit is contained in:
LevelX2 2016-10-13 15:48:56 +02:00
parent 43c799e889
commit 0974e6b42d
8 changed files with 177 additions and 71 deletions

View file

@ -40,8 +40,9 @@ import mage.game.Game;
*/
public class ActivationInfo {
protected int turnNum;
protected int activationCounter;
protected int turnNum = 0;
protected int activationCounter = 0;
protected String key;
public static ActivationInfo getInstance(Game game, UUID sourceId) {
return ActivationInfo.getInstance(game, sourceId, game.getState().getZoneChangeCounter(sourceId));
@ -49,17 +50,25 @@ public class ActivationInfo {
public static ActivationInfo getInstance(Game game, UUID sourceId, int zoneChangeCounter) {
String key = "ActivationInfo" + sourceId.toString() + zoneChangeCounter;
ActivationInfo activationInfo = (ActivationInfo) game.getState().getValue(key);
if (activationInfo == null) {
activationInfo = new ActivationInfo(game);
game.getState().setValue(key, activationInfo);
Integer activations = (Integer) game.getState().getValue(key);
ActivationInfo activationInfo;
if (activations != null) {
Integer turnNum = (Integer) game.getState().getValue(key + "T");
activationInfo = new ActivationInfo(game, turnNum, activations);
} else {
activationInfo = new ActivationInfo(game, game.getTurnNum(), 0);
}
activationInfo.setKey(key);
return activationInfo;
}
protected ActivationInfo(Game game) {
this.turnNum = game.getTurnNum();
this.activationCounter = 0;
public void setKey(String key) {
this.key = key;
}
protected ActivationInfo(Game game, int turnNum, int activationCounter) {
this.turnNum = turnNum;
this.activationCounter = activationCounter;
}
public void addActivation(Game game) {
@ -69,6 +78,8 @@ public class ActivationInfo {
} else {
activationCounter++;
}
game.getState().setValue(key, activationCounter);
game.getState().setValue(key + "T", turnNum);
}
public int getActivationCounter() {

View file

@ -9,7 +9,6 @@ import mage.abilities.effects.Effect;
import mage.constants.PhaseStep;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.turn.Step;
/**
*
@ -30,10 +29,10 @@ public class DealsDamageToOneOrMoreCreaturesTriggeredAbility extends DealsDamage
if (super.checkTrigger(event, game)) {
// check that combat damage does only once trigger also if multiple creatures were damaged because they block or were blocked by source
if (game.getTurn().getStepType().equals(PhaseStep.COMBAT_DAMAGE) || game.getTurn().getStepType().equals(PhaseStep.FIRST_COMBAT_DAMAGE)) {
Step step = (Step) game.getState().getValue("damageStep" + getOriginalId());
if (!game.getStep().equals(step)) {
Integer stepHash = (Integer) game.getState().getValue("damageStep" + getOriginalId());
if (stepHash == null || game.getStep().hashCode() != stepHash) {
// this ability did not trigger during this damage step
game.getState().setValue("damageStep" + getOriginalId(), game.getStep());
game.getState().setValue("damageStep" + getOriginalId(), game.getStep().hashCode());
return true;
}
} else {

View file

@ -37,6 +37,8 @@ import mage.abilities.costs.mana.AlternateManaPaymentAbility;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.AbilityType;
import mage.constants.ManaType;
import mage.constants.Outcome;
@ -56,17 +58,17 @@ import mage.util.CardUtil;
* applies only after the total cost of the spell with delve is determined.
* 702.65b Multiple instances of delve on the same spell are redundant.
*
* The rules for delve have changed slightly since it was last in an
* expansion. Previously, delve reduced the cost to cast a spell. Under the
* current rules, you exile cards from your graveyard at the same time you pay
* the spells cost. Exiling a card this way is simply another way to pay that
* cost. * Delve doesn't change a spells mana cost or converted mana cost. For
* example, Dead Drops converted mana cost is 10 even if you exiled three cards
* to cast it. * You cant exile cards to pay for the colored mana requirements
* of a spell with delve. * You cant exile more cards than the generic mana
* requirement of a spell with delve. For example, you cant exile more than
* nine cards from your graveyard to cast Dead Drop. * Because delve isn't an
* alternative cost, it can be used in conjunction with alternative costs.
* The rules for delve have changed slightly since it was last in an expansion.
* Previously, delve reduced the cost to cast a spell. Under the current rules,
* you exile cards from your graveyard at the same time you pay the spells
* cost. Exiling a card this way is simply another way to pay that cost. * Delve
* doesn't change a spells mana cost or converted mana cost. For example, Dead
* Drops converted mana cost is 10 even if you exiled three cards to cast it. *
* You cant exile cards to pay for the colored mana requirements of a spell
* with delve. * You cant exile more cards than the generic mana requirement of
* a spell with delve. For example, you cant exile more than nine cards from
* your graveyard to cast Dead Drop. * Because delve isn't an alternative cost,
* it can be used in conjunction with alternative costs.
*
* @author LevelX2
*
@ -155,18 +157,23 @@ class DelveEffect extends OneShotEffect {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
ExileFromGraveCost exileFromGraveCost = (ExileFromGraveCost) source.getCosts().get(0);
List<Card> exiledCards = exileFromGraveCost.getExiledCards();
if (exiledCards.size() > 0) {
Cards toDelve = new CardsImpl();
for (Card card : exiledCards) {
toDelve.add(card);
}
ManaPool manaPool = controller.getManaPool();
manaPool.addMana(new Mana(0, 0, 0, 0, 0, 0, 0, exiledCards.size()), game, source);
manaPool.addMana(new Mana(0, 0, 0, 0, 0, 0, 0, toDelve.size()), game, source);
manaPool.unlockManaType(ManaType.COLORLESS);
String keyString = CardUtil.getCardZoneString("delvedCards", source.getSourceId(), game);
@SuppressWarnings("unchecked")
List<Card> delvedCards = (List<Card>) game.getState().getValue(keyString);
Cards delvedCards = (Cards) game.getState().getValue(keyString);
if (delvedCards == null) {
game.getState().setValue(keyString, exiledCards);
game.getState().setValue(keyString, toDelve);
} else {
delvedCards.addAll(exiledCards);
delvedCards.addAll(toDelve);
}
}
return true;

View file

@ -32,6 +32,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
@ -182,7 +183,11 @@ public class GameState implements Serializable, Copyable<GameState> {
this.turnMods = state.turnMods.copy();
this.watchers = state.watchers.copy();
for (Map.Entry<String, Object> entry : state.values.entrySet()) {
this.values.put(entry.getKey(), entry.getValue());
if (entry.getValue() instanceof HashSet) {
this.values.put(entry.getKey(), (HashSet) ((HashSet) entry.getValue()).clone());
} else {
this.values.put(entry.getKey(), entry.getValue());
}
}
this.zones.putAll(state.zones);
this.simultaneousEvents.addAll(state.simultaneousEvents);
@ -893,8 +898,10 @@ public class GameState implements Serializable, Copyable<GameState> {
/**
* Best only use immutable objects, otherwise the states/values of the
* object may be changed by AI simulation, because the Value objects are not
* copied as the state class is copied.
* object may be changed by AI simulation or rollbacks, because the Value
* objects are not copied as the state class is copied. Mutable supported:
* HashSet with immutable entries (e.g. HashSet< UUID > or HashSet< String
* >)
*
* @param valueId
* @param value