mage/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java
Oleg Agafonov c2e7b02e13 Reworked and improved special mana payment abilities (convoke, delve, assist, improvise):
* now it can be used to calc and find available mana and playable abilities;
* now tests and AI can use that abilities;
* now it follows mtg's rules and restrictions for mana activation order (rule 601.2f, see #768);
2020-06-19 13:09:45 +04:00

425 lines
15 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package mage.abilities.effects;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.CompoundAbility;
import mage.abilities.MageSingleton;
import mage.abilities.costs.mana.ActivationManaAbilityStep;
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.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
import mage.target.targetpointer.TargetPointer;
import java.util.*;
/**
* @author BetaSteward_at_googlemail.com, JayDi85
*/
public abstract class ContinuousEffectImpl extends EffectImpl implements ContinuousEffect {
protected Duration duration;
protected Layer layer;
protected SubLayer sublayer;
protected long order;
protected boolean used = false;
protected boolean discarded = false; // for manual effect discard
protected boolean affectedObjectsSet = false;
protected List<MageObjectReference> affectedObjectList = new ArrayList<>();
protected boolean temporary = false;
protected EnumSet<DependencyType> dependencyTypes; // this effect has the dependencyTypes defined here
protected EnumSet<DependencyType> dependendToTypes; // this effect is dependent to this types
/*
A Characteristic Defining Ability (CDA) is an ability that defines a characteristic of a card or token.
There are 3 specific rules that distinguish a CDA from other abilities.
1) A CDA can only define a characteristic of either the card or token it comes from.
2) A CDA can not be triggered, activated, or conditional.
3) A CDA must define a characteristic. Usually color, power and/or toughness, or sub-type.
*/
protected boolean characterDefining = false;
// until your next turn or until end of your next turn
private UUID startingControllerId; // player to check for turn duration (can't different with real controller ability)
private boolean startingTurnWasActive; // effect started during related players turn and related players turn was already active
private int effectStartingOnTurn = 0; // turn the effect started
public ContinuousEffectImpl(Duration duration, Outcome outcome) {
super(outcome);
this.duration = duration;
this.order = 0;
this.effectType = EffectType.CONTINUOUS;
this.dependencyTypes = EnumSet.noneOf(DependencyType.class);
this.dependendToTypes = EnumSet.noneOf(DependencyType.class);
}
public ContinuousEffectImpl(Duration duration, Layer layer, SubLayer sublayer, Outcome outcome) {
this(duration, outcome);
this.layer = layer;
this.sublayer = sublayer;
}
public ContinuousEffectImpl(final ContinuousEffectImpl effect) {
super(effect);
this.duration = effect.duration;
this.layer = effect.layer;
this.sublayer = effect.sublayer;
this.order = effect.order;
this.used = effect.used;
this.discarded = effect.discarded;
this.affectedObjectsSet = effect.affectedObjectsSet;
this.affectedObjectList.addAll(effect.affectedObjectList);
this.temporary = effect.temporary;
this.startingControllerId = effect.startingControllerId;
this.startingTurnWasActive = effect.startingTurnWasActive;
this.effectStartingOnTurn = effect.effectStartingOnTurn;
this.dependencyTypes = effect.dependencyTypes;
this.dependendToTypes = effect.dependendToTypes;
this.characterDefining = effect.characterDefining;
}
@Override
public void setDuration(Duration duration) {
this.duration = duration;
}
@Override
public Duration getDuration() {
return duration;
}
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
if (this.layer == layer && this.sublayer == sublayer) {
return apply(game, source);
}
return false;
}
@Override
public long getOrder() {
return order;
}
@Override
public void setOrder(long order) {
this.order = order;
}
@Override
public void newId() {
if (!(this instanceof MageSingleton)) {
this.id = UUID.randomUUID();
}
}
@Override
public boolean hasLayer(Layer layer) {
return this.layer == layer;
}
@Override
public boolean isUsed() {
return used;
}
@Override
public boolean isDiscarded() {
return discarded;
}
/**
* Sets the discarded state of the effect. So it will be removed on next
* check.
*/
@Override
public void discard() {
this.used = true; // to prevent further usage before effect is removed
this.discarded = true;
}
@Override
public void init(Ability source, Game game) {
init(source, game, game.getActivePlayerId());
}
@Override
public void init(Ability source, Game game, UUID activePlayerId) {
targetPointer.init(game, source);
//20100716 - 611.2c
if (AbilityType.ACTIVATED == source.getAbilityType()
|| AbilityType.SPELL == source.getAbilityType()
|| AbilityType.TRIGGERED == source.getAbilityType()) {
if (layer != null) {
switch (layer) {
case CopyEffects_1:
case ControlChangingEffects_2:
case TextChangingEffects_3:
case TypeChangingEffects_4:
case ColorChangingEffects_5:
case AbilityAddingRemovingEffects_6:
case PTChangingEffects_7:
this.affectedObjectsSet = true;
}
} else if (hasLayer(Layer.CopyEffects_1)
|| hasLayer(Layer.ControlChangingEffects_2)
|| hasLayer(Layer.TextChangingEffects_3)
|| hasLayer(Layer.TypeChangingEffects_4)
|| hasLayer(Layer.ColorChangingEffects_5)
|| hasLayer(Layer.AbilityAddingRemovingEffects_6)
|| hasLayer(Layer.PTChangingEffects_7)) {
this.affectedObjectsSet = true;
}
}
setStartingControllerAndTurnNum(game, source.getControllerId(), activePlayerId);
}
@Override
public UUID getStartingController() {
return startingControllerId;
}
@Override
public void setStartingControllerAndTurnNum(Game game, UUID startingController, UUID activePlayerId) {
this.startingControllerId = startingController;
this.startingTurnWasActive = activePlayerId != null
&& activePlayerId.equals(startingController); // you can't use "game" for active player cause it's called from tests/cheat too
this.effectStartingOnTurn = game.getTurnNum();
}
@Override
public boolean isYourNextTurn(Game game) {
return effectStartingOnTurn < game.getTurnNum()
&& game.isActivePlayer(startingControllerId);
}
@Override
public boolean isInactive(Ability source, Game game) {
// YOUR turn checks
// until end of turn - must be checked on cleanup step, see rules 514.2
// other must checked here (active and leave players), see rules 800.4
switch (duration) {
case UntilYourNextTurn:
case UntilEndOfYourNextTurn:
break;
default:
return false;
}
// cheat engine put cards without play and calls direct applyEffects with clean -- need to ignore it
if (game.getActivePlayerId() == null) {
return false;
}
boolean canDelete = false;
Player player = game.getPlayer(startingControllerId);
// discard on start of turn for leaved player
// 800.4i When a player leaves the game, any continuous effects with durations that last until that player's next turn
// or until a specific point in that turn will last until that turn would have begun.
// They neither expire immediately nor last indefinitely.
switch (duration) {
case UntilYourNextTurn:
case UntilEndOfYourNextTurn:
canDelete = player == null
|| (!player.isInGame()
&& player.hasReachedNextTurnAfterLeaving());
}
// discard on another conditions (start of your turn)
switch (duration) {
case UntilYourNextTurn:
if (player != null
&& player.isInGame()) {
canDelete = canDelete
|| this.isYourNextTurn(game);
}
}
return canDelete;
}
@Override
public Layer getLayer() {
return layer;
}
@Override
public SubLayer getSublayer() {
return sublayer;
}
@Override
public void overrideRuleText(String text) {
this.staticText = text;
}
protected static boolean isCanKill(DynamicValue toughness) {
if (toughness instanceof StaticValue) {
return toughness.calculate(null, null, null) < 0;
}
if (toughness instanceof SignInversionDynamicValue) {
// count this class as used for "-{something_positive}"
return true;
}
if (toughness instanceof DomainValue) {
return ((DomainValue) toughness).getAmount() < 0;
}
return false;
}
@Override
public List<MageObjectReference> getAffectedObjects() {
return affectedObjectList;
}
/**
* Returns the status if the effect is temporary added to the
* ContinuousEffects
*
* @return
*/
@Override
public boolean isTemporary() {
return temporary;
}
@Override
public void setTemporary(boolean temporary) {
this.temporary = temporary;
}
public boolean isCharacterDefining() {
return characterDefining;
}
public void setCharacterDefining(boolean characterDefining) {
this.characterDefining = characterDefining;
}
@Override
public Set<UUID> isDependentTo(List<ContinuousEffect> allEffectsInLayer) {
Set<UUID> dependentToEffects = new HashSet<>();
if (dependendToTypes != null) {
for (ContinuousEffect effect : allEffectsInLayer) {
if (!effect.getId().equals(this.getId())) {
for (DependencyType dependencyType : effect.getDependencyTypes()) {
if (dependendToTypes.contains(dependencyType)) {
dependentToEffects.add(effect.getId());
break;
}
}
}
}
}
return dependentToEffects;
}
@Override
public EnumSet<DependencyType> getDependencyTypes() {
return dependencyTypes;
}
@Override
public EnumSet<DependencyType> getDependedToTypes() {
return dependendToTypes;
}
@Override
public void addDependencyType(DependencyType dependencyType) {
dependencyTypes.add(dependencyType);
}
@Override
public void setDependedToType(DependencyType dependencyType) {
dependendToTypes.clear();
dependendToTypes.add(dependencyType);
}
@Override
public void addDependedToType(DependencyType dependencyType) {
dependendToTypes.add(dependencyType);
}
@Override
public ContinuousEffect setTargetPointer(TargetPointer targetPointer) {
super.setTargetPointer(targetPointer);
return this;
}
/**
* Auto-generates dependencies on different effects (what's apply first and
* what's apply second)
*
* @param abilityToGain
* @param filterToSearch
*/
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);
}
}
public boolean canLookAtNextTopLibraryCard(Game game) {
// If the top card of your library changes while youre casting a spell, playing a land, or activating an ability,
// you cant look at the new top card until you finish doing so. This means that if you cast the top card of
// your library, you cant look at the next one until youre done paying for that spell. (2019-05-03)
if (!game.getStack().isEmpty()) {
StackObject stackObject = game.getStack().getFirst();
return !(stackObject instanceof Spell)
|| !Zone.LIBRARY.equals(((Spell) stackObject).getFromZone())
|| ((Spell) stackObject).getCurrentActivatingManaAbilitiesStep() == ActivationManaAbilityStep.AFTER; // mana payment finished
}
return true;
}
}