mirror of
https://github.com/magefree/mage.git
synced 2026-01-09 20:32:06 -08:00
* 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:
parent
f9b80e5c81
commit
4f1368f3de
25 changed files with 316 additions and 169 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<>();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue