CastTotalManaSpent Treasure

This commit is contained in:
Hans Mackowiak
2021-07-10 07:49:12 +00:00
parent 7744552450
commit 1f0e49d1d4
10 changed files with 138 additions and 55 deletions

View File

@@ -1,26 +1,8 @@
package forge.ai;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.*;
import forge.ai.ability.AnimateAi;
import forge.card.ColorSet;
import forge.card.MagicColor;
@@ -34,20 +16,10 @@ import forge.game.GameActionUtil;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardUtil;
import forge.game.card.CounterEnumType;
import forge.game.card.*;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.cost.Cost;
import forge.game.cost.CostAdjustment;
import forge.game.cost.CostPartMana;
import forge.game.cost.CostPayEnergy;
import forge.game.cost.CostPayment;
import forge.game.cost.*;
import forge.game.keyword.Keyword;
import forge.game.mana.Mana;
import forge.game.mana.ManaCostBeingPaid;
@@ -67,6 +39,10 @@ import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import java.util.*;
public class ComputerUtilMana {
private final static boolean DEBUG_MANA_PAYMENT = false;
@@ -272,28 +248,87 @@ public class ComputerUtilMana {
public static SpellAbility chooseManaAbility(ManaCostBeingPaid cost, SpellAbility sa, Player ai, ManaCostShard toPay,
Collection<SpellAbility> saList, boolean checkCosts) {
Card saHost = sa.getHostCard();
// CastTotalManaSpent (AIPreference:ManaFrom$Type or AIManaPref$ Type)
String manaSourceType = "";
if (saHost.hasSVar("AIPreference")) {
String condition = saHost.getSVar("AIPreference");
if (condition.startsWith("ManaFrom")) {
manaSourceType = TextUtil.split(condition, '$')[1];
}
} else if (sa.hasParam("AIManaPref")) {
manaSourceType = sa.getParam("AIManaPref");
}
if (manaSourceType != "") {
List<SpellAbility> filteredList = Lists.newArrayList(saList);
switch (manaSourceType) {
case "Snow":
filteredList.sort(new Comparator<SpellAbility>() {
@Override
public int compare(SpellAbility ab1, SpellAbility ab2) {
return ab1.getHostCard() != null && ab1.getHostCard().isSnow()
&& ab2.getHostCard() != null && !ab2.getHostCard().isSnow() ? -1 : 1;
}
});
saList = filteredList;
break;
case "Treasure":
// Try to spend only one Treasure if possible
filteredList.sort(new Comparator<SpellAbility>() {
@Override
public int compare(SpellAbility ab1, SpellAbility ab2) {
return ab1.getHostCard() != null && ab1.getHostCard().getType().hasSubtype("Treasure")
&& ab2.getHostCard() != null && !ab2.getHostCard().getType().hasSubtype("Treasure") ? -1 : 1;
}
});
SpellAbility first = filteredList.get(0);
if (first.getHostCard() != null && first.getHostCard().getType().hasSubtype("Treasure")) {
saList.remove(first);
List<SpellAbility> updatedList = Lists.newArrayList();
updatedList.add(first);
updatedList.addAll(saList);
saList = updatedList;
}
break;
case "TreasureMax":
// Ok to spend as many Treasures as possible
filteredList.sort(new Comparator<SpellAbility>() {
@Override
public int compare(SpellAbility ab1, SpellAbility ab2) {
return ab1.getHostCard() != null && ab1.getHostCard().getType().hasSubtype("Treasure")
&& ab2.getHostCard() != null && !ab2.getHostCard().getType().hasSubtype("Treasure") ? -1 : 1;
}
});
saList = filteredList;
break;
default:
break;
}
}
for (final SpellAbility ma : saList) {
if (ma.getHostCard() == sa.getHostCard()) {
if (ma.getHostCard() == saHost) {
continue;
}
if (sa.getHostCard() != null) {
if (saHost != null) {
if (sa.getApi() == ApiType.Animate) {
// For abilities like Genju of the Cedars, make sure that we're not activating the aura ability by tapping the enchanted card for mana
if (sa.getHostCard().isAura() && "Enchanted".equals(sa.getParam("Defined"))
&& ma.getHostCard() == sa.getHostCard().getEnchantingCard()
if (saHost.isAura() && "Enchanted".equals(sa.getParam("Defined"))
&& ma.getHostCard() == saHost.getEnchantingCard()
&& ma.getPayCosts().hasTapCost()) {
continue;
}
// If a manland was previously animated this turn, do not tap it to animate another manland
if (sa.getHostCard().isLand() && ma.getHostCard().isLand()
if (saHost.isLand() && ma.getHostCard().isLand()
&& ai.getController().isAI()
&& AnimateAi.isAnimatedThisTurn(ai, ma.getHostCard())) {
continue;
}
} else if (sa.getApi() == ApiType.Pump) {
if ((sa.getHostCard().isInstant() || sa.getHostCard().isSorcery())
if ((saHost.isInstant() || saHost.isSorcery())
&& ma.getHostCard().isCreature()
&& ai.getController().isAI()
&& ma.getPayCosts().hasTapCost()
@@ -303,7 +338,7 @@ public class ComputerUtilMana {
continue;
}
} else if (sa.getApi() == ApiType.Attach
&& "AvoidPayingWithAttachTarget".equals(sa.getHostCard().getSVar("AIPaymentPreference"))) {
&& "AvoidPayingWithAttachTarget".equals(saHost.getSVar("AIPaymentPreference"))) {
// For cards like Genju of the Cedars, make sure we're not attaching to the same land that will
// be tapped to pay its own cost if there's another untapped land like that available
if (ma.getHostCard().equals(sa.getTargetCard())) {
@@ -320,7 +355,7 @@ public class ComputerUtilMana {
// Exception: when paying generic mana with Cavern of Souls, prefer the colored mana producing ability
// to attempt to make the spell uncounterable when possible.
if (ComputerUtilAbility.getAbilitySourceName(ma).equals("Cavern of Souls")
&& sa.getHostCard().getType().getCreatureTypes().contains(ma.getHostCard().getChosenType())) {
&& saHost.getType().getCreatureTypes().contains(ma.getHostCard().getChosenType())) {
if (toPay == ManaCostShard.COLORLESS && cost.getUnpaidShards().contains(ManaCostShard.GENERIC)) {
// Deprioritize Cavern of Souls, try to pay generic mana with it instead to use the NoCounter ability
continue;

View File

@@ -1832,25 +1832,26 @@ public class AbilityUtils {
return doXMath(list.size(), expr, c, ctb);
}
if (sq[0].startsWith("CastTotalManaSpent")) {
return doXMath(c.getCastSA() != null ? c.getCastSA().getTotalManaSpent() : 0, expr, c, ctb);
if (sq[0].equals("ResolvedThisTurn")) {
return doXMath(sa.getResolvedThisTurn(), expr, c, ctb);
}
if (sq[0].equals("CastTotalSnowManaSpent")) {
if (sq[0].startsWith("TotalManaSpent ")) {
final String[] k = sq[0].split(" ");
int v = 0;
if (c.getCastSA() != null) {
for (Mana m : c.getCastSA().getPayingMana()) {
if (m.isSnow()) {
v += 1;
if (sa.getRootAbility().getPayingMana() != null) {
for (Mana m : sa.getRootAbility().getPayingMana()) {
Card source = m.getSourceCard();
if (source != null) {
if (source.isValid(k[1].split(","), player, c, sa)) {
v += 1;
}
}
}
}
return doXMath(v, expr, c, ctb);
}
if (sq[0].equals("ResolvedThisTurn")) {
return doXMath(sa.getResolvedThisTurn(), expr, c, ctb);
}
} else {
// fallback if ctb isn't a spellability
if (sq[0].startsWith("LastStateBattlefield")) {
@@ -1870,8 +1871,29 @@ public class AbilityUtils {
if (sq[0].startsWith("xPaid")) {
return doXMath(c.getXManaCostPaid(), expr, c, ctb);
}
} // end SpellAbility
if (sq[0].equals("CastTotalManaSpent")) {
return doXMath(c.getCastSA() != null ? c.getCastSA().getTotalManaSpent() : 0, expr, c, ctb);
}
if (sq[0].startsWith("CastTotalManaSpent ")) {
final String[] k = sq[0].split(" ");
int v = 0;
if (c.getCastSA() != null) {
for (Mana m : c.getCastSA().getPayingMana()) {
Card source = m.getSourceCard();
if (source != null) {
if (source.isValid(k[1].split(","), player, c, ctb)) {
v += 1;
}
}
}
}
return doXMath(v, expr, c, ctb);
}
// Count$TargetedLifeTotal (targeted player's life total)
// Not optimal but since xCount doesn't take SAs, we need to replicate while we have it
// Probably would be best if xCount took an optional SA to use in these circumstances

View File

@@ -5,6 +5,7 @@ PT:4/4
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigTap | TriggerDescription$ When CARDNAME enters the battlefield, tap target artifact or creature an opponent controls. If {S} was spent to cast this spell, that permanent doesn't untap during its controller's next untap step. ({S} is mana from a snow source.)
SVar:TrigTap:DB$ Tap | ValidTgts$ Artifact.OppCtrl,Creature.OppCtrl | TgtPrompt$ Choose target artifact or creature an opponent controls | SubAbility$ DBPump
SVar:DBPump:DB$ Pump | Defined$ Targeted | ConditionCheckSVar$ S | ConditionSVarCompare$ GE1 | KW$ HIDDEN This card doesn't untap during your next untap step. | Duration$ Permanent
SVar:S:Count$CastTotalSnowManaSpent
SVar:S:Count$CastTotalManaSpent Snow
SVar:AIPreference:ManaFrom$Snow
DeckNeeds:Type$Snow
Oracle:When Berg Strider enters the battlefield, tap target artifact or creature an opponent controls. If {S} was spent to cast this spell, that permanent doesn't untap during its controller's next untap step. ({S} is mana from a snow source.)

View File

@@ -2,9 +2,10 @@ Name:Blessing of Frost
ManaCost:3 G
Types:Snow Sorcery
A:SP$ PutCounter | Cost$ 3 G | Choices$ Creature.YouCtrl | ChoiceTitle$ Choose any number of creatures you control to distribute counters to | CounterType$ P1P1 | CounterNum$ X | ChoiceAmount$ X | MinChoiceAmount$ 1 | DividedAsYouChoose$ X | SubAbility$ DBDraw | SpellDescription$ Distribute X +1/+1 counters among any number of creatures you control, where X is the amount of {S} spent to cast this spell.
SVar:X:Count$CastTotalSnowManaSpent
SVar:X:Count$CastTotalManaSpent Snow
SVar:DBDraw:DB$ Draw | NumCards$ Y | SpellDescription$ Then draw a card for each creature you control with power 4 or greater.
SVar:Y:Count$Valid Creature.powerGE4+YouCtrl
SVar:AIPreference:ManaFrom$Snow
DeckHints:Type$Snow
DeckHas:Ability$Counters
Oracle:Distribute X +1/+1 counters among any number of creatures you control, where X is the amount of {S} spent to cast this spell. Then draw a card for each creature you control with power 4 or greater.

View File

@@ -5,7 +5,8 @@ A:SP$ Charm | Cost$ 4 B B | Choices$ DestroyCtrs,DestroyPWs | CharmNum$ 1 | Spel
SVar:DestroyCtrs:DB$ DestroyAll | ValidCards$ Creature | SubAbility$ DBReturn | SpellDescription$ Destroy all creatures.
SVar:DestroyPWs:DB$ DestroyAll | ValidCards$ Planeswalker | SubAbility$ DBReturn | SpellDescription$ Destroy all planeswalkers.
SVar:DBReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Hidden$ True | Mandatory$ True | ChangeType$ Creature.YouOwn+cmcLEX,Planeswalker.YouOwn+cmcLEX | SpellDescription$ Then return a creature or planeswalker card with mana value X or less from your graveyard to the battlefield, where X is the amount of {S} spent to cast this spell. ({S} is mana from a snow source.)
SVar:X:Count$CastTotalSnowManaSpent
SVar:X:Count$CastTotalManaSpent Snow
SVar:AIPreference:ManaFrom$Snow
AI:RemoveDeck:Random
DeckHints:Type$Snow
Oracle:Choose one —\n• Destroy all creatures.\n• Destroy all planeswalkers.\nThen return a creature or planeswalker card with mana value X or less from your graveyard to the battlefield, where X is the amount of {S} spent to cast this spell. ({S} is mana from a snow source.)

View File

@@ -3,6 +3,7 @@ ManaCost:3 U U
Types:Snow Instant
A:SP$ Scry | Cost$ 3 U U | ScryNum$ X | SubAbility$ DBDraw | SpellDescription$ Scry X, where X is the amount of {S} spent to cast this spell, then draw three cards. ({S} is mana from a snow source.)
SVar:DBDraw:DB$ Draw | NumCards$ 3
SVar:X:Count$CastTotalSnowManaSpent
SVar:X:Count$CastTotalManaSpent Snow
SVar:AIPreference:ManaFrom$Snow
DeckNeeds:Type$Snow
Oracle:Scry X, where X is the amount of {S} spent to cast this spell, then draw three cards. ({S} is mana from a snow source.)

View File

@@ -3,7 +3,8 @@ ManaCost:2 W
Types:Snow Sorcery
A:SP$ ChangeZone | Cost$ 2 W | Origin$ Library | Destination$ Hand | ChangeType$ Permanent.Snow,Legendary,Saga | ChangeNum$ 1 | SubAbility$ DBGainLife | SpellDescription$ Search your library for a snow permanent card, a legendary card, or a Saga card, reveal it, put it into your hand, then shuffle. You gain 1 life for each {S} spent to cast this spell.
SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ X
SVar:X:Count$CastTotalSnowManaSpent
SVar:X:Count$CastTotalManaSpent Snow
SVar:AIPreference:ManaFrom$Snow
AI:RemoveDeck:Random
DeckHints:Type$Legendary
Oracle:Search your library for a snow permanent card, a legendary card, or a Saga card, reveal it, put it into your hand, then shuffle. You gain 1 life for each {S} spent to cast this spell. ({S} is mana from a snow source.)

View File

@@ -3,6 +3,7 @@ ManaCost:1 R R
Types:Snow Sorcery
A:SP$ DealDamage | Cost$ 1 R R | ValidTgts$ Creature,Planeswalker | TgtPrompt$ Select target creature or planeswalker. | NumDmg$ 4 | SubAbility$ DBColorlessMana | SpellDescription$ CARDNAME deals 4 damage to target creature or planeswalker. Add {C} for each {S} spent to cast this spell. Until end of turn, you don't lose this mana as steps and phases end. | StackDescription$ SpellDescription
SVar:DBColorlessMana:DB$ Mana | Produced$ C | Amount$ X | PersistentMana$ True
SVar:X:Count$CastTotalSnowManaSpent
SVar:X:Count$CastTotalManaSpent Snow
SVar:AIPreference:ManaFrom$Snow
DeckHints:Type$Snow
Oracle:Tundra Fumarole deals 4 damage to target creature or planeswalker. Add {C} for each {S} spent to cast this spell. Until end of turn, you don't lose this mana as steps and phases end. ({S} is mana from a snow source.)

View File

@@ -0,0 +1,10 @@
Name:Forsworn Paladin
ManaCost:B
Types:Creature Human Knight
PT:1/1
K:Menace
A:AB$ Token | Cost$ 1 B T PayLife<1> | TokenAmount$ 1 | TokenScript$ c_a_treasure_sac | TokenOwner$ You | SpellDescription$ Create a Treasure token.
A:AB$ Pump | Cost$ 2 B | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +2 | SubAbility$ DBDeathtouch | AIManaPref$ Treasure | SpellDescription$ Target creature gets +2/+0 until end of turn. If mana from a Treasure was spent to activate this ability, that creature also gains deathtouch until end of turn.
SVar:DBDeathtouch:DB$ Pump | KW$ Deathtouch | Defined$ ParentTarget | ConditionCheckSVar$ TreasureCheck
SVar:TreasureCheck:Count$TotalManaSpent Treasure
Oracle:Menace\n{1}{B}, {T}, Pay 1 life: Create a Treasure token.\n{2}{B}: Target creature gets +2/+0 until end of turn. If mana from a Treasure was spent to activate this ability, that creature also gains deathtouch until end of turn.

View File

@@ -0,0 +1,10 @@
Name:Hired Hexblade
ManaCost:1 B
Types:Creature Elf Warlock
PT:2/2
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | CheckSVar$ TreasureCheck | Execute$ TrigDraw | TriggerDescription$ When CARDNAME enters the battlefield, if mana from a treasure was spent to cast it, you draw a card and you lose 1 life.
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 | SubAbility$ DBLoseLife
SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 1
SVar:TreasureCheck:Count$CastTotalManaSpent Treasure
SVar:AIPreference:ManaFrom$Treasure
Oracle:When Hired Hexblade enters the battlefield, if mana from a treasure was spent to cast it, you draw a card and you lose 1 life.