* 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

@ -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<>();