diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index c6745ff0b20..cb75b1e6139 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -550,7 +550,8 @@ public class HumanPlayer extends PlayerImpl { } 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 } diff --git a/Mage.Sets/src/mage/sets/newphyrexia/UnwindingClock.java b/Mage.Sets/src/mage/sets/newphyrexia/UnwindingClock.java index cf647cfb524..0923b016875 100644 --- a/Mage.Sets/src/mage/sets/newphyrexia/UnwindingClock.java +++ b/Mage.Sets/src/mage/sets/newphyrexia/UnwindingClock.java @@ -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 { 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); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/roe/CastThroughTimeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/roe/CastThroughTimeTest.java index 1dab8eac126..61ba865ed94 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/roe/CastThroughTimeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/roe/CastThroughTimeTest.java @@ -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"); diff --git a/Mage/src/mage/abilities/effects/ContinuousEffects.java b/Mage/src/mage/abilities/effects/ContinuousEffects.java index 8fc330de1bf..00ae523cff4 100644 --- a/Mage/src/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/mage/abilities/effects/ContinuousEffects.java @@ -66,8 +66,9 @@ public class ContinuousEffects implements Serializable { private final AuraReplacementEffect auraReplacementEffect; private List previous = new ArrayList(); - - private Map sources = new HashMap(); + + // effect.id -> sourceId - which effect was added by which sourceId + private Map sources = new HashMap(); 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 entry : effect.sources.entrySet()) { + for (Map.Entry 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 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 getApplicableRequirementEffects(Permanent permanent, Game game) { - List effects = new ArrayList(); + public HashMap> getApplicableRequirementEffects(Permanent permanent, Game game) { + HashMap> effects = new HashMap>(); 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 abilities = requirementEffects.getAbility(effect.getId()); + HashSet applicableAbilities = new HashSet(); + 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 getApplicableRestrictionEffects(Permanent permanent, Game game) { - List effects = new ArrayList(); + public HashMap> getApplicableRestrictionEffects(Permanent permanent, Game game) { + HashMap> effects = new HashMap>(); 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 abilities = restrictionEffects.getAbility(effect.getId()); + HashSet applicableAbilities = new HashSet(); + 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 getApplicableReplacementEffects(GameEvent event, Game game) { - List replaceEffects = new ArrayList(); + private HashMap> getApplicableReplacementEffects(GameEvent event, Game game) { + // List replaceEffects = new ArrayList(); + HashMap> replaceEffects = new HashMap>(); 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 abilities = replacementEffects.getAbility(effect.getId()); + HashSet applicableAbilities = new HashSet(); + 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 abilities = preventionEffects.getAbility(effect.getId()); + HashSet applicableAbilities = new HashSet(); + 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 costEffects = new ArrayList(); 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 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 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 asThoughEffectsList = new ArrayList(); 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 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 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 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 consumed = new ArrayList(); + HashMap> consumed = new HashMap>(); do { - List rEffects = getApplicableReplacementEffects(event, game); - for (Iterator i = rEffects.iterator(); i.hasNext();) { - ReplacementEffect entry = i.next(); - if (consumed.contains(entry.getId())) { - i.remove(); + HashMap> rEffects = getApplicableReplacementEffects(event, game); + // Remove all consumed effects (ability dependant) + for (Iterator it1 = rEffects.keySet().iterator(); it1.hasNext();){ + ReplacementEffect entry = it1.next(); + if (consumed.containsKey(entry.getId())) { + HashSet consumedAbilitiesIds = consumed.get(entry.getId()); + if (consumedAbilitiesIds.size() == ((HashSet) rEffects.get(entry)).size()) { + it1.remove(); + } else { + Iterator it = ((HashSet) 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 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 abilities = (HashSet) 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 set = consumed.get(rEffect.getId()); + if (rAbility != null) { + if (!set.contains(rAbility.getId())) { + set.add(rAbility.getId()); + } + } + } else { + HashSet 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); + } + } + game.applyEffects(); } while (true); return caught; @@ -396,7 +485,10 @@ public class ContinuousEffects implements Serializable { List layerEffects = getLayeredEffects(game); List layer = filterLayeredEffects(layerEffects, Layer.CopyEffects_1); for (ContinuousEffect effect: layer) { - effect.apply(Layer.CopyEffects_1, SubLayer.NA, layeredEffects.getAbility(effect.getId()), game); + HashSet 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 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 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 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 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 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 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 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 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 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 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 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 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 removeGainedEffectsForSource(UUID sourceId) { List effects = new ArrayList(); - for (Map.Entry source : sources.entrySet()) { + Set effectsToRemove = new HashSet(); + for (Map.Entry 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 getReplacementEffectsTexts(List rEffects, Game game) { + public List getReplacementEffectsTexts(HashMap> rEffects, Game game) { List texts = new ArrayList(); - 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) 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; } diff --git a/Mage/src/mage/abilities/effects/ContinuousEffectsList.java b/Mage/src/mage/abilities/effects/ContinuousEffectsList.java index 79df3bf2087..943bdae543d 100644 --- a/Mage/src/mage/abilities/effects/ContinuousEffectsList.java +++ b/Mage/src/mage/abilities/effects/ContinuousEffectsList.java @@ -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 extends ArrayList { - private final Map abilityMap = new HashMap(); + // the effectAbilityMap holds for each effect all abilities that are connected (used) with this effect + private final Map> effectAbilityMap = new HashMap>(); public ContinuousEffectsList() { } @@ -48,8 +52,12 @@ public class ContinuousEffectsList extends ArrayList for (ContinuousEffect cost: effects) { this.add((T)cost.copy()); } - for (Map.Entry entry: effects.abilityMap.entrySet()) { - abilityMap.put(entry.getKey(), entry.getValue().copy()); + for (Map.Entry> entry: effects.effectAbilityMap.entrySet()) { + HashSet newSet = new HashSet(); + for (Ability ability :(HashSet)entry.getValue()) { + newSet.add(ability.copy()); + } + effectAbilityMap.put(entry.getKey(), newSet); } } @@ -62,7 +70,7 @@ public class ContinuousEffectsList 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 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 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 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 set = new HashSet(); + 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 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 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 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(); } } diff --git a/Mage/src/mage/game/GameImpl.java b/Mage/src/mage/game/GameImpl.java index 6014f2a5c3e..023b8c90165 100644 --- a/Mage/src/mage/game/GameImpl.java +++ b/Mage/src/mage/game/GameImpl.java @@ -1642,7 +1642,7 @@ public abstract class GameImpl> implements Game, Serializa } getContinuousEffects().removeGainedEffectsForSource(sourceId); // remove gained triggered abilities - getState().resetForSourceId(sourceId); + getState().resetTriggersForSourceId(sourceId); } @Override diff --git a/Mage/src/mage/game/GameState.java b/Mage/src/mage/game/GameState.java index 87787497786..9fb9c700159 100644 --- a/Mage/src/mage/game/GameState.java +++ b/Mage/src/mage/game/GameState.java @@ -567,7 +567,7 @@ public class GameState implements Serializable, Copyable { * * @param sourceId */ - public void resetForSourceId(UUID sourceId) { + public void resetTriggersForSourceId(UUID sourceId) { List keysToRemove = triggers.removeGainedAbilitiesForSource(sourceId); for (String key : keysToRemove) { triggers.remove(key); diff --git a/Mage/src/mage/game/combat/Combat.java b/Mage/src/mage/game/combat/Combat.java index 456f2e90b79..11aafb2121c 100644 --- a/Mage/src/mage/game/combat/Combat.java +++ b/Mage/src/mage/game/combat/Combat.java @@ -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 { 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)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 { //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)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 { //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; diff --git a/Mage/src/mage/game/permanent/PermanentCard.java b/Mage/src/mage/game/permanent/PermanentCard.java index 08ce565d4e1..aafe9e315fd 100644 --- a/Mage/src/mage/game/permanent/PermanentCard.java +++ b/Mage/src/mage/game/permanent/PermanentCard.java @@ -79,6 +79,8 @@ public class PermanentCard extends PermanentImpl { 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 { 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); diff --git a/Mage/src/mage/game/permanent/PermanentImpl.java b/Mage/src/mage/game/permanent/PermanentImpl.java index 28533a32319..1a4dbf5be4a 100644 --- a/Mage/src/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/mage/game/permanent/PermanentImpl.java @@ -187,7 +187,6 @@ public abstract class PermanentImpl> 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> 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> 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> 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) 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) 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> 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) entry.getValue()) { + if (!effect.canBlock(null, this, ability, game)) { + return false; + } } } diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index f3cc9360813..3206c8e38fc 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -801,7 +801,7 @@ public abstract class PlayerImpl> 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> 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; } } diff --git a/Mage/src/mage/util/trace/TraceUtil.java b/Mage/src/mage/util/trace/TraceUtil.java index fbba70afe39..14874d109bd 100644 --- a/Mage/src/mage/util/trace/TraceUtil.java +++ b/Mage/src/mage/util/trace/TraceUtil.java @@ -130,25 +130,25 @@ public class TraceUtil { } private static void traceForPermanent(Game game, Permanent permanent, String uuid, ContinuousEffectsList 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); } } }