* Made a lot of changes to handling of continuous and triggered abilities. This should fix the problems with Mage Singletons like Flyinging / Intimidate / Reach not working. Fixed also #533 and some other problems with copy effects of cards like Clone that did not end if e.g. Clone left the battlefield.

This commit is contained in:
LevelX2 2014-12-25 02:07:40 +01:00
parent f9b80e5c81
commit 4f1368f3de
25 changed files with 316 additions and 169 deletions

View file

@ -536,7 +536,13 @@ public abstract class AbilityImpl implements Ability {
@Override
public void setSourceId(UUID sourceId) {
this.sourceId = sourceId;
if (this.sourceId == null) {
this.sourceId = sourceId;
} else {
if (!(this instanceof MageSingleton)) {
this.sourceId = sourceId;
}
}
}
@Override

View file

@ -44,6 +44,7 @@ import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentCard;
import org.apache.log4j.Logger;
/**
*
@ -129,6 +130,7 @@ public class TriggeredAbilities extends ConcurrentHashMap<String, TriggeredAbili
* @param attachedTo - the object that gained the ability
*/
public void add(TriggeredAbility ability, UUID sourceId, MageObject attachedTo) {
Logger.getLogger(TriggeredAbilities.class).debug("TRIGGER ADDED - sourceId " + sourceId);
this.add(ability, attachedTo);
List<UUID> uuidList = new LinkedList<>();
uuidList.add(sourceId);
@ -138,6 +140,7 @@ public class TriggeredAbilities extends ConcurrentHashMap<String, TriggeredAbili
}
public void add(TriggeredAbility ability, MageObject attachedTo) {
Logger.getLogger(TriggeredAbilities.class).debug("TRIGGER ADDED - id: " + ability.getId() + " source: " +attachedTo.getId() + " - " + attachedTo.getLogName() + " - " + ability.getRule());
this.put(getKey(ability, attachedTo), ability);
}
@ -149,24 +152,25 @@ public class TriggeredAbilities extends ConcurrentHashMap<String, TriggeredAbili
return key;
}
/**
* Removes gained abilities by sourceId
*
* @param sourceId
* @return
*/
public List<String> removeGainedAbilitiesForSource(UUID sourceId) {
public void removeAbilitiesOfSource(UUID sourceId) {
List<String> keysToRemove = new ArrayList<>();
for (Map.Entry<String, List<UUID>> entry : sources.entrySet()) {
if (entry.getValue().contains(sourceId)) {
keysToRemove.add(entry.getKey());
for (String key: this.keySet()) {
if(key.endsWith(sourceId.toString())) {
keysToRemove.add(key);
}
}
for (String key: keysToRemove) {
sources.remove(key);
for(String key: keysToRemove) {
remove(key);
}
return keysToRemove;
}
public void removeAllGainedAbilities() {
Logger.getLogger(TriggeredAbilities.class).debug("REMOVE all gained Triggered Abilities");
for(String key: sources.keySet()) {
this.remove(key);
}
sources.clear();
}
public TriggeredAbilities copy() {

View file

@ -29,9 +29,9 @@
package mage.abilities.effects;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.MageSingleton;
import mage.abilities.dynamicvalue.DynamicValue;
@ -67,8 +67,8 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
protected boolean used = false;
protected boolean discarded = false; // for manual effect discard
protected boolean affectedObjectsSet = false;
protected List<UUID> objects = new ArrayList<>();
// protected Map<UUID, Integer> metadata = new HashMap<>();
protected List<MageObjectReference> affectedObjectList = new ArrayList<>();
// until your next turn
protected int startingTurn;
protected UUID startingControllerId;
@ -95,7 +95,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
this.used = effect.used;
this.discarded = effect.discarded;
this.affectedObjectsSet = effect.affectedObjectsSet;
this.objects.addAll(effect.objects);
this.affectedObjectList.addAll(effect.affectedObjectList);
this.startingTurn = effect.startingTurn;
this.startingControllerId = effect.startingControllerId;
}
@ -230,7 +230,8 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
}
@Override
public List<UUID> getAffectedObjects() {
return this.objects;
public List<MageObjectReference> getAffectedObjects() {
return affectedObjectList;
}
}

View file

@ -63,7 +63,10 @@ import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.CardIdPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentCard;
import mage.players.Player;
import mage.target.common.TargetCardInHand;
import org.apache.log4j.Logger;
@ -90,15 +93,16 @@ public class ContinuousEffects implements Serializable {
private ContinuousEffectsList<SpliceCardEffect> spliceCardEffects = new ContinuousEffectsList<>();
private final Map<AsThoughEffectType, ContinuousEffectsList<AsThoughEffect>> asThoughEffectsMap = new EnumMap<>(AsThoughEffectType.class);
private final List<ContinuousEffectsList<?>> allEffectsLists = new ArrayList<>();
public final List<ContinuousEffectsList<?>> allEffectsLists = new ArrayList<>();
private final ApplyCountersEffect applyCounters;
private final PlaneswalkerRedirectionEffect planeswalkerRedirectionEffect;
private final AuraReplacementEffect auraReplacementEffect;
private final List<ContinuousEffect> previous = new ArrayList<>();
// effect.id -> sourceId - which effect was added by which sourceId
private final Map<UUID, UUID> sources = new HashMap<>();
// note all effect/abilities that were only added temporary
private final Map<ContinuousEffect, Set<Ability>> temporaryEffects = new HashMap<>();
private final ContinuousEffectSorter sorter = new ContinuousEffectSorter();
public ContinuousEffects() {
@ -125,7 +129,7 @@ public class ContinuousEffects implements Serializable {
costModificationEffects = effect.costModificationEffects.copy();
spliceCardEffects = effect.spliceCardEffects.copy();
sources.putAll(effect.sources);
temporaryEffects.putAll(effect.temporaryEffects);
collectAllEffects();
order = effect.order;
}
@ -339,8 +343,10 @@ public class ContinuousEffects implements Serializable {
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, false)) {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
if (!game.getScopeRelevant() || effect.hasSelfScope() || !event.getTargetId().equals(ability.getSourceId())) {
if (effect.applies(event, ability, game)) {
applicableAbilities.add(ability);
if (checkAbilityStillExists(ability, effect, event, game)) {
if (effect.applies(event, ability, game)) {
applicableAbilities.add(ability);
}
}
}
}
@ -374,6 +380,34 @@ public class ContinuousEffects implements Serializable {
return replaceEffects;
}
private boolean checkAbilityStillExists(Ability ability, ContinuousEffect effect, GameEvent event, Game game) {
if (effect.getDuration().equals(Duration.OneUse)) { // needed for some special replacment effects (e.g. Undying)
return true;
}
MageObject object;
if (event.getType().equals(EventType.ZONE_CHANGE) &&
((ZoneChangeEvent)event).getFromZone().equals(Zone.BATTLEFIELD)&&
event.getTargetId().equals(ability.getSourceId())) {
object = ((ZoneChangeEvent)event).getTarget();
} else {
object = game.getObject(ability.getSourceId());
}
if (object == null) {
return false;
}
boolean exists = true;
if (!object.getAbilities().contains(ability)) {
exists = false;
if (object instanceof PermanentCard) {
PermanentCard permanent = (PermanentCard)object;
if (permanent.canTransform() && event.getType() == GameEvent.EventType.TRANSFORMED) {
exists = permanent.getCard().getAbilities().contains(ability);
}
}
}
return exists;
}
/**
* Filters out cost modification effects that are not active.
*
@ -711,7 +745,7 @@ public class ContinuousEffects implements Serializable {
break;
}
// add used effect to consumed effects
// add the applied effect to the consumed effects
if (rEffect != null) {
if (consumed.containsKey(rEffect.getId())) {
HashSet<UUID> set = consumed.get(rEffect.getId());
@ -728,7 +762,7 @@ public class ContinuousEffects implements Serializable {
consumed.put(rEffect.getId(), set);
}
}
// Must be called here so some
game.applyEffects();
} while (true);
return caught;
@ -854,9 +888,31 @@ public class ContinuousEffects implements Serializable {
}
}
/**
* Adds a continuous ability with a reference to a sourceId.
* It's used for effects that cease to exist again
* So this effects were removed again before each applyEffecs
* @param effect
* @param sourceId
* @param source
*/
public void addEffect(ContinuousEffect effect, UUID sourceId, Ability source) {
if (!(source instanceof MageSingleton)) { // because MageSingletons may never be removed by removing the temporary effecs they are not added to the temporaryEffects to prevent this
Set abilities = temporaryEffects.get(effect);
if (abilities == null) {
abilities = new HashSet<>();
temporaryEffects.put(effect, abilities);
} else {
if (abilities.contains(source)) {
// this ability (for the continuous effect) is already added
return;
}
}
abilities.add(source);
// add the effect itself
}
addEffect(effect, source);
sources.put(effect.getId(), sourceId);
}
public void addEffect(ContinuousEffect effect, Ability source) {
@ -865,7 +921,7 @@ public class ContinuousEffects implements Serializable {
return;
} else if (source == null) {
logger.warn("Adding effect without ability : " +effect.toString());
}
}
switch (effect.getEffectType()) {
case REPLACEMENT:
case REDIRECTION:
@ -909,7 +965,7 @@ public class ContinuousEffects implements Serializable {
ContinuousRuleModifiyingEffect newContinuousRuleModifiyingEffect = (ContinuousRuleModifiyingEffect)effect;
continuousRuleModifyingEffects.addEffect(newContinuousRuleModifiyingEffect, source);
break;
default:
default:
layeredEffects.addEffect(effect, source);
break;
}
@ -942,40 +998,52 @@ public class ContinuousEffects implements Serializable {
for (ContinuousEffectsList effectsList : allEffectsLists) {
effectsList.clear();
}
sources.clear();
temporaryEffects.clear();
}
/**
* Removes effects granted by sourceId
*
* @param sourceId
* @return
*/
public List<Effect> removeGainedEffectsForSource(UUID sourceId) {
List<Effect> effects = new ArrayList<>();
Set<UUID> effectsToRemove = new HashSet<>();
for (Map.Entry<UUID, UUID> source : sources.entrySet()) {
if (sourceId.equals(source.getValue())) {
for (ContinuousEffectsList effectsList : allEffectsLists) {
Iterator it = effectsList.iterator();
while (it.hasNext()) {
ContinuousEffect effect = (ContinuousEffect) it.next();
if (source.getKey().equals(effect.getId())) {
effectsToRemove.add(effect.getId());
effectsList.removeEffectAbilityMap(effect.getId());
it.remove();
}
public void removeAllTemporaryEffects() {
for (Map.Entry<ContinuousEffect, Set<Ability>> entry : temporaryEffects.entrySet()) {
switch (entry.getKey().getEffectType()) {
case REPLACEMENT:
case REDIRECTION:
replacementEffects.removeEffects(entry.getKey().getId(), entry.getValue());
break;
case PREVENTION:
preventionEffects.removeEffects(entry.getKey().getId(), entry.getValue());
break;
case RESTRICTION:
restrictionEffects.removeEffects(entry.getKey().getId(), entry.getValue());
break;
case RESTRICTION_UNTAP_NOT_MORE_THAN:
restrictionUntapNotMoreThanEffects.removeEffects(entry.getKey().getId(), entry.getValue());
break;
case REQUIREMENT:
requirementEffects.removeEffects(entry.getKey().getId(), entry.getValue());
break;
case ASTHOUGH:
AsThoughEffect newAsThoughEffect = (AsThoughEffect)entry.getKey();
if (!asThoughEffectsMap.containsKey(newAsThoughEffect.getAsThoughEffectType())) {
break;
}
}
asThoughEffectsMap.get(newAsThoughEffect.getAsThoughEffectType()).removeEffects(entry.getKey().getId(), entry.getValue());
break;
case COSTMODIFICATION:
costModificationEffects.removeEffects(entry.getKey().getId(), entry.getValue());
break;
case SPLICE:
spliceCardEffects.removeEffects(entry.getKey().getId(), entry.getValue());
break;
case CONTINUOUS_RULE_MODIFICATION:
continuousRuleModifyingEffects.removeEffects(entry.getKey().getId(), entry.getValue());
break;
default:
layeredEffects.removeEffects(entry.getKey().getId(), entry.getValue());
break;
}
}
for (UUID effectId: effectsToRemove) {
sources.remove(effectId);
}
return effects;
}
}
temporaryEffects.clear();
}
public Map<String, String> getReplacementEffectsTexts(HashMap<ReplacementEffect, HashSet<Ability>> rEffects, Game game) {
Map<String, String> texts = new LinkedHashMap<>();

View file

@ -32,8 +32,10 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.MageSingleton;
import mage.constants.Duration;
import mage.game.Game;
import org.apache.log4j.Logger;
@ -111,23 +113,25 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
Ability ability = it.next();
if (ability == null) {
it.remove();
} else if (ability instanceof MageSingleton) {
return false;
} else if (effect.isDiscarded()) {
it.remove();
} else if (game.getObject(ability.getSourceId()) == null) {
logger.debug("Ability without source object removed: " + ability.getSourceId() + " " + ability.getRule());
it.remove(); // if the related source object does no longer exist the effect has to be removed
} else {
switch(effect.getDuration()) {
case WhileOnBattlefield:
if (game.getObject(ability.getSourceId()) == null) {//TODO: does this really works?? object is returned across the game
it.remove();
}
break;
case OneUse:
if (effect.isUsed()) {
logger.debug("Ability one use removed: " + ability.getSourceId() + " " + ability.getRule());
it.remove();
}
break;
case Custom:
case UntilYourNextTurn:
if (effect.isInactive(ability , game)) {
logger.debug("Ability custom removed: " + ability.getSourceId() + " " + ability.getRule());
it.remove();
}
}
@ -165,34 +169,21 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
return effectAbilityMap.get(effectId);
}
/**
* Removes an effect and / or a connected ability.
* If no ability for this effect is left in the effectAbilityMap, the effect will be removed.
* Otherwise the effect won't be removed.
*
* @param effect - effect to remove if all abilities are removed
* @param ability - ability to remove
*/
public void removeEffect(T effect, Ability ability) {
for (Iterator<T> i = this.iterator(); i.hasNext();) {
T entry = i.next();
if (entry.equals(effect)) {
HashSet<Ability> abilities = effectAbilityMap.get(effect.getId());
if (!abilities.isEmpty()) {
abilities.remove(ability);
}
if (abilities.isEmpty()) {
i.remove();
public void removeEffects(UUID effectIdToRemove, Set<Ability> abilitiesToRemove) {
HashSet<Ability> abilities = effectAbilityMap.get(effectIdToRemove);
abilities.removeAll(abilitiesToRemove);
if (abilities.isEmpty()) {
for (Iterator<T> iterator = this.iterator(); iterator.hasNext();) {
ContinuousEffect effect = iterator.next();
if (effect.getId().equals(effectIdToRemove)) {
iterator.remove();
break;
}
}
effectAbilityMap.remove(effectIdToRemove);
}
}
public void removeEffectAbilityMap(UUID effectId) {
effectAbilityMap.remove(effectId);
}
@Override
public void clear() {
super.clear();

View file

@ -69,7 +69,7 @@ public class EntersBattlefieldEffect extends ReplacementEffectImpl {
}
public EntersBattlefieldEffect(Effect baseEffect, Condition condition, String text, boolean selfScope, boolean optional) {
super(Duration.OneUse, baseEffect.getOutcome(), selfScope);
super(Duration.WhileOnBattlefield, baseEffect.getOutcome(), selfScope);
this.baseEffects.add(baseEffect);
this.text = text;
this.condition = condition;
@ -124,12 +124,6 @@ public class EntersBattlefieldEffect extends ReplacementEffectImpl {
game.addEffect((ContinuousEffect) effect, source);
}
else {
// noxx: commented it out because of resulting in a bug
// with CopyEffect (PhantasmalImageTest.java)
/*if (spell != null)
effect.apply(game, spell.getSpellAbility());
else
effect.apply(game, source);*/
if (spell != null) {
effect.setValue(SOURCE_CAST_SPELL_ABILITY, spell.getSpellAbility());
}

View file

@ -28,8 +28,10 @@
package mage.abilities.effects.common;
import java.util.UUID;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.cards.Card;
@ -55,7 +57,6 @@ public class CopyEffect extends ContinuousEffectImpl {
*/
private MageObject target;
private UUID sourceId;
private int zoneChangeCounter;
private ApplyToPermanent applier;
public CopyEffect(MageObject target, UUID sourceId) {
@ -72,23 +73,27 @@ public class CopyEffect extends ContinuousEffectImpl {
super(effect);
this.target = effect.target.copy();
this.sourceId = effect.sourceId;
this.zoneChangeCounter = effect.zoneChangeCounter;
this.applier = effect.applier;
}
@Override
public void init(Ability source, Game game) {
super.init(source, game);
Permanent permanent = game.getPermanent(sourceId);
if (permanent != null) {
this.zoneChangeCounter = permanent.getZoneChangeCounter();
if (affectedObjectsSet) {
affectedObjectList.add(new MageObjectReference(source.getSourceId(), game));
}
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(this.sourceId);
if (affectedObjectsSet) {
permanent = affectedObjectList.get(0).getPermanent(game);
} else {
permanent = game.getPermanent(this.sourceId);
}
if (permanent == null) {
discard();
return false;
}
permanent.setName(target.getName());
@ -107,9 +112,10 @@ public class CopyEffect extends ContinuousEffectImpl {
for (String type: target.getSupertype()) {
permanent.getSupertype().add(type);
}
permanent.removeAllAbilities(source.getSourceId(), game);
for (Ability ability: target.getAbilities()) {
permanent.addAbility(ability, game);
permanent.addAbility(ability, getSourceId(), game, false); // no new Id so consumed replacement effects are known while new continuousEffects.apply happen.
}
permanent.getPower().setValue(target.getPower().getValue());
permanent.getToughness().setValue(target.getToughness().getValue());
@ -131,17 +137,9 @@ public class CopyEffect extends ContinuousEffectImpl {
}
permanent.setCopy(true);
return true;
}
@Override
public boolean isInactive(Ability source, Game game) {
// The copy effect is added, if the copy takes place. If source left battlefield, the copy effect has cease to exist
Permanent permanent = game.getPermanent(this.sourceId);
return permanent == null || permanent.getZoneChangeCounter() != this.zoneChangeCounter;
}
@Override
public CopyEffect copy() {
return new CopyEffect(this);
@ -166,6 +164,5 @@ public class CopyEffect extends ContinuousEffectImpl {
public void setApplier(ApplyToPermanent applier) {
this.applier = applier;
}
}

View file

@ -42,7 +42,8 @@ import mage.game.permanent.Permanent;
* @author nantuko
*/
public class AddCardSubTypeTargetEffect extends ContinuousEffectImpl {
private String addedSubType;
private final String addedSubType;
public AddCardSubTypeTargetEffect(String addedSubType, Duration duration) {
super(duration, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit);
@ -61,6 +62,10 @@ public class AddCardSubTypeTargetEffect extends ContinuousEffectImpl {
if (!target.hasSubtype(addedSubType)) {
target.getSubtype().add(addedSubType);
}
} else {
if (Duration.Custom.equals(duration)) {
discard();
}
}
return false;
}

View file

@ -339,10 +339,6 @@ class SuspendPlayCardEffect extends OneShotEffect {
abilitiesToRemove.add(ability);
}
}
// remove the triggered abilities from the game
game.getState().resetTriggersForSourceId(card.getId());
// remove the continious effects from the game
game.getState().getContinuousEffects().removeGainedEffectsForSource(card.getId());
// remove the abilities from the card
card.getAbilities().removeAll(abilitiesToRemove);
}

View file

@ -388,7 +388,6 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
break;
case BATTLEFIELD:
PermanentCard permanent = new PermanentCard(this, event.getPlayerId()); // controller can be replaced (e.g. Gather Specimens)
game.resetForSourceId(permanent.getId());
game.addPermanent(permanent);
game.setZone(objectId, Zone.BATTLEFIELD);
game.setScopeRelevant(true);
@ -555,7 +554,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
updateZoneChangeCounter();
PermanentCard permanent = new PermanentCard(this, event.getPlayerId());
// reset is done to end continuous effects from previous instances of that permanent (e.g undying)
game.resetForSourceId(permanent.getId());
// game.resetForSourceId(permanent.getId());
// make sure the controller of all continuous effects of this card are switched to the current controller
game.getContinuousEffects().setController(objectId, event.getPlayerId());
game.addPermanent(permanent);

View file

@ -148,7 +148,6 @@ public interface Game extends MageItem, Serializable {
Player getLosingPlayer();
void setStateCheckRequired();
boolean getStateCheckRequired();
void resetForSourceId(UUID sourceId);
//client event methods
void addTableEventListener(Listener<TableEvent> listener);

View file

@ -57,7 +57,6 @@ import mage.abilities.effects.ContinuousEffects;
import mage.abilities.effects.Effect;
import mage.abilities.effects.PreventionEffectData;
import mage.abilities.effects.common.CopyEffect;
import mage.abilities.effects.common.continious.SourceEffect;
import mage.abilities.keyword.LeylineAbility;
import mage.abilities.keyword.MorphAbility;
import mage.abilities.keyword.TransformAbility;
@ -2263,23 +2262,6 @@ public abstract class GameImpl implements Game, Serializable {
shortLivingLKI.clear();
}
@Override
public void resetForSourceId(UUID sourceId) {
// make sure that all effects don't touch this card once it returns back to battlefield
// e.g. this prevents that effects affect creature with undying return from graveyard
for (ContinuousEffect effect : getContinuousEffects().getLayeredEffects(this)) {
if (effect.getAffectedObjects().contains(sourceId)) {
effect.getAffectedObjects().remove(sourceId);
if (effect instanceof SourceEffect) {
effect.discard();
}
}
}
getContinuousEffects().removeGainedEffectsForSource(sourceId);
// remove gained triggered abilities
getState().resetTriggersForSourceId(sourceId);
}
@Override
public void cheat(UUID ownerId, Map<Zone, String> commands) {
if (commands != null) {

View file

@ -58,6 +58,7 @@ import mage.watchers.Watchers;
import java.io.Serializable;
import java.util.*;
import org.apache.log4j.Logger;
/**
*
@ -397,6 +398,7 @@ public class GameState implements Serializable, Copyable<GameState> {
}
public void applyEffects(Game game) {
Logger.getLogger(GameState.class).debug("Apply Effects turn: " + game.getTurnNum() + " - Step: " + (game.getStep() == null ? "null":game.getStep().getType().toString()));
for (Player player: players.values()) {
player.reset();
}
@ -508,6 +510,7 @@ public class GameState implements Serializable, Copyable<GameState> {
eventsToHandle.addAll(simultaneousEvents);
simultaneousEvents.clear();
for (GameEvent event:eventsToHandle) {
Logger.getLogger(GameState.class).debug("Handle Simultanous events - type: " + event.getType().toString() + " sourceId " + event.getSourceId() + " targetId " + event.getTargetId());
this.handleEvent(event, game);
}
}
@ -551,7 +554,12 @@ public class GameState implements Serializable, Copyable<GameState> {
}
}
@Deprecated
/**
* Used for adding abilities that exist permanent on cards/permanents and are not
* only gained for a certain time (e.g. until end of turn).
* @param ability
* @param attachedTo
*/
public void addAbility(Ability ability, MageObject attachedTo) {
if (ability instanceof StaticAbility) {
for (Mode mode: ability.getModes().values()) {
@ -567,6 +575,12 @@ public class GameState implements Serializable, Copyable<GameState> {
}
}
/**
* Abilities that are applied to other objects or applie for a certain time span
* @param ability
* @param sourceId
* @param attachedTo
*/
public void addAbility(Ability ability, UUID sourceId, MageObject attachedTo) {
if (ability instanceof StaticAbility) {
for (Mode mode: ability.getModes().values()) {
@ -678,18 +692,23 @@ public class GameState implements Serializable, Copyable<GameState> {
}
/**
* Removes Triggered abilities that were gained from sourceId
* Removes Triggered abilities that belong to sourceId
* This is used if a token leaves the battlefield
*
* @param sourceId
*/
public void resetTriggersForSourceId(UUID sourceId) {
List<String> keysToRemove = triggers.removeGainedAbilitiesForSource(sourceId);
for (String key : keysToRemove) {
triggers.remove(key);
}
public void removeTriggersOfSourceId(UUID sourceId) {
triggers.removeAbilitiesOfSource(sourceId);
}
/**
* Called before applyEffects
*/
private void reset() {
// All gained abilities have to be removed to prevent adding it multiple times
triggers.removeAllGainedAbilities();
getContinuousEffects().removeAllTemporaryEffects();
this.setLegendaryRuleActive(true);
this.resetOtherAbilities();
}
@ -740,4 +759,11 @@ public class GameState implements Serializable, Copyable<GameState> {
this.legendaryRuleActive = legendaryRuleActive;
}
/**
* Used for diagbostic purposes
* @return
*/
public TriggeredAbilities getTriggers() {
return triggers;
}
}

View file

@ -113,7 +113,8 @@ public interface Permanent extends Card, Controllable {
@Deprecated
void addAbility(Ability ability, Game game);
void addAbility(Ability ability, UUID sourceId, Game game);
void addAbility(Ability ability, UUID sourceId, Game game, boolean createNewId);
void removeAllAbilities(UUID sourceId, Game game);
void addLoyaltyUsed();

View file

@ -204,12 +204,18 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
abilities.add(copyAbility);
}
}
@Override
public void addAbility(Ability ability, UUID sourceId, Game game) {
addAbility(ability, sourceId, game, true);
}
@Override
public void addAbility(Ability ability, UUID sourceId, Game game, boolean createNewId) {
if (!abilities.containsKey(ability.getId())) {
Ability copyAbility = ability.copy();
copyAbility.newId(); // needed so that source can get an ability multiple times (e.g. Raging Ravine)
if (createNewId) {
copyAbility.newId(); // needed so that source can get an ability multiple times (e.g. Raging Ravine)
}
copyAbility.setControllerId(controllerId);
copyAbility.setSourceId(objectId);
game.getState().addAbility(copyAbility, sourceId, this);
@ -220,10 +226,6 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
@Override
public void removeAllAbilities(UUID sourceId, Game game) {
getAbilities().clear();
// removes abilities that were gained from abilities of this permanent
game.getContinuousEffects().removeGainedEffectsForSource(this.getId());
// remove gained triggered abilities
game.getState().resetTriggersForSourceId(this.getId());
}
@Override

View file

@ -49,7 +49,7 @@ public class PermanentToken extends PermanentImpl {
super(controllerId, controllerId, token.getName());
this.expansionSetCode = expansionSetCode;
this.token = token;
this.copyFromToken(this.token, game); // needed to have e.g. subtypes for entersTheBattlefield replacement effects
this.copyFromToken(this.token, game, false); // needed to have e.g. subtypes for entersTheBattlefield replacement effects
}
public PermanentToken(final PermanentToken permanent) {
@ -61,16 +61,21 @@ public class PermanentToken extends PermanentImpl {
@Override
public void reset(Game game) {
Token tokenCopy = token.copy();
copyFromToken(tokenCopy, game);
copyFromToken(tokenCopy, game, true);
super.reset(game);
}
private void copyFromToken(Token token, Game game) {
private void copyFromToken(Token token, Game game, boolean reset) {
this.name = token.getName();
this.abilities.clear();
for (Ability ability : token.getAbilities()) {
this.addAbility(ability, game);
if (reset) {
this.abilities.addAll(token.getAbilities());
} else {
for (Ability ability : token.getAbilities()) {
this.addAbility(ability, game);
}
}
this.abilities.setControllerId(this.controllerId);
this.manaCost.clear();
for (ManaCost cost: token.getManaCost()) {
this.getManaCost().add(cost.copy());
@ -90,8 +95,7 @@ public class PermanentToken extends PermanentImpl {
if (game.getPlayer(controllerId).removeFromBattlefield(this, game)) {
game.setZone(objectId, zone); // needed for triggered dies abilities
game.fireEvent(new ZoneChangeEvent(this, this.getControllerId(), Zone.BATTLEFIELD, zone));
game.getState().resetTriggersForSourceId(this.getId());// if token is gone triggered abilities no longer needed
game.getState().getContinuousEffects().removeGainedEffectsForSource(this.getId());
game.getState().removeTriggersOfSourceId(this.getId());// if token is gone endless triggered abilities have to be removed
return true;
}
}
@ -125,7 +129,7 @@ public class PermanentToken extends PermanentImpl {
Ability copyAbility = ability.copy();
copyAbility.setControllerId(controllerId);
copyAbility.setSourceId(objectId);
game.getState().addAbility(copyAbility, this.getId(), this);
game.getState().addAbility(copyAbility, this);
abilities.add(copyAbility);
}
}

View file

@ -211,7 +211,7 @@ public class Spell implements StackObject, Card {
}
}
if (game.getState().getZone(card.getId()) == Zone.STACK) {
card.moveToZone(Zone.GRAVEYARD, ability.getId(), game, false);
card.moveToZone(Zone.GRAVEYARD, ability.getSourceId(), game, false);
}
}
return result;
@ -229,7 +229,7 @@ public class Spell implements StackObject, Card {
// Otherwise effects like evolve trigger from creature comes into play event
card.getCardType().remove(CardType.CREATURE);
}
if (card.putOntoBattlefield(game, fromZone, ability.getId(), controllerId)) {
if (card.putOntoBattlefield(game, fromZone, ability.getSourceId(), controllerId)) {
if (bestow) {
// card will be copied during putOntoBattlefield, so the card of CardPermanent has to be changed
// TODO: Find a better way to prevent bestow creatures from being effected by creature affecting abilities
@ -250,7 +250,7 @@ public class Spell implements StackObject, Card {
// Aura has no legal target and its a bestow enchantment -> Add it to battlefield as creature
if (this.getSpellAbility() instanceof BestowAbility) {
updateOptionalCosts(0);
result = card.putOntoBattlefield(game, fromZone, ability.getId(), controllerId);
result = card.putOntoBattlefield(game, fromZone, ability.getSourceId(), controllerId);
return result;
} else {
//20091005 - 608.2b
@ -263,7 +263,7 @@ public class Spell implements StackObject, Card {
if (isFaceDown()) {
card.setFaceDown(true);
}
result = card.putOntoBattlefield(game, fromZone, ability.getId(), controllerId);
result = card.putOntoBattlefield(game, fromZone, ability.getSourceId(), controllerId);
return result;
}
}