diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java index c364996f774..97703c7f3bd 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -1229,6 +1229,14 @@ public class ChangeZoneAi extends SpellAbilityAi { } } + // if max power exceeded, do not choose this card (but keep looking for other options) + if (sa.hasParam("MaxTotalTargetPower")) { + if (choice.getNetPower() > sa.getTargetRestrictions().getMaxTotalPower(choice, sa) -sa.getTargets().getTotalTargetedPower()) { + list.remove(choice); + continue; + } + } + // honor the Same Creature Type restriction if (sa.getTargetRestrictions().isWithSameCreatureType()) { Card firstTarget = sa.getTargetCard(); diff --git a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java index 9ccdb5315a2..e252a1d131d 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java @@ -331,6 +331,11 @@ public final class AbilityFactory { abTgt.setMaxTotalCMC(mapParams.get("MaxTotalTargetCMC")); } + if (mapParams.containsKey("MaxTotalTargetPower")) { + // only target cards up to a certain total max power + abTgt.setMaxTotalPower(mapParams.get("MaxTotalTargetPower")); + } + // TargetValidTargeting most for Counter: e.g. target spell that // targets X. if (mapParams.containsKey("TargetValidTargeting")) { diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-game/src/main/java/forge/game/card/CardUtil.java index 975c43a62cc..b682a63ffd7 100644 --- a/forge-game/src/main/java/forge/game/card/CardUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardUtil.java @@ -547,6 +547,21 @@ public final class CardUtil { } } + // Remove cards exceeding total power + if (ability.hasParam("MaxTotalTargetPower")) { + int totalPowerTargeted = 0; + for (final Card c : targeted) { + totalPowerTargeted += c.getNetPower(); + } + + final List choicesCopy = Lists.newArrayList(choices); + for (final Card c : choicesCopy) { + if (c.getNetPower() > tgt.getMaxTotalPower(c, ability) - totalPowerTargeted) { + choices.remove(c); + } + } + } + // If all cards (including subability targets) must have the same controller if (tgt.isSameController() && !targetedObjects.isEmpty()) { final List list = Lists.newArrayList(); diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index 6a6f48d53bb..b23c02e99e7 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -1227,6 +1227,19 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } } + if (hasParam("MaxTotalTargetPower") && entity instanceof Card) { + int soFar = Aggregates.sum(getTargets().getTargetCards(), CardPredicates.Accessors.fnGetNetPower); + // only add if it isn't already targeting + if (!isTargeting(entity)) { + final Card c = (Card) entity; + soFar += c.getNetPower(); + } + + if (soFar > tr.getMaxTotalPower(getHostCard(),this)) { + return false; + } + } + if (tr.isDifferentControllers()) { Player newController; if (entity instanceof Card) { diff --git a/forge-game/src/main/java/forge/game/spellability/TargetChoices.java b/forge-game/src/main/java/forge/game/spellability/TargetChoices.java index 15cc83cd1c0..752e924b460 100644 --- a/forge-game/src/main/java/forge/game/spellability/TargetChoices.java +++ b/forge-game/src/main/java/forge/game/spellability/TargetChoices.java @@ -58,6 +58,14 @@ public class TargetChoices extends ForwardingList implements Cloneab } @Override + public final int getTotalTargetedPower() { + int totalPower = 0; + for (Card c : Iterables.filter(targets, Card.class)) { + totalPower += c.getNetPower(); + } + return totalPower; + } + public final boolean add(final GameObject o) { if (o instanceof Player || o instanceof Card || o instanceof SpellAbility) { return super.add(o); diff --git a/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java b/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java index 5eeca0fb5c3..eaba915e04b 100644 --- a/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java +++ b/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java @@ -73,7 +73,10 @@ public class TargetRestrictions { // What's the max total CMC of targets? private String maxTotalCMC; - + + // What's the max total power of targets? + private String maxTotalPower; + // Not sure what's up with Mandatory? Why wouldn't targeting be mandatory? private boolean bMandatory = false; @@ -92,6 +95,7 @@ public class TargetRestrictions { this.minTargets = target.getMinTargets(); this.maxTargets = target.getMaxTargets(); this.maxTotalCMC = target.getMaxTotalCMC(); + this.maxTotalPower = target.getMaxTotalPower(); this.tgtZone = target.getZone(); this.saValidTargeting = target.getSAValidTargeting(); this.uniqueTargets = target.isUniqueTargets(); @@ -163,6 +167,29 @@ public class TargetRestrictions { this.maxTotalCMC = cmc; } + /** + *

+ * setMaxTotalPower. + *

+ * + * @param power + * a String. + */ + public final void setMaxTotalPower(final String power) { + this.maxTotalPower = power; + } + + /** + *

+ * doesTarget. + *

+ * + * @return a boolean. + */ + public final boolean doesTarget() { + return this.tgtValid; + } + /** *

* getValidTgts. @@ -216,6 +243,19 @@ public class TargetRestrictions { return AbilityUtils.calculateAmount(c, this.maxTotalCMC, sa); } + /** + * Gets the max targets. + * + * @return the max targets + */ + private final String getMaxTotalPower() { + return this.maxTotalPower; + } + + public final int getMaxTotalPower(final Card c, final SpellAbility sa) { + return AbilityUtils.calculateAmount(c, this.maxTotalPower, sa); + } + /** *

* Getter for the field minTargets. @@ -541,7 +581,7 @@ public class TargetRestrictions { for (final Card c : game.getCardsIn(this.tgtZone)) { if (c.isValid(this.validTgts, srcCard.getController(), srcCard, sa) - && (!isTargeted || sa.canTarget(c)) + && (!isTargeted || sa.canTarget(c)) && !sa.getTargets().contains(c)) { candidates.add(c); } diff --git a/forge-gui/res/cardsfolder/n/nethroi_apex_of_death.txt b/forge-gui/res/cardsfolder/n/nethroi_apex_of_death.txt new file mode 100755 index 00000000000..c74292dfddc --- /dev/null +++ b/forge-gui/res/cardsfolder/n/nethroi_apex_of_death.txt @@ -0,0 +1,11 @@ +Name:Nethroi, Apex of Death +ManaCost:2 W B G +Types:Legendary Creature Cat Nightmare Beast +PT:5/5 +K:Mutate:4 GW B B +K:Deathtouch +K:Lifelink +T:Mode$ Mutates | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ Whenever this creature mutates, return any number of target creature cards with total power 10 or less from your graveyard to the battlefield. +SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TargetMin$ 0 | TargetMax$ X | References$ X | ValidTgts$ Creature.YouOwn | MaxTotalTargetPower$ 10 | TgtPrompt$ Select any number of creature cards with total power 10 or less +SVar:X:Count$ValidGraveyard Creature.YouOwn +Oracle:Mutate {4}{G/W}{B}{B} (If you cast this spell for its mutate cost, put it over or under target non-Human creature you own. They mutate into the creature on top plus all abilities from under it.)\nDeathtouch, lifelink\nWhenever this creature mutates, return any number of target creature cards with total power 10 or less from your graveyard to the battlefield. diff --git a/forge-gui/src/main/java/forge/match/input/InputSelectTargets.java b/forge-gui/src/main/java/forge/match/input/InputSelectTargets.java index a8b1622baed..24605b802d7 100644 --- a/forge-gui/src/main/java/forge/match/input/InputSelectTargets.java +++ b/forge-gui/src/main/java/forge/match/input/InputSelectTargets.java @@ -212,6 +212,20 @@ public final class InputSelectTargets extends InputSyncronizedBase { } } + if (sa.hasParam("MaxTotalTargetPower")) { + int maxTotalPower = tgt.getMaxTotalPower(sa.getHostCard(), sa); + if (maxTotalPower > 0) { + int soFar = Aggregates.sum(sa.getTargets().getTargetCards(), CardPredicates.Accessors.fnGetNetPower); + if (!sa.isTargeting(card)) { + soFar += card.getNetPower(); + } + if (soFar > maxTotalPower) { + showMessage(sa.getHostCard() + " - Cannot target this card (power limit exceeded)"); + return false; + } + } + } + // If all cards must have different controllers if (tgt.isDifferentControllers()) { final List targetedControllers = new ArrayList<>();