forked from External/mage
1263 lines
60 KiB
Java
1263 lines
60 KiB
Java
/*
|
||
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
|
||
*
|
||
* Redistribution and use in source and binary forms, with or without modification, are
|
||
* permitted provided that the following conditions are met:
|
||
*
|
||
* 1. Redistributions of source code must retain the above copyright notice, this list of
|
||
* conditions and the following disclaimer.
|
||
*
|
||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||
* of conditions and the following disclaimer in the documentation and/or other materials
|
||
* provided with the distribution.
|
||
*
|
||
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
|
||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
*
|
||
* The views and conclusions contained in the software and documentation are those of the
|
||
* authors and should not be interpreted as representing official policies, either expressed
|
||
* or implied, of BetaSteward_at_googlemail.com.
|
||
*/
|
||
package mage.abilities.effects;
|
||
|
||
import java.io.Serializable;
|
||
import java.util.*;
|
||
import java.util.Map.Entry;
|
||
import java.util.stream.Collectors;
|
||
|
||
import mage.MageObject;
|
||
import mage.abilities.*;
|
||
import mage.abilities.keyword.SpliceOntoArcaneAbility;
|
||
import mage.cards.Cards;
|
||
import mage.cards.CardsImpl;
|
||
import mage.constants.*;
|
||
import mage.filter.FilterCard;
|
||
import mage.filter.predicate.Predicate;
|
||
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.game.stack.Spell;
|
||
import mage.players.ManaPoolItem;
|
||
import mage.players.Player;
|
||
import mage.target.common.TargetCardInHand;
|
||
import org.apache.log4j.Logger;
|
||
|
||
/**
|
||
* @author BetaSteward_at_googlemail.com
|
||
*/
|
||
public class ContinuousEffects implements Serializable {
|
||
|
||
private static final Logger logger = Logger.getLogger(ContinuousEffects.class);
|
||
|
||
private long order = 0;
|
||
|
||
//transient Continuous effects
|
||
private ContinuousEffectsList<ContinuousEffect> layeredEffects = new ContinuousEffectsList<>();
|
||
private ContinuousEffectsList<ContinuousRuleModifyingEffect> continuousRuleModifyingEffects = new ContinuousEffectsList<>();
|
||
private ContinuousEffectsList<ReplacementEffect> replacementEffects = new ContinuousEffectsList<>();
|
||
private ContinuousEffectsList<PreventionEffect> preventionEffects = new ContinuousEffectsList<>();
|
||
private ContinuousEffectsList<RequirementEffect> requirementEffects = new ContinuousEffectsList<>();
|
||
private ContinuousEffectsList<RestrictionEffect> restrictionEffects = new ContinuousEffectsList<>();
|
||
private ContinuousEffectsList<RestrictionUntapNotMoreThanEffect> restrictionUntapNotMoreThanEffects = new ContinuousEffectsList<>();
|
||
private ContinuousEffectsList<CostModificationEffect> costModificationEffects = new ContinuousEffectsList<>();
|
||
private ContinuousEffectsList<SpliceCardEffect> spliceCardEffects = new ContinuousEffectsList<>();
|
||
|
||
private final Map<AsThoughEffectType, ContinuousEffectsList<AsThoughEffect>> asThoughEffectsMap = new EnumMap<>(AsThoughEffectType.class);
|
||
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<>();
|
||
|
||
// note all effect/abilities that were only added temporary
|
||
private final Map<ContinuousEffect, Set<Ability>> temporaryEffects = new HashMap<>();
|
||
|
||
|
||
public ContinuousEffects() {
|
||
applyCounters = new ApplyCountersEffect();
|
||
planeswalkerRedirectionEffect = new PlaneswalkerRedirectionEffect();
|
||
auraReplacementEffect = new AuraReplacementEffect();
|
||
collectAllEffects();
|
||
}
|
||
|
||
public ContinuousEffects(final ContinuousEffects effect) {
|
||
this.applyCounters = effect.applyCounters.copy();
|
||
this.planeswalkerRedirectionEffect = effect.planeswalkerRedirectionEffect.copy();
|
||
this.auraReplacementEffect = effect.auraReplacementEffect.copy();
|
||
layeredEffects = effect.layeredEffects.copy();
|
||
continuousRuleModifyingEffects = effect.continuousRuleModifyingEffects.copy();
|
||
replacementEffects = effect.replacementEffects.copy();
|
||
preventionEffects = effect.preventionEffects.copy();
|
||
requirementEffects = effect.requirementEffects.copy();
|
||
restrictionEffects = effect.restrictionEffects.copy();
|
||
restrictionUntapNotMoreThanEffects = effect.restrictionUntapNotMoreThanEffects.copy();
|
||
for (Map.Entry<AsThoughEffectType, ContinuousEffectsList<AsThoughEffect>> entry : effect.asThoughEffectsMap.entrySet()) {
|
||
asThoughEffectsMap.put(entry.getKey(), entry.getValue().copy());
|
||
}
|
||
|
||
costModificationEffects = effect.costModificationEffects.copy();
|
||
spliceCardEffects = effect.spliceCardEffects.copy();
|
||
temporaryEffects.putAll(effect.temporaryEffects);
|
||
collectAllEffects();
|
||
order = effect.order;
|
||
}
|
||
|
||
private void collectAllEffects() {
|
||
allEffectsLists.add(layeredEffects);
|
||
allEffectsLists.add(continuousRuleModifyingEffects);
|
||
allEffectsLists.add(replacementEffects);
|
||
allEffectsLists.add(preventionEffects);
|
||
allEffectsLists.add(requirementEffects);
|
||
allEffectsLists.add(restrictionEffects);
|
||
allEffectsLists.add(restrictionUntapNotMoreThanEffects);
|
||
allEffectsLists.add(costModificationEffects);
|
||
allEffectsLists.add(spliceCardEffects);
|
||
for (ContinuousEffectsList continuousEffectsList : asThoughEffectsMap.values()) {
|
||
allEffectsLists.add(continuousEffectsList);
|
||
}
|
||
}
|
||
|
||
public ContinuousEffects copy() {
|
||
return new ContinuousEffects(this);
|
||
}
|
||
|
||
public List<RequirementEffect> getRequirementEffects() {
|
||
return requirementEffects;
|
||
}
|
||
|
||
public List<RestrictionEffect> getRestrictionEffects() {
|
||
return restrictionEffects;
|
||
}
|
||
|
||
public void removeEndOfCombatEffects() {
|
||
layeredEffects.removeEndOfCombatEffects();
|
||
continuousRuleModifyingEffects.removeEndOfCombatEffects();
|
||
replacementEffects.removeEndOfCombatEffects();
|
||
preventionEffects.removeEndOfCombatEffects();
|
||
requirementEffects.removeEndOfCombatEffects();
|
||
restrictionEffects.removeEndOfCombatEffects();
|
||
for (ContinuousEffectsList asThoughtlist : asThoughEffectsMap.values()) {
|
||
asThoughtlist.removeEndOfCombatEffects();
|
||
}
|
||
costModificationEffects.removeEndOfCombatEffects();
|
||
spliceCardEffects.removeEndOfCombatEffects();
|
||
}
|
||
|
||
public void removeEndOfTurnEffects() {
|
||
layeredEffects.removeEndOfTurnEffects();
|
||
continuousRuleModifyingEffects.removeEndOfTurnEffects();
|
||
replacementEffects.removeEndOfTurnEffects();
|
||
preventionEffects.removeEndOfTurnEffects();
|
||
requirementEffects.removeEndOfTurnEffects();
|
||
restrictionEffects.removeEndOfTurnEffects();
|
||
for (ContinuousEffectsList asThoughtlist : asThoughEffectsMap.values()) {
|
||
asThoughtlist.removeEndOfTurnEffects();
|
||
}
|
||
costModificationEffects.removeEndOfTurnEffects();
|
||
spliceCardEffects.removeEndOfTurnEffects();
|
||
}
|
||
|
||
public void removeInactiveEffects(Game game) {
|
||
layeredEffects.removeInactiveEffects(game);
|
||
continuousRuleModifyingEffects.removeInactiveEffects(game);
|
||
replacementEffects.removeInactiveEffects(game);
|
||
preventionEffects.removeInactiveEffects(game);
|
||
requirementEffects.removeInactiveEffects(game);
|
||
restrictionEffects.removeInactiveEffects(game);
|
||
restrictionUntapNotMoreThanEffects.removeInactiveEffects(game);
|
||
for (ContinuousEffectsList asThoughtlist : asThoughEffectsMap.values()) {
|
||
asThoughtlist.removeInactiveEffects(game);
|
||
}
|
||
costModificationEffects.removeInactiveEffects(game);
|
||
spliceCardEffects.removeInactiveEffects(game);
|
||
}
|
||
|
||
public List<ContinuousEffect> getLayeredEffects(Game game) {
|
||
List<ContinuousEffect> layerEffects = new ArrayList<>();
|
||
for (ContinuousEffect effect : layeredEffects) {
|
||
switch (effect.getDuration()) {
|
||
case WhileOnBattlefield:
|
||
case WhileOnStack:
|
||
case WhileInGraveyard:
|
||
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
|
||
if (!abilities.isEmpty()) {
|
||
for (Ability ability : abilities) {
|
||
// If e.g. triggerd abilities (non static) created the effect, the ability must not be in usable zone (e.g. Unearth giving Haste effect)
|
||
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, null)) {
|
||
layerEffects.add(effect);
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
logger.error("No abilities for continuous effect: " + effect.toString());
|
||
}
|
||
break;
|
||
default:
|
||
layerEffects.add(effect);
|
||
}
|
||
}
|
||
|
||
updateTimestamps(layerEffects);
|
||
|
||
Collections.sort(layerEffects, Comparator.comparingLong(ContinuousEffect::getOrder));
|
||
return layerEffects;
|
||
}
|
||
|
||
/**
|
||
* Initially effect timestamp is set when game starts in game.loadCard
|
||
* method. After that timestamp should be updated whenever effect becomes
|
||
* "actual" meaning it becomes turned on that is defined by
|
||
* Ability.#isInUseableZone(Game, boolean) method in
|
||
* #getLayeredEffects(Game).
|
||
*
|
||
* @param layerEffects
|
||
*/
|
||
private void updateTimestamps(List<ContinuousEffect> layerEffects) {
|
||
for (ContinuousEffect continuousEffect : layerEffects) {
|
||
// check if it's new, then set order
|
||
if (!previous.contains(continuousEffect)) {
|
||
setOrder(continuousEffect);
|
||
}
|
||
}
|
||
previous.clear();
|
||
previous.addAll(layerEffects);
|
||
}
|
||
|
||
public void setOrder(ContinuousEffect effect) {
|
||
effect.setOrder(order++);
|
||
}
|
||
|
||
private List<ContinuousEffect> filterLayeredEffects(List<ContinuousEffect> effects, Layer layer) {
|
||
return effects.stream().filter(effect->effect.hasLayer(layer)).collect(Collectors.toList());
|
||
}
|
||
|
||
public HashMap<RequirementEffect, HashSet<Ability>> getApplicableRequirementEffects(Permanent permanent, Game game) {
|
||
HashMap<RequirementEffect, HashSet<Ability>> effects = new HashMap<>();
|
||
for (RequirementEffect effect : requirementEffects) {
|
||
HashSet<Ability> abilities = requirementEffects.getAbility(effect.getId());
|
||
HashSet<Ability> applicableAbilities = new HashSet<>();
|
||
for (Ability ability : abilities) {
|
||
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, ability instanceof MageSingleton ? permanent : null, null)) {
|
||
if (effect.applies(permanent, ability, game)) {
|
||
applicableAbilities.add(ability);
|
||
}
|
||
}
|
||
}
|
||
if (!applicableAbilities.isEmpty()) {
|
||
effects.put(effect, abilities);
|
||
}
|
||
}
|
||
return effects;
|
||
}
|
||
|
||
public HashMap<RestrictionEffect, HashSet<Ability>> getApplicableRestrictionEffects(Permanent permanent, Game game) {
|
||
HashMap<RestrictionEffect, HashSet<Ability>> effects = new HashMap<>();
|
||
for (RestrictionEffect effect : restrictionEffects) {
|
||
HashSet<Ability> abilities = restrictionEffects.getAbility(effect.getId());
|
||
HashSet<Ability> applicableAbilities = new HashSet<>();
|
||
for (Ability ability : abilities) {
|
||
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, ability instanceof MageSingleton ? permanent : null, null)) {
|
||
if (effect.applies(permanent, ability, game)) {
|
||
applicableAbilities.add(ability);
|
||
}
|
||
}
|
||
}
|
||
if (!applicableAbilities.isEmpty()) {
|
||
effects.put(effect, abilities);
|
||
}
|
||
}
|
||
return effects;
|
||
}
|
||
|
||
public HashMap<RestrictionUntapNotMoreThanEffect, HashSet<Ability>> getApplicableRestrictionUntapNotMoreThanEffects(Player player, Game game) {
|
||
HashMap<RestrictionUntapNotMoreThanEffect, HashSet<Ability>> effects = new HashMap<>();
|
||
for (RestrictionUntapNotMoreThanEffect effect : restrictionUntapNotMoreThanEffects) {
|
||
HashSet<Ability> abilities = restrictionUntapNotMoreThanEffects.getAbility(effect.getId());
|
||
HashSet<Ability> applicableAbilities = new HashSet<>();
|
||
for (Ability ability : abilities) {
|
||
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, null)) {
|
||
if (effect.applies(player, ability, game)) {
|
||
applicableAbilities.add(ability);
|
||
}
|
||
}
|
||
}
|
||
if (!applicableAbilities.isEmpty()) {
|
||
effects.put(effect, abilities);
|
||
}
|
||
}
|
||
return effects;
|
||
}
|
||
|
||
public boolean checkIfThereArePayCostToAttackBlockEffects(GameEvent event, Game game) {
|
||
for (ReplacementEffect effect : replacementEffects) {
|
||
if (!effect.checksEventType(event, game)) {
|
||
continue;
|
||
}
|
||
if (effect instanceof PayCostToAttackBlockEffect) {
|
||
HashSet<Ability> abilities = replacementEffects.getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
// for replacment effects of static abilities do not use LKI to check if to apply
|
||
if (ability.getAbilityType() != AbilityType.STATIC || ability.isInUseableZone(game, null, event)) {
|
||
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
|
||
if (!game.getScopeRelevant() || effect.hasSelfScope() || !event.getTargetId().equals(ability.getSourceId())) {
|
||
if (effect.applies(event, ability, game)
|
||
&& !((PayCostToAttackBlockEffect) effect).isCostless(event, ability, game)) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* @param event
|
||
* @param game
|
||
* @return a list of all {@link ReplacementEffect} that apply to the current
|
||
* event
|
||
*/
|
||
private HashMap<ReplacementEffect, HashSet<Ability>> getApplicableReplacementEffects(GameEvent event, Game game) {
|
||
HashMap<ReplacementEffect, HashSet<Ability>> replaceEffects = new HashMap<>();
|
||
if (planeswalkerRedirectionEffect.checksEventType(event, game) && planeswalkerRedirectionEffect.applies(event, null, game)) {
|
||
replaceEffects.put(planeswalkerRedirectionEffect, null);
|
||
}
|
||
if (auraReplacementEffect.checksEventType(event, game) && auraReplacementEffect.applies(event, null, game)) {
|
||
replaceEffects.put(auraReplacementEffect, null);
|
||
}
|
||
// boolean checkLKI = event.getType().equals(EventType.ZONE_CHANGE) || event.getType().equals(EventType.DESTROYED_PERMANENT);
|
||
//get all applicable transient Replacement effects
|
||
for (ReplacementEffect effect : replacementEffects) {
|
||
if (!effect.checksEventType(event, game)) {
|
||
continue;
|
||
}
|
||
if (event.getAppliedEffects() != null && event.getAppliedEffects().contains(effect.getId())) {
|
||
// Effect already applied to this event, ignore it
|
||
// TODO: Handle also gained effect that are connected to different abilities.
|
||
continue;
|
||
}
|
||
HashSet<Ability> abilities = replacementEffects.getAbility(effect.getId());
|
||
HashSet<Ability> applicableAbilities = new HashSet<>();
|
||
for (Ability ability : abilities) {
|
||
// for replacment effects of static abilities do not use LKI to check if to apply
|
||
if (ability.getAbilityType() != AbilityType.STATIC || ability.isInUseableZone(game, null, event)) {
|
||
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 (!applicableAbilities.isEmpty()) {
|
||
replaceEffects.put(effect, applicableAbilities);
|
||
}
|
||
}
|
||
for (PreventionEffect effect : preventionEffects) {
|
||
if (!effect.checksEventType(event, game)) {
|
||
continue;
|
||
}
|
||
if (event.getAppliedEffects() != null && event.getAppliedEffects().contains(effect.getId())) {
|
||
// Effect already applied to this event, ignore it
|
||
// TODO: Handle also gained effect that are connected to different abilities.
|
||
continue;
|
||
}
|
||
HashSet<Ability> abilities = preventionEffects.getAbility(effect.getId());
|
||
HashSet<Ability> applicableAbilities = new HashSet<>();
|
||
for (Ability ability : abilities) {
|
||
if (ability.getAbilityType() != AbilityType.STATIC || ability.isInUseableZone(game, null, event)) {
|
||
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
|
||
if (effect.applies(event, ability, game)) {
|
||
applicableAbilities.add(ability);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (!applicableAbilities.isEmpty()) {
|
||
replaceEffects.put((ReplacementEffect) effect, applicableAbilities);
|
||
}
|
||
}
|
||
return replaceEffects;
|
||
}
|
||
|
||
private boolean checkAbilityStillExists(Ability ability, ContinuousEffect effect, GameEvent event, Game game) {
|
||
switch (effect.getDuration()) { // effects with fixed duration don't need an object with the source ability (e.g. a silence cast with isochronic Scepter has no more a card object
|
||
case EndOfCombat:
|
||
case EndOfGame:
|
||
case EndOfStep:
|
||
case EndOfTurn:
|
||
case OneUse:
|
||
case Custom: // custom duration means the effect ends itself if needed
|
||
return true;
|
||
}
|
||
if (ability.getSourceId() == null) { // commander replacement effect
|
||
return true;
|
||
}
|
||
MageObject object;
|
||
if (event.getType() == EventType.ZONE_CHANGE
|
||
&& ((ZoneChangeEvent) event).getFromZone() == 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.isTransformable() && event.getType() == GameEvent.EventType.TRANSFORMED) {
|
||
exists = permanent.getCard().getAbilities().contains(ability);
|
||
}
|
||
}
|
||
} else if (object instanceof PermanentCard) {
|
||
PermanentCard permanent = (PermanentCard) object;
|
||
if (permanent.isFaceDown(game) && !ability.getWorksFaceDown()) {
|
||
return false;
|
||
}
|
||
} else if (object instanceof Spell) {
|
||
Spell spell = (Spell) object;
|
||
if (spell.isFaceDown(game) && !ability.getWorksFaceDown()) {
|
||
return false;
|
||
}
|
||
}
|
||
return exists;
|
||
}
|
||
|
||
/**
|
||
* Filters out cost modification effects that are not active.
|
||
*
|
||
* @param game
|
||
* @return
|
||
*/
|
||
private List<CostModificationEffect> getApplicableCostModificationEffects(Game game) {
|
||
List<CostModificationEffect> costEffects = new ArrayList<>();
|
||
|
||
for (CostModificationEffect effect : costModificationEffects) {
|
||
HashSet<Ability> abilities = costModificationEffects.getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, null)) {
|
||
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
|
||
costEffects.add(effect);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return costEffects;
|
||
}
|
||
|
||
/**
|
||
* Filters out splice effects that are not active.
|
||
*
|
||
* @param game
|
||
* @return
|
||
*/
|
||
private List<SpliceCardEffect> getApplicableSpliceCardEffects(Game game, UUID playerId) {
|
||
List<SpliceCardEffect> spliceEffects = new ArrayList<>();
|
||
|
||
for (SpliceCardEffect effect : spliceCardEffects) {
|
||
HashSet<Ability> abilities = spliceCardEffects.getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
if (ability.getControllerId().equals(playerId) && (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, null))) {
|
||
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
|
||
spliceEffects.add(effect);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return spliceEffects;
|
||
}
|
||
|
||
public boolean asThough(UUID objectId, AsThoughEffectType type, UUID controllerId, Game game) {
|
||
return asThough(objectId, type, null, controllerId, game);
|
||
}
|
||
|
||
public boolean asThough(UUID objectId, AsThoughEffectType type, Ability affectedAbility, UUID controllerId, Game game) {
|
||
List<AsThoughEffect> asThoughEffectsList = getApplicableAsThoughEffects(type, game);
|
||
for (AsThoughEffect effect : asThoughEffectsList) {
|
||
HashSet<Ability> abilities = asThoughEffectsMap.get(type).getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
if (affectedAbility == null) {
|
||
if (effect.applies(objectId, ability, controllerId, game)) {
|
||
return true;
|
||
}
|
||
} else if (effect.applies(objectId, affectedAbility, ability, game)) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
|
||
}
|
||
|
||
public ManaType asThoughMana(ManaType manaType, ManaPoolItem mana, UUID objectId, Ability affectedAbility, UUID controllerId, Game game) {
|
||
// First check existing only effects
|
||
List<AsThoughEffect> asThoughEffectsList = getApplicableAsThoughEffects(AsThoughEffectType.SPEND_ONLY_MANA, game);
|
||
for (AsThoughEffect effect : asThoughEffectsList) {
|
||
HashSet<Ability> abilities = asThoughEffectsMap.get(AsThoughEffectType.SPEND_ONLY_MANA).getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
if ((affectedAbility == null && effect.applies(objectId, ability, controllerId, game))
|
||
|| effect.applies(objectId, affectedAbility, ability, game)) {
|
||
if (((AsThoughManaEffect) effect).getAsThoughManaType(manaType, mana, controllerId, ability, game) == null) {
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// then check effects that allow to use other mana types to pay the current mana type to pay
|
||
asThoughEffectsList = getApplicableAsThoughEffects(AsThoughEffectType.SPEND_OTHER_MANA, game);
|
||
for (AsThoughEffect effect : asThoughEffectsList) {
|
||
HashSet<Ability> abilities = asThoughEffectsMap.get(AsThoughEffectType.SPEND_OTHER_MANA).getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
if ((affectedAbility == null && effect.applies(objectId, ability, controllerId, game))
|
||
|| effect.applies(objectId, affectedAbility, ability, game)) {
|
||
ManaType usableManaType = ((AsThoughManaEffect) effect).getAsThoughManaType(manaType, mana, controllerId, ability, game);
|
||
if (usableManaType != null) {
|
||
return usableManaType;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return manaType;
|
||
}
|
||
|
||
/**
|
||
* Filters out asThough effects that are not active.
|
||
*
|
||
* @param type type
|
||
* @param game
|
||
* @return
|
||
*/
|
||
private List<AsThoughEffect> getApplicableAsThoughEffects(AsThoughEffectType type, Game game) {
|
||
List<AsThoughEffect> asThoughEffectsList = new ArrayList<>();
|
||
if (asThoughEffectsMap.containsKey(type)) {
|
||
for (AsThoughEffect effect : asThoughEffectsMap.get(type)) {
|
||
HashSet<Ability> abilities = asThoughEffectsMap.get(type).getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, null)) {
|
||
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
|
||
asThoughEffectsList.add(effect);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return asThoughEffectsList;
|
||
}
|
||
|
||
/**
|
||
* 601.2e The player determines the total cost of the spell. Usually this is
|
||
* just the mana cost. Some spells have additional or alternative costs.
|
||
* Some effects may increase or reduce the cost to pay, or may provide other
|
||
* alternative costs. Costs may include paying mana, tapping permanents,
|
||
* sacrificing permanents, discarding cards, and so on. The total cost is
|
||
* the mana cost or alternative cost (as determined in rule 601.2b), plus
|
||
* all additional costs and cost increases, and minus all cost reductions.
|
||
* If the mana component of the total cost is reduced to nothing by cost
|
||
* reduction effects, it is considered to be {0}. It can’t be reduced to
|
||
* less than {0}. Once the total cost is determined, any effects that
|
||
* directly affect the total cost are applied. Then the resulting total cost
|
||
* becomes “locked in.” If effects would change the total cost after this
|
||
* time, they have no effect.
|
||
*/
|
||
/**
|
||
* Inspects all {@link Permanent permanent's} {@link Ability abilities} on
|
||
* the battlefield for
|
||
* {@link CostModificationEffect cost modification effects} and applies them
|
||
* if necessary.
|
||
*
|
||
* @param abilityToModify
|
||
* @param game
|
||
*/
|
||
public void costModification(Ability abilityToModify, Game game) {
|
||
List<CostModificationEffect> costEffects = getApplicableCostModificationEffects(game);
|
||
|
||
for (CostModificationEffect effect : costEffects) {
|
||
if (effect.getModificationType() == CostModificationType.INCREASE_COST) {
|
||
HashSet<Ability> abilities = costModificationEffects.getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
if (effect.applies(abilityToModify, ability, game)) {
|
||
effect.apply(game, ability, abilityToModify);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
for (CostModificationEffect effect : costEffects) {
|
||
if (effect.getModificationType() == CostModificationType.REDUCE_COST) {
|
||
HashSet<Ability> abilities = costModificationEffects.getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
if (effect.applies(abilityToModify, ability, game)) {
|
||
effect.apply(game, ability, abilityToModify);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
for (CostModificationEffect effect : costEffects) {
|
||
if (effect.getModificationType() == CostModificationType.SET_COST) {
|
||
HashSet<Ability> abilities = costModificationEffects.getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
if (effect.applies(abilityToModify, ability, game)) {
|
||
effect.apply(game, ability, abilityToModify);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Checks all available splice effects to be applied.
|
||
*
|
||
* @param abilityToModify
|
||
* @param game
|
||
*/
|
||
public void applySpliceEffects(Ability abilityToModify, Game game) {
|
||
if (((SpellAbility) abilityToModify).getSpellAbilityType() == SpellAbilityType.SPLICE) {
|
||
// on a spliced ability of a spell can't be spliced again
|
||
return;
|
||
}
|
||
List<SpliceCardEffect> spliceEffects = getApplicableSpliceCardEffects(game, abilityToModify.getControllerId());
|
||
// get the applyable splice abilities
|
||
List<SpliceOntoArcaneAbility> spliceAbilities = new ArrayList<>();
|
||
for (SpliceCardEffect effect : spliceEffects) {
|
||
HashSet<Ability> abilities = spliceCardEffects.getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
if (effect.applies(abilityToModify, ability, game)) {
|
||
spliceAbilities.add((SpliceOntoArcaneAbility) ability);
|
||
}
|
||
}
|
||
}
|
||
// check if player wants to use splice
|
||
|
||
if (!spliceAbilities.isEmpty()) {
|
||
Player controller = game.getPlayer(abilityToModify.getControllerId());
|
||
if (controller.chooseUse(Outcome.Benefit, "Splice a card?", abilityToModify, game)) {
|
||
Cards cardsToReveal = new CardsImpl();
|
||
do {
|
||
FilterCard filter = new FilterCard("a card to splice");
|
||
ArrayList<Predicate<MageObject>> idPredicates = new ArrayList<>();
|
||
for (SpliceOntoArcaneAbility ability : spliceAbilities) {
|
||
idPredicates.add(new CardIdPredicate((ability.getSourceId())));
|
||
}
|
||
filter.add(Predicates.or(idPredicates));
|
||
TargetCardInHand target = new TargetCardInHand(filter);
|
||
controller.chooseTarget(Outcome.Benefit, target, abilityToModify, game);
|
||
UUID cardId = target.getFirstTarget();
|
||
if (cardId != null) {
|
||
SpliceOntoArcaneAbility selectedAbility = null;
|
||
for (SpliceOntoArcaneAbility ability : spliceAbilities) {
|
||
if (ability.getSourceId().equals(cardId)) {
|
||
selectedAbility = ability;
|
||
break;
|
||
}
|
||
}
|
||
if (selectedAbility != null) {
|
||
SpliceCardEffect spliceEffect = (SpliceCardEffect) selectedAbility.getEffects().get(0);
|
||
spliceEffect.apply(game, selectedAbility, abilityToModify);
|
||
cardsToReveal.add(game.getCard(cardId));
|
||
spliceAbilities.remove(selectedAbility);
|
||
}
|
||
}
|
||
} while (!spliceAbilities.isEmpty() && controller.chooseUse(Outcome.Benefit, "Splice another card?", abilityToModify, game));
|
||
controller.revealCards("Spliced cards", cardsToReveal, game);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Checks if an event won't happen because of an rule modifying effect
|
||
*
|
||
* @param event
|
||
* @param targetAbility ability the event is attached to. can be null.
|
||
* @param game
|
||
* @param checkPlayableMode true if the event does not really happen but
|
||
* it's checked if the event would be replaced
|
||
* @return
|
||
*/
|
||
public boolean preventedByRuleModification(GameEvent event, Ability targetAbility, Game game, boolean checkPlayableMode) {
|
||
for (ContinuousRuleModifyingEffect effect : continuousRuleModifyingEffects) {
|
||
if (!effect.checksEventType(event, game)) {
|
||
continue;
|
||
}
|
||
for (Ability sourceAbility : continuousRuleModifyingEffects.getAbility(effect.getId())) {
|
||
if (!(sourceAbility instanceof StaticAbility) || sourceAbility.isInUseableZone(game, null, event)) {
|
||
if (checkAbilityStillExists(sourceAbility, effect, event, game)) {
|
||
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
|
||
effect.setValue("targetAbility", targetAbility);
|
||
if (effect.applies(event, sourceAbility, game)) {
|
||
if (targetAbility instanceof ActivatedAbility && ((ActivatedAbility) targetAbility).isCheckPlayableMode()) {
|
||
checkPlayableMode = true;
|
||
}
|
||
if (!checkPlayableMode) {
|
||
String message = effect.getInfoMessage(sourceAbility, event, game);
|
||
if (message != null && !message.isEmpty()) {
|
||
if (effect.sendMessageToUser()) {
|
||
Player player = game.getPlayer(event.getPlayerId());
|
||
if (player != null && !game.isSimulation()) {
|
||
game.informPlayer(player, message);
|
||
}
|
||
}
|
||
if (effect.sendMessageToGameLog() && !game.isSimulation()) {
|
||
game.informPlayers(message);
|
||
}
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public boolean replaceEvent(GameEvent event, Game game) {
|
||
boolean caught = false;
|
||
HashMap<UUID, HashSet<UUID>> consumed = new HashMap<>();
|
||
do {
|
||
HashMap<ReplacementEffect, HashSet<Ability>> rEffects = getApplicableReplacementEffects(event, game);
|
||
// Remove all consumed effects (ability dependant)
|
||
for (Iterator<ReplacementEffect> it1 = rEffects.keySet().iterator(); it1.hasNext();) {
|
||
ReplacementEffect entry = it1.next();
|
||
if (consumed.containsKey(entry.getId())) {
|
||
HashSet<UUID> consumedAbilitiesIds = consumed.get(entry.getId());
|
||
if (rEffects.get(entry) == null || consumedAbilitiesIds.size() == rEffects.get(entry).size()) {
|
||
it1.remove();
|
||
} else {
|
||
Iterator it = rEffects.get(entry).iterator();
|
||
while (it.hasNext()) {
|
||
Ability ability = (Ability) it.next();
|
||
if (consumedAbilitiesIds.contains(ability.getId())) {
|
||
it.remove();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// no effects left, quit
|
||
if (rEffects.isEmpty()) {
|
||
break;
|
||
}
|
||
int index;
|
||
boolean onlyOne = false;
|
||
if (rEffects.size() == 1) {
|
||
ReplacementEffect effect = rEffects.keySet().iterator().next();
|
||
HashSet<Ability> abilities;
|
||
if (effect.getEffectType() == EffectType.REPLACEMENT) {
|
||
abilities = replacementEffects.getAbility(effect.getId());
|
||
} else {
|
||
abilities = preventionEffects.getAbility(effect.getId());
|
||
}
|
||
if (abilities == null || abilities.size() == 1) {
|
||
onlyOne = true;
|
||
}
|
||
}
|
||
if (onlyOne) {
|
||
index = 0;
|
||
} else {
|
||
//20100716 - 616.1c
|
||
Player player = game.getPlayer(event.getPlayerId());
|
||
index = player.chooseReplacementEffect(getReplacementEffectsTexts(rEffects, game), game);
|
||
}
|
||
// get the selected effect
|
||
int checked = 0;
|
||
ReplacementEffect rEffect = null;
|
||
Ability rAbility = null;
|
||
for (Map.Entry<ReplacementEffect, HashSet<Ability>> entry : rEffects.entrySet()) {
|
||
if (entry.getValue() == null) {
|
||
if (checked == index) {
|
||
rEffect = entry.getKey();
|
||
break;
|
||
} else {
|
||
checked++;
|
||
}
|
||
} else {
|
||
HashSet<Ability> abilities = entry.getValue();
|
||
int size = abilities.size();
|
||
if (index > (checked + size - 1)) {
|
||
checked += size;
|
||
} else {
|
||
rEffect = entry.getKey();
|
||
Iterator it = abilities.iterator();
|
||
while (it.hasNext() && rAbility == null) {
|
||
if (checked == index) {
|
||
rAbility = (Ability) it.next();
|
||
} else {
|
||
it.next();
|
||
checked++;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (rEffect != null) {
|
||
event.getAppliedEffects().add(rEffect.getId());
|
||
caught = rEffect.replaceEvent(event, rAbility, game);
|
||
}
|
||
if (caught) { // Event was completely replaced -> stop applying effects to it
|
||
break;
|
||
}
|
||
|
||
// add the applied effect to the consumed effects
|
||
if (rEffect != null) {
|
||
if (consumed.containsKey(rEffect.getId())) {
|
||
HashSet<UUID> set = consumed.get(rEffect.getId());
|
||
if (rAbility != null) {
|
||
set.add(rAbility.getId());
|
||
|
||
}
|
||
} else {
|
||
HashSet<UUID> set = new HashSet<>();
|
||
if (rAbility != null) { // in case of AuraReplacementEffect or PlaneswalkerReplacementEffect there is no Ability
|
||
set.add(rAbility.getId());
|
||
}
|
||
consumed.put(rEffect.getId(), set);
|
||
}
|
||
}
|
||
// Must be called here for some effects to be able to work correctly
|
||
// For example: Vesuva copying a Dark Depth (VesuvaTest:testDarkDepth)
|
||
// This call should be removed if possible as replacement effects of EntersTheBattlefield events
|
||
// do no longer work correctly because the entering permanents are not yet on the battlefield (before they were).
|
||
// game.applyEffects();
|
||
} while (true);
|
||
return caught;
|
||
}
|
||
|
||
//20091005 - 613
|
||
public void apply(Game game) {
|
||
removeInactiveEffects(game);
|
||
List<ContinuousEffect> activeLayerEffects = getLayeredEffects(game);
|
||
|
||
List<ContinuousEffect> layer = filterLayeredEffects(activeLayerEffects, Layer.CopyEffects_1);
|
||
for (ContinuousEffect effect : layer) {
|
||
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
effect.apply(Layer.CopyEffects_1, SubLayer.NA, ability, game);
|
||
}
|
||
}
|
||
//Reload layerEffect if copy effects were applied
|
||
if (!layer.isEmpty()) {
|
||
activeLayerEffects = getLayeredEffects(game);
|
||
}
|
||
|
||
layer = filterLayeredEffects(activeLayerEffects, Layer.ControlChangingEffects_2);
|
||
// apply control changing effects multiple times if it's needed
|
||
// for cases when control over permanents with change control abilities is changed
|
||
// e.g. Mind Control is controlled by Steal Enchantment
|
||
while (true) {
|
||
for (ContinuousEffect effect : layer) {
|
||
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
effect.apply(Layer.ControlChangingEffects_2, SubLayer.NA, ability, game);
|
||
}
|
||
}
|
||
// if control over all permanent has not changed, we can no longer reapply control changing effects
|
||
if (!game.getBattlefield().fireControlChangeEvents(game)) {
|
||
break;
|
||
}
|
||
// reset control before reapplying control changing effects
|
||
game.getBattlefield().resetPermanentsControl();
|
||
}
|
||
|
||
applyLayer(activeLayerEffects, Layer.TextChangingEffects_3, game);
|
||
applyLayer(activeLayerEffects, Layer.TypeChangingEffects_4, game);
|
||
applyLayer(activeLayerEffects, Layer.ColorChangingEffects_5, game);
|
||
|
||
Map<ContinuousEffect, List<Ability>> appliedEffectAbilities = new HashMap<>();
|
||
boolean done = false;
|
||
Map<ContinuousEffect, Set<UUID>> waitingEffects = new LinkedHashMap<>();
|
||
Set<UUID> appliedEffects = new HashSet<>();
|
||
while (!done) { // loop needed if a added effect adds again an effect (e.g. Level 5- of Joraga Treespeaker)
|
||
done = true;
|
||
layer = filterLayeredEffects(activeLayerEffects, Layer.AbilityAddingRemovingEffects_6);
|
||
for (ContinuousEffect effect : layer) {
|
||
if (activeLayerEffects.contains(effect) && !appliedEffects.contains(effect.getId())) { // Effect does still exist and was not applied yet
|
||
Set<UUID> dependentTo = effect.isDependentTo(layer);
|
||
if (!appliedEffects.containsAll(dependentTo)) {
|
||
waitingEffects.put(effect, dependentTo);
|
||
continue;
|
||
}
|
||
List<Ability> appliedAbilities = appliedEffectAbilities.get(effect);
|
||
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
if (appliedAbilities == null || !appliedAbilities.contains(ability)) {
|
||
if (appliedAbilities == null) {
|
||
appliedAbilities = new ArrayList<>();
|
||
appliedEffectAbilities.put(effect, appliedAbilities);
|
||
}
|
||
appliedAbilities.add(ability);
|
||
effect.apply(Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, ability, game);
|
||
done = false;
|
||
// list must be updated after each applied effect (eg. if "Turn to Frog" removes abilities)
|
||
activeLayerEffects = getLayeredEffects(game);
|
||
}
|
||
}
|
||
appliedEffects.add(effect.getId());
|
||
|
||
if (!waitingEffects.isEmpty()) {
|
||
// check if waiting effects can be applied now
|
||
for (Iterator<Map.Entry<ContinuousEffect, Set<UUID>>> iterator = waitingEffects.entrySet().iterator(); iterator.hasNext();) {
|
||
Map.Entry<ContinuousEffect, Set<UUID>> entry = iterator.next();
|
||
if (appliedEffects.containsAll(entry.getValue())) { // all dependent to effects are applied now so apply the effect itself
|
||
appliedAbilities = appliedEffectAbilities.get(entry.getKey());
|
||
abilities = layeredEffects.getAbility(entry.getKey().getId());
|
||
for (Ability ability : abilities) {
|
||
if (appliedAbilities == null || !appliedAbilities.contains(ability)) {
|
||
if (appliedAbilities == null) {
|
||
appliedAbilities = new ArrayList<>();
|
||
appliedEffectAbilities.put(entry.getKey(), appliedAbilities);
|
||
}
|
||
appliedAbilities.add(ability);
|
||
entry.getKey().apply(Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, ability, game);
|
||
done = false;
|
||
// list must be updated after each applied effect (eg. if "Turn to Frog" removes abilities)
|
||
activeLayerEffects = getLayeredEffects(game);
|
||
}
|
||
}
|
||
appliedEffects.add(entry.getKey().getId());
|
||
iterator.remove();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
layer = filterLayeredEffects(activeLayerEffects, Layer.PTChangingEffects_7);
|
||
for (ContinuousEffect effect : layer) {
|
||
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
if (abilityActive(ability, game)) {
|
||
effect.apply(Layer.PTChangingEffects_7, SubLayer.CharacteristicDefining_7a, ability, game);
|
||
}
|
||
}
|
||
}
|
||
for (ContinuousEffect effect : layer) {
|
||
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
effect.apply(Layer.PTChangingEffects_7, SubLayer.SetPT_7b, ability, game);
|
||
}
|
||
}
|
||
for (ContinuousEffect effect : layer) {
|
||
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
effect.apply(Layer.PTChangingEffects_7, SubLayer.ModifyPT_7c, ability, game);
|
||
}
|
||
}
|
||
|
||
applyCounters.apply(Layer.PTChangingEffects_7, SubLayer.Counters_7d, null, game);
|
||
|
||
for (ContinuousEffect effect : layer) {
|
||
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
effect.apply(Layer.PTChangingEffects_7, SubLayer.SwitchPT_e, ability, game);
|
||
}
|
||
}
|
||
layer = filterLayeredEffects(activeLayerEffects, Layer.PlayerEffects);
|
||
for (ContinuousEffect effect : layer) {
|
||
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
effect.apply(Layer.PlayerEffects, SubLayer.NA, ability, game);
|
||
}
|
||
}
|
||
layer = filterLayeredEffects(activeLayerEffects, Layer.RulesEffects);
|
||
for (ContinuousEffect effect : layer) {
|
||
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
effect.apply(Layer.RulesEffects, SubLayer.NA, ability, game);
|
||
}
|
||
}
|
||
}
|
||
|
||
private boolean abilityActive(Ability ability, Game game) {
|
||
MageObject object = game.getObject(ability.getSourceId());
|
||
return object != null && object.hasAbility(ability.getId(), game);
|
||
}
|
||
|
||
private void applyLayer(List<ContinuousEffect> activeLayerEffects, Layer currentLayer, Game game) {
|
||
List<ContinuousEffect> layer = filterLayeredEffects(activeLayerEffects, currentLayer);
|
||
if (!layer.isEmpty()) {
|
||
int numberOfEffects = layer.size();
|
||
Set<UUID> appliedEffects = new HashSet<>();
|
||
Map<ContinuousEffect, Set<UUID>> waitingEffects = new LinkedHashMap<>();
|
||
for (ContinuousEffect effect : layer) {
|
||
if (numberOfEffects > 1) { // If an effect is dependent to not applied effects yet of this layer, so wait to apply this effect
|
||
Set<UUID> dependentTo = effect.isDependentTo(layer);
|
||
if (!appliedEffects.containsAll(dependentTo)) {
|
||
waitingEffects.put(effect, dependentTo);
|
||
continue;
|
||
}
|
||
}
|
||
applyContinuousEffect(effect, currentLayer, game);
|
||
appliedEffects.add(effect.getId());
|
||
if (!waitingEffects.isEmpty()) {
|
||
// check if waiting effects can be applied now
|
||
for (Entry<ContinuousEffect, Set<UUID>> entry : waitingEffects.entrySet()) {
|
||
if (appliedEffects.containsAll(entry.getValue())) { // all dependent to effects are applied now so apply the effect itself
|
||
applyContinuousEffect(entry.getKey(), currentLayer, game);
|
||
appliedEffects.add(entry.getKey().getId());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private void applyContinuousEffect(ContinuousEffect effect, Layer currentLayer, Game game) {
|
||
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
effect.apply(currentLayer, SubLayer.NA, ability, game);
|
||
}
|
||
}
|
||
|
||
public HashSet<Ability> getLayeredEffectAbilities(ContinuousEffect effect) {
|
||
return layeredEffects.getAbility(effect.getId());
|
||
}
|
||
|
||
/**
|
||
* 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
|
||
effect.setTemporary(true);
|
||
Set<Ability> 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);
|
||
}
|
||
|
||
public void addEffect(ContinuousEffect effect, Ability source) {
|
||
if (effect == null) {
|
||
logger.error("Effect is null: " + source.toString());
|
||
return;
|
||
} else if (source == null) {
|
||
logger.warn("Adding effect without ability : " + effect.toString());
|
||
}
|
||
switch (effect.getEffectType()) {
|
||
case REPLACEMENT:
|
||
case REDIRECTION:
|
||
ReplacementEffect newReplacementEffect = (ReplacementEffect) effect;
|
||
replacementEffects.addEffect(newReplacementEffect, source);
|
||
break;
|
||
case PREVENTION:
|
||
PreventionEffect newPreventionEffect = (PreventionEffect) effect;
|
||
preventionEffects.addEffect(newPreventionEffect, source);
|
||
break;
|
||
case RESTRICTION:
|
||
RestrictionEffect newRestrictionEffect = (RestrictionEffect) effect;
|
||
restrictionEffects.addEffect(newRestrictionEffect, source);
|
||
break;
|
||
case RESTRICTION_UNTAP_NOT_MORE_THAN:
|
||
RestrictionUntapNotMoreThanEffect newRestrictionUntapNotMoreThanEffect = (RestrictionUntapNotMoreThanEffect) effect;
|
||
restrictionUntapNotMoreThanEffects.addEffect(newRestrictionUntapNotMoreThanEffect, source);
|
||
break;
|
||
case REQUIREMENT:
|
||
RequirementEffect newRequirementEffect = (RequirementEffect) effect;
|
||
requirementEffects.addEffect(newRequirementEffect, source);
|
||
break;
|
||
case ASTHOUGH:
|
||
AsThoughEffect newAsThoughEffect = (AsThoughEffect) effect;
|
||
if (!asThoughEffectsMap.containsKey(newAsThoughEffect.getAsThoughEffectType())) {
|
||
ContinuousEffectsList<AsThoughEffect> list = new ContinuousEffectsList<>();
|
||
allEffectsLists.add(list);
|
||
asThoughEffectsMap.put(newAsThoughEffect.getAsThoughEffectType(), list);
|
||
}
|
||
asThoughEffectsMap.get(newAsThoughEffect.getAsThoughEffectType()).addEffect(newAsThoughEffect, source);
|
||
break;
|
||
case COSTMODIFICATION:
|
||
CostModificationEffect newCostModificationEffect = (CostModificationEffect) effect;
|
||
costModificationEffects.addEffect(newCostModificationEffect, source);
|
||
break;
|
||
case SPLICE:
|
||
SpliceCardEffect newSpliceCardEffect = (SpliceCardEffect) effect;
|
||
spliceCardEffects.addEffect(newSpliceCardEffect, source);
|
||
break;
|
||
case CONTINUOUS_RULE_MODIFICATION:
|
||
ContinuousRuleModifyingEffect newContinuousRuleModifiyingEffect = (ContinuousRuleModifyingEffect) effect;
|
||
continuousRuleModifyingEffects.addEffect(newContinuousRuleModifiyingEffect, source);
|
||
break;
|
||
default:
|
||
layeredEffects.addEffect(effect, source);
|
||
break;
|
||
}
|
||
}
|
||
|
||
public void setController(UUID cardId, UUID controllerId) {
|
||
for (ContinuousEffectsList effectsList : allEffectsLists) {
|
||
setControllerForEffect(effectsList, cardId, controllerId);
|
||
}
|
||
}
|
||
|
||
private void setControllerForEffect(ContinuousEffectsList<?> effects, UUID sourceId, UUID controllerId) {
|
||
for (Effect effect : effects) {
|
||
HashSet<Ability> abilities = effects.getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
if (ability.getSourceId() != null) {
|
||
if (ability.getSourceId().equals(sourceId)) {
|
||
ability.setControllerId(controllerId);
|
||
}
|
||
} else if (ability.getZone() != Zone.COMMAND) {
|
||
logger.fatal("Continuous effect for ability with no sourceId Ability: " + ability);
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
public void clear() {
|
||
for (ContinuousEffectsList effectsList : allEffectsLists) {
|
||
effectsList.clear();
|
||
}
|
||
temporaryEffects.clear();
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
}
|
||
temporaryEffects.clear();
|
||
}
|
||
|
||
public Map<String, String> getReplacementEffectsTexts(HashMap<ReplacementEffect, HashSet<Ability>> rEffects, Game game) {
|
||
Map<String, String> texts = new LinkedHashMap<>();
|
||
for (Map.Entry<ReplacementEffect, HashSet<Ability>> entry : rEffects.entrySet()) {
|
||
if (entry.getValue() != null) {
|
||
for (Ability ability : entry.getValue()) {
|
||
MageObject object = game.getObject(ability.getSourceId());
|
||
if (object != null) {
|
||
texts.put(ability.getId().toString() + '_' + entry.getKey().getId().toString(), object.getName() + ": " + ability.getRule(object.getName()));
|
||
} else {
|
||
texts.put(ability.getId().toString() + '_' + entry.getKey().getId().toString(), entry.getKey().getText(null));
|
||
}
|
||
}
|
||
} else {
|
||
if (!(entry.getKey() instanceof AuraReplacementEffect)
|
||
&& !(entry.getKey() instanceof PlaneswalkerRedirectionEffect)) {
|
||
logger.error("Replacement effect without ability: " + entry.getKey().toString());
|
||
}
|
||
}
|
||
}
|
||
return texts;
|
||
}
|
||
|
||
public boolean existRequirementEffects() {
|
||
return !requirementEffects.isEmpty();
|
||
}
|
||
|
||
public UUID getControllerOfSourceId(UUID sourceId) {
|
||
UUID controllerFound = null;
|
||
for (PreventionEffect effect : preventionEffects) {
|
||
HashSet<Ability> abilities = preventionEffects.getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
if (ability.getSourceId().equals(sourceId)) {
|
||
if (controllerFound == null || controllerFound.equals(ability.getControllerId())) {
|
||
controllerFound = ability.getControllerId();
|
||
} else {
|
||
// not unique controller - No solution yet
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
for (ReplacementEffect effect : replacementEffects) {
|
||
HashSet<Ability> abilities = replacementEffects.getAbility(effect.getId());
|
||
for (Ability ability : abilities) {
|
||
if (ability.getSourceId() != null) {
|
||
if (ability.getSourceId().equals(sourceId)) {
|
||
if (controllerFound == null || controllerFound.equals(ability.getControllerId())) {
|
||
controllerFound = ability.getControllerId();
|
||
} else {
|
||
// not unique controller - No solution yet
|
||
return null;
|
||
}
|
||
}
|
||
} else {
|
||
logger.warn("Ability without sourceId:" + ability.getRule());
|
||
}
|
||
}
|
||
}
|
||
return controllerFound;
|
||
}
|
||
}
|