Added effect type and logic to handle multiple untap restriction effects.

This commit is contained in:
LevelX2 2013-08-10 13:06:32 +02:00
parent ae44981cfa
commit 0cf7ca3fca
5 changed files with 298 additions and 9 deletions

View file

@ -67,6 +67,7 @@ public class ContinuousEffects implements Serializable {
private ContinuousEffectsList<PreventionEffect> preventionEffects = new ContinuousEffectsList<PreventionEffect>(); private ContinuousEffectsList<PreventionEffect> preventionEffects = new ContinuousEffectsList<PreventionEffect>();
private ContinuousEffectsList<RequirementEffect> requirementEffects = new ContinuousEffectsList<RequirementEffect>(); private ContinuousEffectsList<RequirementEffect> requirementEffects = new ContinuousEffectsList<RequirementEffect>();
private ContinuousEffectsList<RestrictionEffect> restrictionEffects = new ContinuousEffectsList<RestrictionEffect>(); private ContinuousEffectsList<RestrictionEffect> restrictionEffects = new ContinuousEffectsList<RestrictionEffect>();
private ContinuousEffectsList<RestrictionUntapNotMoreThanEffect> restrictionUntapNotMoreThanEffects = new ContinuousEffectsList<RestrictionUntapNotMoreThanEffect>();
private ContinuousEffectsList<AsThoughEffect> asThoughEffects = new ContinuousEffectsList<AsThoughEffect>(); private ContinuousEffectsList<AsThoughEffect> asThoughEffects = new ContinuousEffectsList<AsThoughEffect>();
private ContinuousEffectsList<CostModificationEffect> costModificationEffects = new ContinuousEffectsList<CostModificationEffect>(); private ContinuousEffectsList<CostModificationEffect> costModificationEffects = new ContinuousEffectsList<CostModificationEffect>();
private ContinuousEffectsList<SpliceCardEffect> spliceCardEffects = new ContinuousEffectsList<SpliceCardEffect>(); private ContinuousEffectsList<SpliceCardEffect> spliceCardEffects = new ContinuousEffectsList<SpliceCardEffect>();
@ -98,6 +99,7 @@ public class ContinuousEffects implements Serializable {
preventionEffects = effect.preventionEffects.copy(); preventionEffects = effect.preventionEffects.copy();
requirementEffects = effect.requirementEffects.copy(); requirementEffects = effect.requirementEffects.copy();
restrictionEffects = effect.restrictionEffects.copy(); restrictionEffects = effect.restrictionEffects.copy();
restrictionUntapNotMoreThanEffects = effect.restrictionUntapNotMoreThanEffects.copy();
asThoughEffects = effect.asThoughEffects.copy(); asThoughEffects = effect.asThoughEffects.copy();
costModificationEffects = effect.costModificationEffects.copy(); costModificationEffects = effect.costModificationEffects.copy();
spliceCardEffects = effect.spliceCardEffects.copy(); spliceCardEffects = effect.spliceCardEffects.copy();
@ -114,6 +116,7 @@ public class ContinuousEffects implements Serializable {
allEffectsLists.add(preventionEffects); allEffectsLists.add(preventionEffects);
allEffectsLists.add(requirementEffects); allEffectsLists.add(requirementEffects);
allEffectsLists.add(restrictionEffects); allEffectsLists.add(restrictionEffects);
allEffectsLists.add(restrictionUntapNotMoreThanEffects);
allEffectsLists.add(asThoughEffects); allEffectsLists.add(asThoughEffects);
allEffectsLists.add(costModificationEffects); allEffectsLists.add(costModificationEffects);
allEffectsLists.add(spliceCardEffects); allEffectsLists.add(spliceCardEffects);
@ -159,6 +162,7 @@ public class ContinuousEffects implements Serializable {
preventionEffects.removeInactiveEffects(game); preventionEffects.removeInactiveEffects(game);
requirementEffects.removeInactiveEffects(game); requirementEffects.removeInactiveEffects(game);
restrictionEffects.removeInactiveEffects(game); restrictionEffects.removeInactiveEffects(game);
restrictionUntapNotMoreThanEffects.removeInactiveEffects(game);
asThoughEffects.removeInactiveEffects(game); asThoughEffects.removeInactiveEffects(game);
costModificationEffects.removeInactiveEffects(game); costModificationEffects.removeInactiveEffects(game);
spliceCardEffects.removeInactiveEffects(game); spliceCardEffects.removeInactiveEffects(game);
@ -251,7 +255,7 @@ public class ContinuousEffects implements Serializable {
HashSet<Ability> abilities = restrictionEffects.getAbility(effect.getId()); HashSet<Ability> abilities = restrictionEffects.getAbility(effect.getId());
HashSet<Ability> applicableAbilities = new HashSet<Ability>(); HashSet<Ability> applicableAbilities = new HashSet<Ability>();
for (Ability ability : abilities) { for (Ability ability : abilities) {
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, permanent, false)) { if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, false)) {
if (effect.applies(permanent, ability, game)) { if (effect.applies(permanent, ability, game)) {
applicableAbilities.add(ability); applicableAbilities.add(ability);
} }
@ -264,6 +268,25 @@ public class ContinuousEffects implements Serializable {
return effects; return effects;
} }
public HashMap<RestrictionUntapNotMoreThanEffect, HashSet<Ability>> getApplicableRestrictionUntapNotMoreThanEffects(Player player, Game game) {
HashMap<RestrictionUntapNotMoreThanEffect, HashSet<Ability>> effects = new HashMap<RestrictionUntapNotMoreThanEffect, HashSet<Ability>>();
for (RestrictionUntapNotMoreThanEffect effect: restrictionUntapNotMoreThanEffects) {
HashSet<Ability> abilities = restrictionUntapNotMoreThanEffects.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(player, ability, game)) {
applicableAbilities.add(ability);
}
}
}
if (!applicableAbilities.isEmpty()) {
effects.put(effect, abilities);
}
}
return effects;
}
/** /**
* *
* @param event * @param event
@ -708,6 +731,10 @@ public class ContinuousEffects implements Serializable {
RestrictionEffect newRestrictionEffect = (RestrictionEffect)effect; RestrictionEffect newRestrictionEffect = (RestrictionEffect)effect;
restrictionEffects.addEffect(newRestrictionEffect, source); restrictionEffects.addEffect(newRestrictionEffect, source);
break; break;
case RESTRICTION_UNTAP_NOT_MORE_THAN:
RestrictionUntapNotMoreThanEffect newRestrictionUntapNotMoreThanEffect = (RestrictionUntapNotMoreThanEffect)effect;
restrictionUntapNotMoreThanEffects.addEffect(newRestrictionUntapNotMoreThanEffect, source);
break;
case REQUIREMENT: case REQUIREMENT:
RequirementEffect newRequirementEffect = (RequirementEffect)effect; RequirementEffect newRequirementEffect = (RequirementEffect)effect;
requirementEffects.addEffect(newRequirementEffect, source); requirementEffects.addEffect(newRequirementEffect, source);

View file

@ -28,12 +28,14 @@
package mage.abilities.effects; package mage.abilities.effects;
import mage.abilities.Ability;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.EffectType; import mage.constants.EffectType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.abilities.Ability; import mage.filter.FilterPermanent;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player;
/** /**
* *
@ -41,13 +43,28 @@ import mage.game.permanent.Permanent;
*/ */
public abstract class RestrictionEffect<T extends RestrictionEffect<T>> extends ContinuousEffectImpl<T> { public abstract class RestrictionEffect<T extends RestrictionEffect<T>> extends ContinuousEffectImpl<T> {
private boolean notMoreThanRestriction;
private int notMoreThanNumber;
private FilterPermanent notMoreThanNumberFilter;
public RestrictionEffect(Duration duration) { public RestrictionEffect(Duration duration) {
this(duration, false, 0, null);
}
public RestrictionEffect(Duration duration, boolean notMoreThanRestriction, int notMoreThanNumber, FilterPermanent notMoreThanNumberFilter) {
super(duration, Outcome.Detriment); super(duration, Outcome.Detriment);
this.effectType = EffectType.RESTRICTION; this.effectType = EffectType.RESTRICTION;
this.notMoreThanRestriction = notMoreThanRestriction;
this.notMoreThanNumber = notMoreThanNumber;
this.notMoreThanNumberFilter = notMoreThanNumberFilter;
} }
public RestrictionEffect(final RestrictionEffect effect) { public RestrictionEffect(final RestrictionEffect effect) {
super(effect); super(effect);
this.notMoreThanRestriction = effect.notMoreThanRestriction;
if (this.notMoreThanNumberFilter != null) {
this.notMoreThanNumberFilter = effect.notMoreThanNumberFilter.copy();
}
} }
@Override @Override
@ -57,6 +74,15 @@ public abstract class RestrictionEffect<T extends RestrictionEffect<T>> extends
public abstract boolean applies(Permanent permanent, Ability source, Game game); public abstract boolean applies(Permanent permanent, Ability source, Game game);
/*
* only used for the notMoreThanRestrictions, called to check if the effect shall be applied for a player
*
*/
public boolean appliesNotMoreThan(Player player, Ability source, Game game) {
return false;
}
public boolean canAttack(Game game) { public boolean canAttack(Game game) {
return true; return true;
} }
@ -77,5 +103,16 @@ public abstract class RestrictionEffect<T extends RestrictionEffect<T>> extends
return true; return true;
} }
public boolean isNotMoreThanRestriction() {
return notMoreThanRestriction;
}
public int getNotMoreThanNumber() {
return notMoreThanNumber;
}
public FilterPermanent getNotMoreThanNumberFilter() {
return notMoreThanNumberFilter;
}
} }

View file

@ -0,0 +1,78 @@
/*
* 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 mage.abilities.Ability;
import mage.constants.Duration;
import mage.constants.EffectType;
import mage.constants.Outcome;
import mage.filter.common.FilterControlledPermanent;
import mage.game.Game;
import mage.players.Player;
/**
*
* @author LevelX2
*/
public abstract class RestrictionUntapNotMoreThanEffect<T extends RestrictionUntapNotMoreThanEffect<T>> extends ContinuousEffectImpl<T> {
private int number;
private FilterControlledPermanent filter;
public RestrictionUntapNotMoreThanEffect(Duration duration, int number, FilterControlledPermanent filter) {
super(duration, Outcome.Detriment);
this.effectType = EffectType.RESTRICTION_UNTAP_NOT_MORE_THAN;
this.number = number;
this.filter = filter;
}
public RestrictionUntapNotMoreThanEffect(final RestrictionUntapNotMoreThanEffect effect) {
super(effect);
this.number = effect.number;
if (effect.filter != null) {
this.filter = effect.filter.copy();
}
}
@Override
public boolean apply(Game game, Ability source) {
throw new UnsupportedOperationException("Not supported.");
}
public abstract boolean applies(Player player, Ability source, Game game);
public int getNumber() {
return number;
}
public FilterControlledPermanent getFilter() {
return filter;
}
}

View file

@ -13,6 +13,7 @@ public enum EffectType {
REDIRECTION("Redirection Effect"), REDIRECTION("Redirection Effect"),
ASTHOUGH("As Though Effect"), ASTHOUGH("As Though Effect"),
RESTRICTION("Restriction Effect"), RESTRICTION("Restriction Effect"),
RESTRICTION_UNTAP_NOT_MORE_THAN("Restriction untap not more than Effect"),
REQUIREMENT("Requirement Effect"), REQUIREMENT("Requirement Effect"),
COSTMODIFICATION("Cost Modification Effect"), COSTMODIFICATION("Cost Modification Effect"),
SPLICE("Splice Card Effect"); SPLICE("Splice Card Effect");

View file

@ -29,6 +29,7 @@
package mage.players; package mage.players;
import java.io.Serializable; import java.io.Serializable;
import java.security.InvalidParameterException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -36,6 +37,7 @@ import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Random; import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -56,6 +58,7 @@ import mage.abilities.common.delayed.AtTheEndOfTurnStepPostDelayedTriggeredAbili
import mage.abilities.costs.AdjustingSourceCosts; import mage.abilities.costs.AdjustingSourceCosts;
import mage.abilities.costs.AlternativeCost; import mage.abilities.costs.AlternativeCost;
import mage.abilities.effects.RestrictionEffect; import mage.abilities.effects.RestrictionEffect;
import mage.abilities.effects.RestrictionUntapNotMoreThanEffect;
import mage.abilities.effects.common.LoseControlOnOtherPlayersControllerEffect; import mage.abilities.effects.common.LoseControlOnOtherPlayersControllerEffect;
import mage.abilities.keyword.FlashbackAbility; import mage.abilities.keyword.FlashbackAbility;
import mage.abilities.keyword.HexproofAbility; import mage.abilities.keyword.HexproofAbility;
@ -82,8 +85,11 @@ import mage.counters.Counter;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.counters.Counters; import mage.counters.Counters;
import mage.filter.FilterCard; import mage.filter.FilterCard;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.common.FilterCreatureForCombat; import mage.filter.common.FilterCreatureForCombat;
import mage.filter.common.FilterCreatureForCombatBlock; import mage.filter.common.FilterCreatureForCombatBlock;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.permanent.PermanentIdPredicate;
import mage.game.ExileZone; import mage.game.ExileZone;
import mage.game.Game; import mage.game.Game;
import mage.game.combat.CombatGroup; import mage.game.combat.CombatGroup;
@ -99,10 +105,12 @@ import mage.players.net.UserData;
import mage.target.Target; import mage.target.Target;
import mage.target.TargetAmount; import mage.target.TargetAmount;
import mage.target.TargetCard; import mage.target.TargetCard;
import mage.target.TargetPermanent;
import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInLibrary;
import mage.target.common.TargetDiscard; import mage.target.common.TargetDiscard;
import mage.watchers.common.BloodthirstWatcher; import mage.watchers.common.BloodthirstWatcher;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.omg.CORBA.DynAnyPackage.Invalid;
public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Serializable { public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Serializable {
@ -999,6 +1007,122 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser
@Override @Override
public void untap(Game game) { public void untap(Game game) {
// create list of all "notMoreThan" effects to track which one are consumed
HashMap<Entry<RestrictionUntapNotMoreThanEffect, HashSet<Ability>>, Integer> notMoreThanEffectsUsage = new HashMap<Entry<RestrictionUntapNotMoreThanEffect, HashSet<Ability>>, Integer>();
for (Entry<RestrictionUntapNotMoreThanEffect, HashSet<Ability>> restrictionEffect: game.getContinuousEffects().getApplicableRestrictionUntapNotMoreThanEffects(this, game).entrySet()) {
notMoreThanEffectsUsage.put(restrictionEffect, new Integer(restrictionEffect.getKey().getNumber()));
}
if (!notMoreThanEffectsUsage.isEmpty()) {
// create list of all permanents that can be untapped generally
List<Permanent> canBeUntapped = new ArrayList<Permanent>();
for (Permanent permanent: game.getBattlefield().getAllActivePermanents(playerId)) {
boolean untap = true;
for (RestrictionEffect effect: game.getContinuousEffects().getApplicableRestrictionEffects(permanent, game).keySet()) {
untap &= effect.canBeUntapped(permanent, game);
}
if (untap) {
canBeUntapped.add(permanent);
}
}
// selected permanents to untap
List<Permanent> selectedToUntap = new ArrayList<Permanent>();
// player can cancel the seletion of an effect to use a prefered order of restriction effects
boolean playerCanceledSelection;
do {
playerCanceledSelection = false;
// select permanents to untap to consume the "notMoreThan" effects
for(Map.Entry<Entry<RestrictionUntapNotMoreThanEffect, HashSet<Ability>>, Integer> handledEntry: notMoreThanEffectsUsage.entrySet()) {
// select a permanent to untap for this entry
int numberToUntap = handledEntry.getValue().intValue();
if (numberToUntap > 0) {
List<Permanent> leftForUntap = getPermanentsThatCanBeUntapped(game, canBeUntapped, handledEntry.getKey().getKey(), notMoreThanEffectsUsage);
FilterControlledPermanent filter = handledEntry.getKey().getKey().getFilter().copy();
String message = filter.getMessage();
// omitt already from other untap effects selected permanents
for (Permanent permanent: selectedToUntap) {
filter.add(Predicates.not(new PermanentIdPredicate(permanent.getId())));
}
// while targets left and there is still allowed to untap
while (leftForUntap.size() > 0 && numberToUntap > 0) {
// player has to select the permanent he wants to untap for this restriction
Ability ability = handledEntry.getKey().getValue().iterator().next();
if (ability != null) {
StringBuilder sb = new StringBuilder(message).append(" to untap").append(" (").append(Math.min(leftForUntap.size(), numberToUntap)).append(" in total");
MageObject effectSource = game.getObject(ability.getSourceId());
if (effectSource != null) {
sb.append(" from ").append(effectSource.getName()).toString();
}
sb.append(")");
filter.setMessage(sb.toString());
Target target = new TargetPermanent(filter);
if (!this.chooseTarget(Outcome.Untap, target, ability, game)) {
// player canceled, go on with the next effect (if no other effect available, this effect will be active again)
playerCanceledSelection = true;
break;
}
Permanent selectedPermanent = game.getPermanent(target.getFirstTarget());
if (leftForUntap.contains(selectedPermanent)) {
selectedToUntap.add(selectedPermanent);
numberToUntap--;
// don't allow to select same permanent twice
filter.add(Predicates.not(new PermanentIdPredicate(selectedPermanent.getId())));
// reduce available untap numbers from other "UntapNotMoreThan" effects if selected permanent applies to their filter too
for (Entry<Entry<RestrictionUntapNotMoreThanEffect, HashSet<Ability>>, Integer> notMoreThanEffect : notMoreThanEffectsUsage.entrySet()) {
if (notMoreThanEffect.getValue().intValue() > 0 && notMoreThanEffect.getKey().getKey().getFilter().match(selectedPermanent, game)) {
notMoreThanEffect.setValue(new Integer(notMoreThanEffect.getValue().intValue() - 1));
}
}
// update the left for untap list
leftForUntap = getPermanentsThatCanBeUntapped(game, canBeUntapped, handledEntry.getKey().getKey(), notMoreThanEffectsUsage);
// remove already selected permanents
for (Permanent permanent :selectedToUntap) {
if (leftForUntap.contains(permanent)) {
leftForUntap.remove(permanent);
}
}
} else {
// player selected an permanent that is restricted by another effect, disallow it (so AI can select another one)
filter.add(Predicates.not(new PermanentIdPredicate(selectedPermanent.getId())));
if (this.isHuman()) {
game.informPlayer(this, "This permanent can't be untapped because of other restricting effect.");
}
}
}
}
}
}
} while (playerCanceledSelection);
// show in log which permanents were selected to untap
for(Permanent permanent :selectedToUntap) {
game.informPlayers(new StringBuilder(this.getName()).append(" untapped ").append(permanent.getName()).toString());
}
// untap if permanent is not concerned by notMoreThan effects or is included in the selectedToUntapList
for (Permanent permanent: canBeUntapped) {
boolean doUntap = true;
if (!selectedToUntap.contains(permanent)) {
// if the permanent is covered by one of the restriction effects, don't untap it
for (Entry<Entry<RestrictionUntapNotMoreThanEffect, HashSet<Ability>>, Integer> notMoreThanEffect : notMoreThanEffectsUsage.entrySet()) {
if (notMoreThanEffect.getKey().getKey().getFilter().match(permanent, game)) {
doUntap = false;
break;
}
}
}
if (permanent != null && doUntap) {
permanent.untap(game);
}
}
} else {
//20091005 - 502.2 //20091005 - 502.2
for (Permanent permanent: game.getBattlefield().getAllActivePermanents(playerId)) { for (Permanent permanent: game.getBattlefield().getAllActivePermanents(playerId)) {
boolean untap = true; boolean untap = true;
@ -1010,6 +1134,28 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser
} }
} }
} }
}
private List<Permanent> getPermanentsThatCanBeUntapped(Game game, List<Permanent> canBeUntapped, RestrictionUntapNotMoreThanEffect handledEffect, HashMap<Entry<RestrictionUntapNotMoreThanEffect, HashSet<Ability>>, Integer> notMoreThanEffectsUsage) {
List<Permanent> leftForUntap = new ArrayList<Permanent>();
// select permanents that can still be untapped
for (Permanent permanent: canBeUntapped) {
if (handledEffect.getFilter().match(permanent, game)) { // matches the restricted permanents of handled entry
boolean canBeSelected = true;
// check if the permanent is restriced by another restriction that has left no permanent
for (Entry<Entry<RestrictionUntapNotMoreThanEffect, HashSet<Ability>>, Integer> notMoreThanEffect : notMoreThanEffectsUsage.entrySet()) {
if (notMoreThanEffect.getKey().getKey().getFilter().match(permanent, game) && notMoreThanEffect.getValue().intValue() == 0) {
canBeSelected = false;
break;
}
}
if (canBeSelected) {
leftForUntap.add(permanent);
}
}
}
return leftForUntap;
}
@Override @Override
public UUID getId() { public UUID getId() {