* Gain abilities - fixed wrong order effects with changeling ability (all creature type effect, #6147);

This commit is contained in:
Oleg Agafonov 2020-01-07 06:38:34 +04:00
parent 1b4145e5b8
commit b6d76a7c02
16 changed files with 220 additions and 32 deletions

View file

@ -17,6 +17,7 @@ import mage.cards.FrameStyle;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.util.GameLog;
import mage.util.SubTypeList;
@ -251,8 +252,14 @@ public abstract class MageObjectImpl implements MageObject {
if (value.getSubTypeSet() != SubTypeSet.CreatureType) {
return false;
}
// as it is creature subtype, then check the existence of Changeling
return abilities.contains(ChangelingAbility.getInstance()) || isAllCreatureTypes();
// as it is a creature subtype, then check the existence of Changeling
Abilities<Ability> checkList;
if (this instanceof Permanent) {
checkList = ((Permanent) this).getAbilities(game);
} else {
checkList = abilities;
}
return checkList.contains(ChangelingAbility.getInstance()) || isAllCreatureTypes();
}
}

View file

@ -162,6 +162,14 @@ public class ConditionalContinuousEffect extends ContinuousEffectImpl {
return super.getDependencyTypes();
}
@Override
public EnumSet<DependencyType> getDependedToTypes() {
if (effect != null) {
return effect.getDependedToTypes();
}
return super.getDependedToTypes();
}
@Override
public Set<UUID> isDependentTo(List<ContinuousEffect> allEffectsInLayer) {
if (effect != null) {

View file

@ -59,6 +59,8 @@ public interface ContinuousEffect extends Effect {
void setDependedToType(DependencyType dependencyType);
EnumSet<DependencyType> getDependedToTypes();
void addDependedToType(DependencyType dependencyType);
void setStartingControllerAndTurnNum(Game game, UUID startingController, UUID activePlayerId);

View file

@ -2,12 +2,17 @@ package mage.abilities.effects;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.CompoundAbility;
import mage.abilities.MageSingleton;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.DomainValue;
import mage.abilities.dynamicvalue.common.SignInversionDynamicValue;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.keyword.ChangelingAbility;
import mage.constants.*;
import mage.filter.Filter;
import mage.filter.predicate.Predicate;
import mage.filter.predicate.Predicates;
import mage.game.Game;
import mage.players.Player;
import mage.target.targetpointer.TargetPointer;
@ -319,6 +324,11 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
return dependencyTypes;
}
@Override
public EnumSet<DependencyType> getDependedToTypes() {
return dependendToTypes;
}
@Override
public void addDependencyType(DependencyType dependencyType) {
dependencyTypes.add(dependencyType);
@ -341,4 +351,48 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
return this;
}
/**
* Auto-generates dependencies on different effects (what's apply first and what's apply second)
*/
public void generateGainAbilityDependencies(Ability abilityToGain, Filter filterToSearch) {
this.addDependencyType(DependencyType.AddingAbility);
this.generateGainAbilityDependenciesFromAbility(abilityToGain);
this.generateGainAbilityDependenciesFromFilter(filterToSearch);
}
public void generateGainAbilityDependencies(CompoundAbility abilityToGain, Filter filterToSearch) {
this.addDependencyType(DependencyType.AddingAbility);
this.generateGainAbilityDependenciesFromAbility(abilityToGain);
this.generateGainAbilityDependenciesFromFilter(filterToSearch);
}
private void generateGainAbilityDependenciesFromAbility(CompoundAbility compoundAbility) {
if (compoundAbility == null) return;
for (Ability ability : compoundAbility) {
generateGainAbilityDependenciesFromAbility(ability);
}
}
private void generateGainAbilityDependenciesFromAbility(Ability ability) {
if (ability == null) return;
// 1. "Is all type" ability (changeling)
// make dependency
if (ability instanceof ChangelingAbility) {
this.addDependencyType(DependencyType.AddingCreatureType);
}
}
private void generateGainAbilityDependenciesFromFilter(Filter filter) {
if (filter == null) return;
// 1. "Is all type" ability (changeling)
// wait dependency
// extraPredicates from some filters is player related, you don't need it here
List<Predicate> list = new ArrayList<>();
Predicates.collectAllComponents(filter.getPredicates(), list);
if (list.stream().anyMatch(p -> p instanceof SubType.SubTypePredicate)) {
this.addDependedToType(DependencyType.AddingCreatureType);
}
}
}

View file

@ -55,7 +55,8 @@ public class GainAbilityAllEffect extends ContinuousEffectImpl {
this.ability.newId();
this.filter = filter;
this.excludeSource = excludeSource;
this.addDependencyType(DependencyType.AddingAbility);
this.generateGainAbilityDependencies(ability, filter);
}
public GainAbilityAllEffect(final GainAbilityAllEffect effect) {

View file

@ -3,12 +3,7 @@ package mage.abilities.effects.common.continuous;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.AttachmentType;
import mage.constants.DependencyType;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.constants.*;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
@ -50,7 +45,8 @@ public class GainAbilityAttachedEffect extends ContinuousEffectImpl {
} else {
this.staticText = rule;
}
this.addDependencyType(DependencyType.AddingAbility);
this.generateGainAbilityDependencies(ability, null);
}
public GainAbilityAttachedEffect(final GainAbilityAttachedEffect effect) {

View file

@ -53,8 +53,9 @@ public class GainAbilityControlledEffect extends ContinuousEffectImpl {
this.ability = ability;
this.filter = filter;
this.excludeSource = excludeSource;
this.addDependencyType(DependencyType.AddingAbility);
setText();
this.generateGainAbilityDependencies(ability, filter);
}
public GainAbilityControlledEffect(final GainAbilityControlledEffect effect) {

View file

@ -5,7 +5,6 @@ import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.cards.Card;
import mage.constants.DependencyType;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
@ -48,7 +47,8 @@ public class GainAbilitySourceEffect extends ContinuousEffectImpl implements Sou
if (noStaticText) {
staticText = null;
}
this.addDependencyType(DependencyType.AddingAbility);
this.generateGainAbilityDependencies(ability, null);
}
public GainAbilitySourceEffect(final GainAbilitySourceEffect effect) {

View file

@ -44,7 +44,8 @@ public class GainAbilityTargetEffect extends ContinuousEffectImpl {
this.ability = ability;
staticText = rule;
this.onCard = onCard;
this.addDependencyType(DependencyType.AddingAbility);
this.generateGainAbilityDependencies(ability, null);
}
public GainAbilityTargetEffect(final GainAbilityTargetEffect effect) {

View file

@ -1,21 +1,23 @@
package mage.constants;
/**
* Dependency types are a part of a workaround solution to handle dependencies
* of continuous effects.
*
* <p>
* All continuous effects can:
* addDependencyType -- make dependency (effect makes some changes)
* addDependedToType -- wait another dependency (effect must wait until all ather effects finished)
* <p>
* http://magiccards.info/rule/613-interaction-of-continuous-effects.html
*
* https://github.com/magefree/mage/issues/1259
*
*
* @author LevelX2
*/
public enum DependencyType {
AuraAddingRemoving,
ArtifactAddingRemoving,
AddingAbility,
AddingCreatureType,
BecomeForest,
BecomeIsland,
BecomeMountain,

View file

@ -75,7 +75,6 @@ public class FilterCard extends FilterObject<Card> {
public Set<Card> filter(Set<Card> cards, Game game) {
return cards.stream().filter(card -> match(card, game)).collect(Collectors.toSet());
}
public boolean hasPredicates() {

View file

@ -71,5 +71,4 @@ public class FilterPermanent extends FilterObject<Permanent> implements FilterIn
public FilterPermanent copy() {
return new FilterPermanent(this);
}
}

View file

@ -56,5 +56,4 @@ public class FilterPlayer extends FilterImpl<Player> {
public FilterPlayer copy() {
return new FilterPlayer(this);
}
}

View file

@ -1,8 +1,5 @@
package mage.filter;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.filter.predicate.ObjectPlayer;
import mage.filter.predicate.ObjectPlayerPredicate;
import mage.filter.predicate.ObjectSourcePlayer;
@ -11,6 +8,10 @@ import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.stack.StackObject;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
*
* @author North
@ -51,5 +52,4 @@ public class FilterStackObject extends FilterObject<StackObject> {
public FilterStackObject copy() {
return new FilterStackObject(this);
}
}

View file

@ -21,9 +21,10 @@ public final class Predicates {
/**
* Returns a predicate that evaluates to {@code true} if the given predicate evaluates to {@code false}.
*
* @param <T>
* @param predicate
* @return
* @return
*/
public static <T> Predicate<T> not(Predicate<T> predicate) {
return new NotPredicate<T>(predicate);
@ -34,9 +35,10 @@ public final class Predicates {
* components are evaluated in order, and evaluation will be "short-circuited" as soon as a false predicate is
* found. It defensively copies the iterable passed in, so future changes to it won't alter the behavior of this
* predicate. If {@code components} is empty, the returned predicate will always evaluate to {@code true}.
*
* @param <T>
* @param components
* @return
* @return
*/
public static <T> Predicate<T> and(Iterable<? extends Predicate<? super T>> components) {
return new AndPredicate<T>(defensiveCopy(components));
@ -47,9 +49,10 @@ public final class Predicates {
* components are evaluated in order, and evaluation will be "short-circuited" as soon as a false predicate is
* found. It defensively copies the array passed in, so future changes to it won't alter the behavior of this
* predicate. If {@code components} is empty, the returned predicate will always evaluate to {@code true}.
*
* @param <T>
* @param components
* @return
* @return
*/
public static <T> Predicate<T> and(Predicate<? super T>... components) {
return new AndPredicate<T>(defensiveCopy(components));
@ -59,10 +62,11 @@ public final class Predicates {
* Returns a predicate that evaluates to {@code true} if both of its components evaluate to {@code true}. The
* components are evaluated in order, and evaluation will be "short-circuited" as soon as a false predicate is
* found.
*
* @param <T>
* @param first
* @param second
* @return
* @return
*/
public static <T> Predicate<T> and(Predicate<? super T> first, Predicate<? super T> second) {
return new AndPredicate<T>(Predicates.<T>asList(checkNotNull(first), checkNotNull(second)));
@ -73,9 +77,10 @@ public final class Predicates {
* components are evaluated in order, and evaluation will be "short-circuited" as soon as a true predicate is found.
* It defensively copies the iterable passed in, so future changes to it won't alter the behavior of this predicate.
* If {@code components} is empty, the returned predicate will always evaluate to {@code true}.
*
* @param <T>
* @param components
* @return
* @return
*/
public static <T> Predicate<T> or(Iterable<? extends Predicate<? super T>> components) {
return new OrPredicate<T>(defensiveCopy(components));
@ -86,9 +91,10 @@ public final class Predicates {
* components are evaluated in order, and evaluation will be "short-circuited" as soon as a true predicate is found.
* It defensively copies the array passed in, so future changes to it won't alter the behavior of this predicate. If
* {@code components} is empty, the returned predicate will always evaluate to {@code true}.
*
* @param <T>
* @param components
* @return
* @return
*/
public static <T> Predicate<T> or(Predicate<? super T>... components) {
return new OrPredicate<T>(defensiveCopy(components));
@ -97,10 +103,11 @@ public final class Predicates {
/**
* Returns a predicate that evaluates to {@code true} if either of its components evaluates to {@code true}. The
* components are evaluated in order, and evaluation will be "short-circuited" as soon as a true predicate is found.
*
* @param <T>
* @param first
* @param second
* @return
* @return
*/
public static <T> Predicate<T> or(Predicate<? super T> first, Predicate<? super T> second) {
return new OrPredicate<T>(Predicates.<T>asList(first, second));
@ -126,6 +133,7 @@ public final class Predicates {
public String toString() {
return "Not(" + predicate.toString() + ')';
}
private static final long serialVersionUID = 0;
}
@ -150,6 +158,7 @@ public final class Predicates {
public String toString() {
return "And(" + commaJoin(components) + ')';
}
private static final long serialVersionUID = 0;
}
@ -173,6 +182,7 @@ public final class Predicates {
public String toString() {
return "Or(" + commaJoin(components) + ')';
}
private static final long serialVersionUID = 0;
}
@ -215,4 +225,25 @@ public final class Predicates {
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
/**
* Collect real predicates for searching some data (see dependency effect code)
*/
public static void collectAllComponents(Predicate predicate, List<Predicate> res) {
if (predicate instanceof NotPredicate) {
res.add(((NotPredicate) predicate).predicate);
} else if (predicate instanceof AndPredicate) {
collectAllComponents(((AndPredicate) predicate).components, res);
} else if (predicate instanceof OrPredicate) {
collectAllComponents(((OrPredicate) predicate).components, res);
} else {
res.add(predicate);
}
}
public static void collectAllComponents(List<Predicate> predicates, List<Predicate> res) {
predicates.forEach(p -> {
collectAllComponents(p, res);
});
}
}