Added the logic, that one continious effect in game state can be connected to multiple abilities. This fixes issue #198 and #196.

This commit is contained in:
LevelX2 2013-04-17 23:44:50 +02:00
parent e0bd0e3ffe
commit 1e5100e0d6
12 changed files with 402 additions and 206 deletions

View file

@ -550,7 +550,8 @@ public class HumanPlayer extends PlayerImpl<HumanPlayer> {
}
private void removeAttackerIfPossible(Game game, Permanent attacker) {
for (RequirementEffect effect : game.getContinuousEffects().getApplicableRequirementEffects(attacker, game)) {
for (Map.Entry entry : game.getContinuousEffects().getApplicableRequirementEffects(attacker, game).entrySet()) {
RequirementEffect effect = (RequirementEffect)entry.getKey();
if (effect.mustAttack(game)) {
return; // we can't cancel attacking
}

View file

@ -27,6 +27,7 @@
*/
package mage.sets.newphyrexia;
import java.util.Map;
import java.util.UUID;
import mage.Constants.CardType;
import mage.Constants.Duration;
@ -97,7 +98,7 @@ class UnwindingClockEffect extends ContinuousEffectImpl<UnwindingClockEffect> {
game.getState().setValue(source.getSourceId() + "applied", true);
for (Permanent artifact: game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(), game)) {
boolean untap = true;
for (RestrictionEffect effect : game.getContinuousEffects().getApplicableRestrictionEffects(artifact, game)) {
for (RestrictionEffect effect: game.getContinuousEffects().getApplicableRestrictionEffects(artifact, game).keySet()) {
untap &= effect.canBeUntapped(artifact, game);
}
if (untap) artifact.untap(game);

View file

@ -18,6 +18,12 @@ public class CastThroughTimeTest extends CardTestPlayerBase {
@Test
public void testCastWithRebound() {
addCard(Constants.Zone.BATTLEFIELD, playerA, "Mountain", 1);
/*
* Instant and sorcery spells you control have rebound. (Exile the spell as
* it resolves if you cast it from your hand. At the beginning of your next
* upkeep, you may cast that card from exile without paying its mana cost.)
*
*/
addCard(Constants.Zone.BATTLEFIELD, playerA, "Cast Through Time");
addCard(Constants.Zone.HAND, playerA, "Lightning Bolt");

View file

@ -66,8 +66,9 @@ public class ContinuousEffects implements Serializable {
private final AuraReplacementEffect auraReplacementEffect;
private List<ContinuousEffect> previous = new ArrayList<ContinuousEffect>();
private Map<Effect, UUID> sources = new HashMap<Effect, UUID>();
// effect.id -> sourceId - which effect was added by which sourceId
private Map<UUID, UUID> sources = new HashMap<UUID, UUID>();
public ContinuousEffects() {
applyCounters = new ApplyCountersEffect();
@ -87,7 +88,7 @@ public class ContinuousEffects implements Serializable {
restrictionEffects = effect.restrictionEffects.copy();
asThoughEffects = effect.asThoughEffects.copy();
costModificationEffects = effect.costModificationEffects.copy();
for (Map.Entry<Effect, UUID> entry : effect.sources.entrySet()) {
for (Map.Entry<UUID, UUID> entry : effect.sources.entrySet()) {
sources.put(entry.getKey(), entry.getValue());
}
collectAllEffects();
@ -116,29 +117,6 @@ public class ContinuousEffects implements Serializable {
return restrictionEffects;
}
public Ability getAbility(UUID effectId) {
Ability ability = layeredEffects.getAbility(effectId);
if (ability == null) {
ability = replacementEffects.getAbility(effectId);
}
if (ability == null) {
ability = preventionEffects.getAbility(effectId);
}
if (ability == null) {
ability = requirementEffects.getAbility(effectId);
}
if (ability == null) {
ability = restrictionEffects.getAbility(effectId);
}
if (ability == null) {
ability = asThoughEffects.getAbility(effectId);
}
if (ability == null) {
ability = costModificationEffects.getAbility(effectId);
}
return ability;
}
public void removeEndOfTurnEffects() {
layeredEffects.removeEndOfTurnEffects();
replacementEffects.removeEndOfTurnEffects();
@ -166,9 +144,12 @@ public class ContinuousEffects implements Serializable {
case WhileOnBattlefield:
case WhileOnStack:
case WhileInGraveyard:
Ability ability = layeredEffects.getAbility(effect.getId());
if (ability.isInUseableZone(game, null, false)) {
layerEffects.add(effect);
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
for (Ability ability: abilities) {
if (ability.isInUseableZone(game, null, false)) {
layerEffects.add(effect);
break;
}
}
break;
default:
@ -217,28 +198,40 @@ public class ContinuousEffects implements Serializable {
return layerEffects;
}
public List<RequirementEffect> getApplicableRequirementEffects(Permanent permanent, Game game) {
List<RequirementEffect> effects = new ArrayList<RequirementEffect>();
public HashMap<RequirementEffect, HashSet<Ability>> getApplicableRequirementEffects(Permanent permanent, Game game) {
HashMap<RequirementEffect, HashSet<Ability>> effects = new HashMap<RequirementEffect, HashSet<Ability>>();
for (RequirementEffect effect: requirementEffects) {
Ability ability = requirementEffects.getAbility(effect.getId());
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, false)) {
if (effect.applies(permanent, ability, game)) {
effects.add(effect);
HashSet<Ability> abilities = requirementEffects.getAbility(effect.getId());
HashSet<Ability> applicableAbilities = new HashSet<Ability>();
for (Ability ability : abilities) {
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, false)) {
if (effect.applies(permanent, ability, game)) {
applicableAbilities.add(ability);
}
}
}
if (!applicableAbilities.isEmpty()) {
effects.put(effect, abilities);
}
}
return effects;
}
public List<RestrictionEffect> getApplicableRestrictionEffects(Permanent permanent, Game game) {
List<RestrictionEffect> effects = new ArrayList<RestrictionEffect>();
public HashMap<RestrictionEffect, HashSet<Ability>> getApplicableRestrictionEffects(Permanent permanent, Game game) {
HashMap<RestrictionEffect, HashSet<Ability>> effects = new HashMap<RestrictionEffect, HashSet<Ability>>();
for (RestrictionEffect effect: restrictionEffects) {
Ability ability = restrictionEffects.getAbility(effect.getId());
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, permanent, false)) {
if (effect.applies(permanent, ability, game)) {
effects.add(effect);
HashSet<Ability> abilities = restrictionEffects.getAbility(effect.getId());
HashSet<Ability> applicableAbilities = new HashSet<Ability>();
for (Ability ability : abilities) {
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, permanent, false)) {
if (effect.applies(permanent, ability, game)) {
applicableAbilities.add(ability);
}
}
}
if (!applicableAbilities.isEmpty()) {
effects.put(effect, abilities);
}
}
return effects;
}
@ -249,36 +242,49 @@ public class ContinuousEffects implements Serializable {
* @param game
* @return a list of all {@link ReplacementEffect} that apply to the current event
*/
private List<ReplacementEffect> getApplicableReplacementEffects(GameEvent event, Game game) {
List<ReplacementEffect> replaceEffects = new ArrayList<ReplacementEffect>();
private HashMap<ReplacementEffect, HashSet<Ability>> getApplicableReplacementEffects(GameEvent event, Game game) {
// List<ReplacementEffect> replaceEffects = new ArrayList<ReplacementEffect>();
HashMap<ReplacementEffect, HashSet<Ability>> replaceEffects = new HashMap<ReplacementEffect, HashSet<Ability>>();
if (planeswalkerRedirectionEffect.applies(event, null, game)) {
replaceEffects.add(planeswalkerRedirectionEffect);
replaceEffects.put(planeswalkerRedirectionEffect, null);
}
if(auraReplacementEffect.applies(event, null, game)){
replaceEffects.add(auraReplacementEffect);
replaceEffects.put(auraReplacementEffect, null);
}
//get all applicable transient Replacement effects
for (ReplacementEffect effect: replacementEffects) {
Ability ability = replacementEffects.getAbility(effect.getId());
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, false)) {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
if (!game.getScopeRelevant() || effect.hasSelfScope() || !event.getTargetId().equals(this.replacementEffects.getAbility(effect.getId()).getSourceId())) {
if (effect.applies(event, ability, game)) {
replaceEffects.add(effect);
HashSet<Ability> abilities = replacementEffects.getAbility(effect.getId());
HashSet<Ability> applicableAbilities = new HashSet<Ability>();
for (Ability ability : abilities) {
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 (!applicableAbilities.isEmpty()) {
replaceEffects.put((ReplacementEffect) effect, applicableAbilities);
}
}
for (PreventionEffect effect: preventionEffects) {
Ability ability = preventionEffects.getAbility(effect.getId());
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, false)) {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
if (effect.applies(event, ability, game)) {
replaceEffects.add(effect);
HashSet<Ability> abilities = preventionEffects.getAbility(effect.getId());
HashSet<Ability> applicableAbilities = new HashSet<Ability>();
for (Ability ability : abilities) {
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, false)) {
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;
}
@ -293,10 +299,13 @@ public class ContinuousEffects implements Serializable {
List<CostModificationEffect> costEffects = new ArrayList<CostModificationEffect>();
for (CostModificationEffect effect: costModificationEffects) {
Ability ability = costModificationEffects.getAbility(effect.getId());
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, false)) {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
costEffects.add(effect);
HashSet<Ability> abilities = costModificationEffects.getAbility(effect.getId());
for (Ability ability : abilities) {
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, false)) {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
costEffects.add(effect);
break;
}
}
}
}
@ -309,8 +318,11 @@ public class ContinuousEffects implements Serializable {
for (AsThoughEffect effect: asThoughEffectsList) {
if (effect.getAsThoughEffectType() == type) {
if (effect.applies(objectId, asThoughEffects.getAbility(effect.getId()), game)) {
return true;
HashSet<Ability> abilities = asThoughEffects.getAbility(effect.getId());
for (Ability ability : abilities) {
if (effect.applies(objectId, ability, game)) {
return true;
}
}
}
}
@ -327,10 +339,13 @@ public class ContinuousEffects implements Serializable {
List<AsThoughEffect> asThoughEffectsList = new ArrayList<AsThoughEffect>();
for (AsThoughEffect effect: asThoughEffects) {
Ability ability = asThoughEffects.getAbility(effect.getId());
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, false)) {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
asThoughEffectsList.add(effect);
HashSet<Ability> abilities = asThoughEffects.getAbility(effect.getId());
for (Ability ability : abilities) {
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, false)) {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
asThoughEffectsList.add(effect);
break;
}
}
}
}
@ -350,41 +365,115 @@ public class ContinuousEffects implements Serializable {
List<CostModificationEffect> costEffects = getApplicableCostModificationEffects(game);
for ( CostModificationEffect effect : costEffects) {
if ( effect.applies(abilityToModify, costModificationEffects.getAbility(effect.getId()), game) ) {
effect.apply(game, costModificationEffects.getAbility(effect.getId()), abilityToModify);
HashSet<Ability> abilities = costModificationEffects.getAbility(effect.getId());
for (Ability ability : abilities) {
if ( effect.applies(abilityToModify, ability, game) ) {
effect.apply(game, ability, abilityToModify);
}
}
}
}
public boolean replaceEvent(GameEvent event, Game game) {
boolean caught = false;
List<UUID> consumed = new ArrayList<UUID>();
HashMap<UUID, HashSet<UUID>> consumed = new HashMap<UUID, HashSet<UUID>>();
do {
List<ReplacementEffect> rEffects = getApplicableReplacementEffects(event, game);
for (Iterator<ReplacementEffect> i = rEffects.iterator(); i.hasNext();) {
ReplacementEffect entry = i.next();
if (consumed.contains(entry.getId())) {
i.remove();
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 (consumedAbilitiesIds.size() == ((HashSet<Ability>) rEffects.get(entry)).size()) {
it1.remove();
} else {
Iterator it = ((HashSet<Ability>) 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) {
index = 0;
ReplacementEffect effect = (ReplacementEffect) rEffects.keySet().iterator().next();
HashSet<Ability> abilities = replacementEffects.getAbility(effect.getId());
if (abilities == null || abilities.size() == 1) {
onlyOne = true;
}
}
else {
if (onlyOne) {
index = 0;
} else {
//20100716 - 616.1c
Player player = game.getPlayer(event.getPlayerId());
index = player.chooseEffect(getReplacementEffectsTexts(rEffects, game), game);
}
ReplacementEffect rEffect = rEffects.get(index);
caught = rEffect.replaceEvent(event, this.getAbility(rEffect.getId()), game);
if (caught) {
// get the selected effect
int checked = 0;
ReplacementEffect rEffect = null;
Ability rAbility = null;
for (Map.Entry entry : rEffects.entrySet()) {
if (entry.getValue() == null) {
if (checked == index) {
rEffect = (ReplacementEffect) entry.getKey();
break;
} else {
checked++;
}
} else {
HashSet<Ability> abilities = (HashSet<Ability>) entry.getValue();
int size = abilities.size();
if (index > (checked + size - 1)) {
checked += size;
} else {
rEffect = (ReplacementEffect) entry.getKey();
Iterator it = abilities.iterator();
while (it.hasNext() && rAbility == null) {
if (checked == index) {
rAbility = (Ability) it.next();
} else {
it.next();
checked++;
}
}
}
}
}
if (rEffect != null) {
caught = rEffect.replaceEvent(event, rAbility, game);
}
if (caught) { // Event was completely replaced -> stop applying effects to it
break;
}
consumed.add(rEffect.getId());
// add used effect to consumed effects
if (rEffect != null) {
if (consumed.containsKey(rEffect.getId())) {
HashSet<UUID> set = consumed.get(rEffect.getId());
if (rAbility != null) {
if (!set.contains(rAbility.getId())) {
set.add(rAbility.getId());
}
}
} else {
HashSet<UUID> set = new HashSet<UUID>();
if (rAbility != null) { // in case of AuraReplacementEffect or PlaneswalkerReplacementEffect there is no Ability
set.add(rAbility.getId());
}
consumed.put(rEffect.getId(), set);
}
}
game.applyEffects();
} while (true);
return caught;
@ -396,7 +485,10 @@ public class ContinuousEffects implements Serializable {
List<ContinuousEffect> layerEffects = getLayeredEffects(game);
List<ContinuousEffect> layer = filterLayeredEffects(layerEffects, Layer.CopyEffects_1);
for (ContinuousEffect effect: layer) {
effect.apply(Layer.CopyEffects_1, SubLayer.NA, layeredEffects.getAbility(effect.getId()), game);
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.size()>0) {
@ -405,51 +497,81 @@ public class ContinuousEffects implements Serializable {
layer = filterLayeredEffects(layerEffects, Layer.ControlChangingEffects_2);
for (ContinuousEffect effect: layer) {
effect.apply(Layer.ControlChangingEffects_2, SubLayer.NA, layeredEffects.getAbility(effect.getId()), game);
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
for (Ability ability : abilities) {
effect.apply(Layer.ControlChangingEffects_2, SubLayer.NA, ability, game);
}
}
layer = filterLayeredEffects(layerEffects, Layer.TextChangingEffects_3);
for (ContinuousEffect effect: layer) {
effect.apply(Layer.TextChangingEffects_3, SubLayer.NA, layeredEffects.getAbility(effect.getId()), game);
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
for (Ability ability : abilities) {
effect.apply(Layer.TextChangingEffects_3, SubLayer.NA, ability, game);
}
}
layer = filterLayeredEffects(layerEffects, Layer.TypeChangingEffects_4);
for (ContinuousEffect effect: layer) {
effect.apply(Layer.TypeChangingEffects_4, SubLayer.NA, layeredEffects.getAbility(effect.getId()), game);
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
for (Ability ability : abilities) {
effect.apply(Layer.TypeChangingEffects_4, SubLayer.NA, ability, game);
}
}
layer = filterLayeredEffects(layerEffects, Layer.ColorChangingEffects_5);
for (ContinuousEffect effect: layer) {
effect.apply(Layer.ColorChangingEffects_5, SubLayer.NA, layeredEffects.getAbility(effect.getId()), game);
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
for (Ability ability : abilities) {
effect.apply(Layer.ColorChangingEffects_5, SubLayer.NA, ability, game);
}
}
layer = filterLayeredEffects(layerEffects, Layer.AbilityAddingRemovingEffects_6);
for (ContinuousEffect effect: layer) {
if (layerEffects.contains(effect)) {
effect.apply(Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, layeredEffects.getAbility(effect.getId()), game);
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
for (Ability ability : abilities) {
effect.apply(Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, ability, game);
}
}
layerEffects = getLayeredEffects(game);
}
layer = filterLayeredEffects(layerEffects, Layer.PTChangingEffects_7);
for (ContinuousEffect effect: layer) {
effect.apply(Layer.PTChangingEffects_7, SubLayer.SetPT_7b, layeredEffects.getAbility(effect.getId()), game);
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) {
effect.apply(Layer.PTChangingEffects_7, SubLayer.ModifyPT_7c, layeredEffects.getAbility(effect.getId()), game);
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) {
effect.apply(Layer.PTChangingEffects_7, SubLayer.SwitchPT_e, layeredEffects.getAbility(effect.getId()), game);
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
for (Ability ability : abilities) {
effect.apply(Layer.PTChangingEffects_7, SubLayer.SwitchPT_e, ability, game);
}
}
layer = filterLayeredEffects(layerEffects, Layer.PlayerEffects);
for (ContinuousEffect effect: layer) {
effect.apply(Layer.PlayerEffects, SubLayer.NA, layeredEffects.getAbility(effect.getId()), game);
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
for (Ability ability : abilities) {
effect.apply(Layer.PlayerEffects, SubLayer.NA, ability, game);
}
}
layer = filterLayeredEffects(layerEffects, Layer.RulesEffects);
for (ContinuousEffect effect: layer) {
effect.apply(Layer.RulesEffects, SubLayer.NA, layeredEffects.getAbility(effect.getId()), game);
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
for (Ability ability : abilities) {
effect.apply(Layer.RulesEffects, SubLayer.NA, ability, game);
}
}
}
public void addEffect(ContinuousEffect effect, UUID sourceId, Ability source) {
addEffect(effect, source);
sources.put(effect, sourceId);
sources.put(effect.getId(), sourceId);
}
public void addEffect(ContinuousEffect effect, Ability source) {
@ -493,9 +615,11 @@ public class ContinuousEffects implements Serializable {
private void setControllerForEffect(ContinuousEffectsList<?> effects, UUID cardId, UUID controllerId) {
for (Effect effect : effects) {
Ability ability = effects.getAbility(effect.getId());
if (ability.getSourceId().equals(cardId)) {
ability.setControllerId(controllerId);
HashSet<Ability> abilities = effects.getAbility(effect.getId());
for (Ability ability : abilities) {
if (ability.getSourceId().equals(cardId)) {
ability.setControllerId(controllerId);
}
}
}
}
@ -507,19 +631,6 @@ public class ContinuousEffects implements Serializable {
sources.clear();
}
/**
* Removes all granted effects
*/
public void removeGainedEffects() {
for (Map.Entry<Effect, UUID> source : sources.entrySet()) {
for (ContinuousEffectsList effectsList : allEffectsLists) {
Ability ability = effectsList.getAbility(source.getKey().getId());
if (ability != null && !ability.getSourceId().equals(source.getValue())) {
effectsList.removeEffect((ContinuousEffect) source.getKey());
}
}
}
}
/**
* Removes effects granted by sourceId
@ -528,30 +639,40 @@ public class ContinuousEffects implements Serializable {
*/
public List<Effect> removeGainedEffectsForSource(UUID sourceId) {
List<Effect> effects = new ArrayList<Effect>();
for (Map.Entry<Effect, UUID> source : sources.entrySet()) {
Set<UUID> effectsToRemove = new HashSet<UUID>();
for (Map.Entry<UUID, UUID> source : sources.entrySet()) {
if (sourceId.equals(source.getValue())) {
for (ContinuousEffectsList effectsList : allEffectsLists) {
Ability ability = effectsList.getAbility(source.getKey().getId());
if (ability != null && !ability.getSourceId().equals(source.getValue())) {
effectsList.removeEffect((ContinuousEffect) source.getKey());
effects.add(source.getKey());
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();
}
}
}
}
}
for (UUID effectId: effectsToRemove) {
sources.remove(effectId);
}
return effects;
}
public List<String> getReplacementEffectsTexts(List<ReplacementEffect> rEffects, Game game) {
public List<String> getReplacementEffectsTexts(HashMap<ReplacementEffect, HashSet<Ability>> rEffects, Game game) {
List<String> texts = new ArrayList<String>();
for (ReplacementEffect effect: rEffects) {
Ability ability = replacementEffects.getAbility(effect.getId());
MageObject object = game.getObject(ability.getSourceId());
if (object != null) {
texts.add(ability.getRule(object.getName()));
} else {
texts.add(effect.getText(null));
for (Map.Entry entry : rEffects.entrySet()) {
for (Ability ability :(HashSet<Ability>) entry.getValue()) {
MageObject object = game.getObject(ability.getSourceId());
if (object != null) {
texts.add(ability.getRule(object.getName()));
} else {
texts.add(((ReplacementEffect)entry.getKey()).getText(null));
}
}
}
return texts;
}

View file

@ -32,6 +32,9 @@ import mage.abilities.Ability;
import mage.game.Game;
import java.util.*;
import static mage.Constants.Duration.Custom;
import static mage.Constants.Duration.OneUse;
import static mage.Constants.Duration.WhileOnBattlefield;
/**
*
@ -39,7 +42,8 @@ import java.util.*;
*/
public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList<T> {
private final Map<UUID, Ability> abilityMap = new HashMap<UUID, Ability>();
// the effectAbilityMap holds for each effect all abilities that are connected (used) with this effect
private final Map<UUID, HashSet<Ability>> effectAbilityMap = new HashMap<UUID, HashSet<Ability>>();
public ContinuousEffectsList() { }
@ -48,8 +52,12 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
for (ContinuousEffect cost: effects) {
this.add((T)cost.copy());
}
for (Map.Entry<UUID, Ability> entry: effects.abilityMap.entrySet()) {
abilityMap.put(entry.getKey(), entry.getValue().copy());
for (Map.Entry<UUID, HashSet<Ability>> entry: effects.effectAbilityMap.entrySet()) {
HashSet<Ability> newSet = new HashSet<Ability>();
for (Ability ability :(HashSet<Ability>)entry.getValue()) {
newSet.add(ability.copy());
}
effectAbilityMap.put(entry.getKey(), newSet);
}
}
@ -62,7 +70,7 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
T entry = i.next();
if (entry.getDuration() == Constants.Duration.EndOfTurn) {
i.remove();
abilityMap.remove(entry.getId());
effectAbilityMap.remove(entry.getId());
}
}
}
@ -72,58 +80,100 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
T entry = i.next();
if (isInactive(entry, game)) {
i.remove();
abilityMap.remove(entry.getId());
effectAbilityMap.remove(entry.getId());
}
}
}
private boolean isInactive(T effect, Game game) {
Ability ability = abilityMap.get(effect.getId());
if (ability == null)
return true;
if (effect.isDiscarded())
return true;
switch(effect.getDuration()) {
case WhileOnBattlefield:
if (game.getObject(ability.getSourceId()) == null) {//TODO: does this really works?? object is returned across the game
return (true); // LevelX2: I guess it's not used, because effects stay the whole game
HashSet<Ability> set = effectAbilityMap.get(effect.getId());
Iterator it = set.iterator();
while (it.hasNext()) {
Ability ability = (Ability)it.next();
if (ability == null) {
it.remove();
} else if (effect.isDiscarded()) {
it.remove();
} 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();
}
case OneUse:
if (effect.isUsed()) {
it.remove();
}
case Custom:
if (effect.isInactive(ability , game)) {
it.remove();
}
}
case OneUse:
return effect.isUsed();
case Custom:
return effect.isInactive(abilityMap.get(effect.getId()), game);
}
}
return false;
return set.isEmpty();
}
/**
* Adds an effect and its connected ability to the list.
* For each effect will be stored, which abilities are connected to the effect.
* So an effect can be connected to multiple abilities.
*
* @param effect - effect to add
* @param source - connected ability
*/
public void addEffect(T effect, Ability source) {
if (abilityMap.containsKey(effect.getId()))
if (effectAbilityMap.containsKey(effect.getId())) {
HashSet<Ability> set = effectAbilityMap.get(effect.getId());
for (Ability ability: set) {
if (ability.getId().equals(source.getId()) && ability.getSourceId().equals(source.getSourceId()) ) {
return;
}
}
set.add(source);
return;
}
HashSet<Ability> set = new HashSet<Ability>();
set.add(source);
this.effectAbilityMap.put(effect.getId(), set);
this.add(effect);
this.abilityMap.put(effect.getId(), source);
}
public Ability getAbility(UUID effectId) {
return abilityMap.get(effectId);
public HashSet<Ability> getAbility(UUID effectId) {
return effectAbilityMap.get(effectId);
}
public void removeEffect(T effect) {
/**
* 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)) {
i.remove();
if (abilityMap.containsKey(effect.getId())) {
abilityMap.remove(effect.getId());
HashSet<Ability> abilities = effectAbilityMap.get(effect.getId());
if (!abilities.isEmpty()) {
abilities.remove(ability);
}
if (abilities.isEmpty()) {
i.remove();
}
}
}
}
public void removeEffectAbilityMap(UUID effectId) {
effectAbilityMap.remove(effectId);
}
@Override
public void clear() {
super.clear();
abilityMap.clear();
effectAbilityMap.clear();
}
}

View file

@ -1642,7 +1642,7 @@ public abstract class GameImpl<T extends GameImpl<T>> implements Game, Serializa
}
getContinuousEffects().removeGainedEffectsForSource(sourceId);
// remove gained triggered abilities
getState().resetForSourceId(sourceId);
getState().resetTriggersForSourceId(sourceId);
}
@Override

View file

@ -567,7 +567,7 @@ public class GameState implements Serializable, Copyable<GameState> {
*
* @param sourceId
*/
public void resetForSourceId(UUID sourceId) {
public void resetTriggersForSourceId(UUID sourceId) {
List<String> keysToRemove = triggers.removeGainedAbilitiesForSource(sourceId);
for (String key : keysToRemove) {
triggers.remove(key);

View file

@ -31,6 +31,7 @@ package mage.game.combat;
import java.io.Serializable;
import java.util.*;
import mage.Constants.Outcome;
import mage.abilities.Ability;
import mage.abilities.effects.RequirementEffect;
import mage.abilities.keyword.CantAttackAloneAbility;
import mage.abilities.keyword.VigilanceAbility;
@ -176,21 +177,24 @@ public class Combat implements Serializable, Copyable<Combat> {
protected void checkAttackRequirements(Player player, Game game) {
//20101001 - 508.1d
for (Permanent creature : player.getAvailableAttackers(game)) {
for (RequirementEffect effect : game.getContinuousEffects().getApplicableRequirementEffects(creature, game)) {
for (Map.Entry entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) {
RequirementEffect effect = (RequirementEffect)entry.getKey();
if (effect.mustAttack(game)) {
UUID defenderId = effect.mustAttackDefender(game.getContinuousEffects().getAbility(effect.getId()), game);
if (defenderId == null) {
if (defenders.size() == 1) {
player.declareAttacker(creature.getId(), defenders.iterator().next(), game);
} else {
TargetDefender target = new TargetDefender(defenders, creature.getId());
target.setRequired(true);
if (player.chooseTarget(Outcome.Damage, target, null, game)) {
player.declareAttacker(creature.getId(), target.getFirstTarget(), game);
for (Ability ability: (HashSet<Ability>)entry.getValue()) {
UUID defenderId = effect.mustAttackDefender(ability, game);
if (defenderId == null) {
if (defenders.size() == 1) {
player.declareAttacker(creature.getId(), defenders.iterator().next(), game);
} else {
TargetDefender target = new TargetDefender(defenders, creature.getId());
target.setRequired(true);
if (player.chooseTarget(Outcome.Damage, target, null, game)) {
player.declareAttacker(creature.getId(), target.getFirstTarget(), game);
}
}
} else {
player.declareAttacker(creature.getId(), defenderId, game);
}
} else {
player.declareAttacker(creature.getId(), defenderId, game);
}
}
}
@ -269,12 +273,15 @@ public class Combat implements Serializable, Copyable<Combat> {
//20101001 - 509.1c
for (Permanent creature : game.getBattlefield().getActivePermanents(filterBlockers, player.getId(), game)) {
if (game.getOpponents(attackerId).contains(creature.getControllerId())) {
for (RequirementEffect effect : game.getContinuousEffects().getApplicableRequirementEffects(creature, game)) {
for (Map.Entry entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) {
RequirementEffect effect = (RequirementEffect)entry.getKey();
if (effect.mustBlock(game)) {
UUID attackId = effect.mustBlockAttacker(game.getContinuousEffects().getAbility(effect.getId()), game);
Player defender = game.getPlayer(creature.getControllerId());
if (attackId != null && defender != null) {
defender.declareBlocker(creature.getId(), attackId, game);
for (Ability ability: (HashSet<Ability>)entry.getValue()) {
UUID attackId = effect.mustBlockAttacker(ability, game);
Player defender = game.getPlayer(creature.getControllerId());
if (attackId != null && defender != null) {
defender.declareBlocker(creature.getId(), attackId, game);
}
}
}
}
@ -286,7 +293,7 @@ public class Combat implements Serializable, Copyable<Combat> {
//20101001 - 509.1c
for (Permanent creature : game.getBattlefield().getActivePermanents(new FilterControlledCreaturePermanent(), player.getId(), game)) {
if (creature.getBlocking() == 0 && game.getOpponents(attackerId).contains(creature.getControllerId())) {
for (RequirementEffect effect : game.getContinuousEffects().getApplicableRequirementEffects(creature, game)) {
for (RequirementEffect effect : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).keySet()) {
if (effect.mustBlockAny(game)) {
// check that it can block an attacker
boolean mayBlock = false;

View file

@ -79,6 +79,8 @@ public class PermanentCard extends PermanentImpl<PermanentCard> {
public void reset(Game game) {
// when the permanent is reset copy all original values from the card
// must copy card each reset so that the original values don't get modified
// game.getState().getContinuousEffects().removeGainedEffectsForSource(objectId);
// game.getState().resetTriggersForSourceId(this.getId());
copyFromCard(card, game);
super.reset(game);
}
@ -126,6 +128,7 @@ public class PermanentCard extends PermanentImpl<PermanentCard> {
protected void copyFromCard(Card card, Game game) {
this.name = card.getName();
// this.removeAllAbilities(objectId, game);
this.abilities.clear();
this.abilities.addAll(card.getAbilities());
this.abilities.setControllerId(this.controllerId);

View file

@ -187,7 +187,6 @@ public abstract class PermanentImpl<T extends PermanentImpl<T>> extends CardImpl
public void addAbility(Ability ability, UUID sourceId, Game game) {
if (!abilities.containsKey(ability.getId())) {
Ability copyAbility = ability.copy();
copyAbility.newId(); // needed e.g. if a gainAll ability gives Forestwalk to multiple creatures
copyAbility.setControllerId(controllerId);
copyAbility.setSourceId(objectId);
game.getState().addAbility(copyAbility, sourceId, this);
@ -201,7 +200,7 @@ public abstract class PermanentImpl<T extends PermanentImpl<T>> extends CardImpl
// removes abilities that were gained from abilities of this permanent
game.getContinuousEffects().removeGainedEffectsForSource(this.getId());
// remove gained triggered abilities
game.getState().resetForSourceId(this.getId());
game.getState().resetTriggersForSourceId(this.getId());
}
@Override
@ -836,7 +835,7 @@ public abstract class PermanentImpl<T extends PermanentImpl<T>> extends CardImpl
return false;
}
//20101001 - 508.1c
for (RestrictionEffect effect : game.getContinuousEffects().getApplicableRestrictionEffects(this, game)) {
for (RestrictionEffect effect: game.getContinuousEffects().getApplicableRestrictionEffects(this, game).keySet()) {
if (!effect.canAttack(game)) {
return false;
}
@ -854,19 +853,24 @@ public abstract class PermanentImpl<T extends PermanentImpl<T>> extends CardImpl
}
Permanent attacker = game.getPermanent(attackerId);
//20101001 - 509.1b
for (RestrictionEffect effect : game.getContinuousEffects().getApplicableRestrictionEffects(this, game)) {
if (!effect.canBlock(attacker, this, game.getContinuousEffects().getAbility(effect.getId()), game)) {
return false;
for (Map.Entry entry: game.getContinuousEffects().getApplicableRestrictionEffects(this, game).entrySet()) {
RestrictionEffect effect = (RestrictionEffect)entry.getKey();
for (Ability ability : (HashSet<Ability>) entry.getValue()) {
if (!effect.canBlock(attacker, this, ability, game)) {
return false;
}
}
}
// check also attacker's restriction effects
for (RestrictionEffect effect : game.getContinuousEffects().getApplicableRestrictionEffects(attacker, game)) {
/*if (!effect.canBlock(attacker, this, game))
return false;*/
if (!effect.canBeBlocked(attacker, this, game.getContinuousEffects().getAbility(effect.getId()), game)) {
return false;
for (Map.Entry entry: game.getContinuousEffects().getApplicableRestrictionEffects(attacker, game).entrySet()) {
RestrictionEffect effect = (RestrictionEffect)entry.getKey();
for (Ability ability : (HashSet<Ability>) entry.getValue()) {
if (!effect.canBeBlocked(attacker, this, ability, game)) {
return false;
}
}
}
if (attacker != null && attacker.hasProtectionFrom(this, game)) {
return false;
}
@ -880,9 +884,12 @@ public abstract class PermanentImpl<T extends PermanentImpl<T>> extends CardImpl
}
//20101001 - 509.1b
for (RestrictionEffect effect : game.getContinuousEffects().getApplicableRestrictionEffects(this, game)) {
if (!effect.canBlock(null, this, game.getContinuousEffects().getAbility(effect.getId()), game)) {
return false;
for (Map.Entry entry: game.getContinuousEffects().getApplicableRestrictionEffects(this, game).entrySet()) {
RestrictionEffect effect = (RestrictionEffect)entry.getKey();
for (Ability ability : (HashSet<Ability>) entry.getValue()) {
if (!effect.canBlock(null, this, ability, game)) {
return false;
}
}
}

View file

@ -801,7 +801,7 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser
//20091005 - 502.2
for (Permanent permanent: game.getBattlefield().getAllActivePermanents(playerId)) {
boolean untap = true;
for (RestrictionEffect effect : game.getContinuousEffects().getApplicableRestrictionEffects(permanent, game)) {
for (RestrictionEffect effect: game.getContinuousEffects().getApplicableRestrictionEffects(permanent, game).keySet()) {
untap &= effect.canBeUntapped(permanent, game);
}
if (untap) {
@ -944,7 +944,7 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser
Permanent source = game.getPermanent(sourceId);
if(source == null){
MageObject lastKnownInformation = game.getLastKnownInformation(sourceId, Zone.BATTLEFIELD);
if(lastKnownInformation instanceof Permanent){
if(lastKnownInformation != null && lastKnownInformation instanceof Permanent){
source = (Permanent) lastKnownInformation;
}
}

View file

@ -130,25 +130,25 @@ public class TraceUtil {
}
private static void traceForPermanent(Game game, Permanent permanent, String uuid, ContinuousEffectsList<RestrictionEffect> restrictionEffects) {
Ability ability;
for (RestrictionEffect effect: restrictionEffects) {
ability = restrictionEffects.getAbility(effect.getId());
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, permanent, false)) {
log.error(uuid+" ability=" + ability + ", applies_to_attacker=" + effect.applies(permanent, ability, game));
} else {
boolean usable = ability.isInUseableZone(game, permanent, false);
log.error(uuid+" instanceof: " + (ability instanceof StaticAbility) + ", ability=" + ability);
log.error(uuid+" usable: " + usable + ", ability=" + ability);
if (!usable) {
Constants.Zone zone = ability.getZone();
log.error(uuid+" zone: " + zone);
MageObject object = game.getObject(ability.getSourceId());
log.error(uuid+" object: " + object);
if (object != null) {
log.error(uuid + " contains:" + object.getAbilities().contains(ability));
for (Ability ability : restrictionEffects.getAbility(effect.getId())) {
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, permanent, false)) {
log.error(uuid+" ability=" + ability + ", applies_to_attacker=" + effect.applies(permanent, ability, game));
} else {
boolean usable = ability.isInUseableZone(game, permanent, false);
log.error(uuid+" instanceof: " + (ability instanceof StaticAbility) + ", ability=" + ability);
log.error(uuid+" usable: " + usable + ", ability=" + ability);
if (!usable) {
Constants.Zone zone = ability.getZone();
log.error(uuid+" zone: " + zone);
MageObject object = game.getObject(ability.getSourceId());
log.error(uuid+" object: " + object);
if (object != null) {
log.error(uuid + " contains:" + object.getAbilities().contains(ability));
}
Constants.Zone test = game.getState().getZone(ability.getSourceId());
log.error(uuid+" test_zone: " + test);
}
Constants.Zone test = game.getState().getZone(ability.getSourceId());
log.error(uuid+" test_zone: " + test);
}
}
}