mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 19:28:01 +00:00
Fix AI can't use special mana abilities of e.g. Faeburrow Elder
This commit is contained in:
@@ -45,6 +45,9 @@ In IntelliJ, if the SDK Manager is not already running, go to Tools > Android >
|
||||
- Android SDK Build-tools 35.0.0
|
||||
- Android 15 (API 35) SDK Platform
|
||||
|
||||
> [!CAUTION]
|
||||
> Be careful about using unsupported api calls e.g. ``StringBuilder.isEmpty()``. Google's documentation for these is sometimes inaccurate.
|
||||
|
||||
### Proguard update
|
||||
|
||||
Standalone Proguard 7.6.0 is included with the project (proguard.jar) under forge-gui-android > tools and supports up to Java 23 (latest android uses Java 17).
|
||||
|
||||
@@ -66,10 +66,8 @@ public class UntapAi extends SpellAbilityAi {
|
||||
if (pDefined.isEmpty() || (pDefined.get(0).isTapped() && pDefined.get(0).getController() == ai)) {
|
||||
// If the defined card is tapped, or if there are no defined cards, we can play this ability
|
||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||
} else {
|
||||
// Otherwise, we can't play this ability
|
||||
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||
}
|
||||
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -57,7 +57,6 @@ import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* GameActionUtil class.
|
||||
@@ -859,8 +858,6 @@ public final class GameActionUtil {
|
||||
}
|
||||
} else if (sa.getApi() == ApiType.ManaReflected) {
|
||||
baseMana = abMana.getExpressChoice();
|
||||
} else if (abMana.isSpecialMana()) {
|
||||
baseMana = abMana.getExpressChoice();
|
||||
} else {
|
||||
baseMana = abMana.mana(sa);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import static forge.util.TextUtil.toManaString;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.util.Lang;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@@ -17,14 +18,11 @@ import forge.game.GameActionUtil;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilityManaPart;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Localizer;
|
||||
import io.sentry.Breadcrumb;
|
||||
import io.sentry.Sentry;
|
||||
@@ -41,8 +39,8 @@ public class ManaEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card card = sa.getHostCard();
|
||||
final Game game = card.getGame();
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = host.getGame();
|
||||
final AbilityManaPart abMana = sa.getManaPart();
|
||||
final List<Player> tgtPlayers = getDefinedPlayersOrTargeted(sa);
|
||||
final Player activator = sa.getActivatingPlayer();
|
||||
@@ -63,13 +61,13 @@ public class ManaEffect extends SpellAbilityEffect {
|
||||
|
||||
final Player chooser;
|
||||
if (sa.hasParam("Chooser")) {
|
||||
chooser = AbilityUtils.getDefinedPlayers(card, sa.getParam("Chooser"), sa).get(0);
|
||||
chooser = AbilityUtils.getDefinedPlayers(host, sa.getParam("Chooser"), sa).get(0);
|
||||
} else {
|
||||
chooser = p;
|
||||
}
|
||||
|
||||
if (abMana.isComboMana()) {
|
||||
int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(card, sa.getParam("Amount"), sa) : 1;
|
||||
int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(host, sa.getParam("Amount"), sa) : 1;
|
||||
if (amount <= 0)
|
||||
continue;
|
||||
|
||||
@@ -117,7 +115,7 @@ public class ManaEffect extends SpellAbilityEffect {
|
||||
byte chosenColor = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa,
|
||||
differentChoice && (colorsNeeded == null || colorsNeeded.length <= nMana) ? fullOptions : colorOptions);
|
||||
if (chosenColor == 0)
|
||||
throw new RuntimeException("ManaEffect::resolve() /*combo mana*/ - " + p + " color mana choice is empty for " + card.getName());
|
||||
throw new RuntimeException("ManaEffect::resolve() /*combo mana*/ - " + p + " color mana choice is empty for " + host.getName());
|
||||
|
||||
if (differentChoice) {
|
||||
fullOptions = ColorSet.fromMask(fullOptions.getColor() - chosenColor);
|
||||
@@ -159,99 +157,14 @@ public class ManaEffect extends SpellAbilityEffect {
|
||||
colorMenu = mask == 0 ? ColorSet.WUBRG : ColorSet.fromMask(mask);
|
||||
byte val = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, colorMenu);
|
||||
if (0 == val) {
|
||||
throw new RuntimeException("ManaEffect::resolve() /*any mana*/ - " + p + " color mana choice is empty for " + card.getName());
|
||||
throw new RuntimeException("ManaEffect::resolve() /*any mana*/ - " + p + " color mana choice is empty for " + host.getName());
|
||||
}
|
||||
|
||||
game.getAction().notifyOfValue(sa, card, MagicColor.toSymbol(val), p);
|
||||
game.getAction().notifyOfValue(sa, host, MagicColor.toSymbol(val), p);
|
||||
abMana.setExpressChoice(MagicColor.toShortString(val));
|
||||
}
|
||||
else if (abMana.isSpecialMana()) {
|
||||
String type = abMana.getOrigProduced().split("Special ")[1];
|
||||
|
||||
if (type.equals("EnchantedManaCost")) {
|
||||
Card enchanted = card.getEnchantingCard();
|
||||
if (enchanted == null)
|
||||
continue;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int generic = enchanted.getManaCost().getGenericCost();
|
||||
|
||||
for (ManaCostShard s : enchanted.getManaCost()) {
|
||||
ColorSet cs = ColorSet.fromMask(s.getColorMask());
|
||||
byte chosenColor;
|
||||
if (cs.isColorless())
|
||||
continue;
|
||||
if (s.isOr2Generic()) { // CR 106.8
|
||||
chosenColor = chooser.getController().chooseColorAllowColorless(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), card, cs);
|
||||
if (chosenColor == MagicColor.COLORLESS) {
|
||||
generic += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (cs.isMonoColor())
|
||||
chosenColor = s.getColorMask();
|
||||
else /* (cs.isMulticolor()) */ {
|
||||
chosenColor = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs);
|
||||
}
|
||||
sb.append(MagicColor.toShortString(chosenColor));
|
||||
sb.append(' ');
|
||||
}
|
||||
if (generic > 0) {
|
||||
sb.append(generic);
|
||||
}
|
||||
|
||||
abMana.setExpressChoice(sb.toString().trim());
|
||||
} else if (type.equals("LastNotedType")) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
int nMana = 0;
|
||||
for (Object o : card.getRemembered()) {
|
||||
if (o instanceof String) {
|
||||
sb.append(o);
|
||||
nMana++;
|
||||
}
|
||||
}
|
||||
if (nMana == 0) {
|
||||
return;
|
||||
}
|
||||
abMana.setExpressChoice(sb.toString());
|
||||
} else if (type.startsWith("EachColorAmong")) {
|
||||
final String res = type.split("_")[1];
|
||||
final boolean defined = type.startsWith("EachColorAmongDefined");
|
||||
final ZoneType zone = defined || type.startsWith("EachColorAmong_") ? ZoneType.Battlefield :
|
||||
ZoneType.smartValueOf(type.split("_")[0].substring(14));
|
||||
final CardCollection list = defined ? AbilityUtils.getDefinedCards(card, res, sa) :
|
||||
CardLists.getValidCards(card.getGame().getCardsIn(zone), res, activator, card, sa);
|
||||
byte colors = 0;
|
||||
for (Card c : list) {
|
||||
colors |= c.getColor().getColor();
|
||||
}
|
||||
if (colors == 0) return;
|
||||
abMana.setExpressChoice(ColorSet.fromMask(colors));
|
||||
} else if (type.startsWith("EachColoredManaSymbol")) {
|
||||
final String res = type.split("_")[1];
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Card c : AbilityUtils.getDefinedCards(card, res, sa)) {
|
||||
for (ManaCostShard s : c.getManaCost()) {
|
||||
ColorSet cs = ColorSet.fromMask(s.getColorMask());
|
||||
if (cs.isColorless())
|
||||
continue;
|
||||
sb.append(' ');
|
||||
if (cs.isMonoColor())
|
||||
sb.append(MagicColor.toShortString(s.getColorMask()));
|
||||
else /* (cs.isMulticolor()) */ {
|
||||
byte chosenColor = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs);
|
||||
sb.append(MagicColor.toShortString(chosenColor));
|
||||
}
|
||||
}
|
||||
}
|
||||
abMana.setExpressChoice(sb.toString().trim());
|
||||
} else if (type.startsWith("DoubleManaInPool")) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte color : ManaAtom.MANATYPES) {
|
||||
sb.append(StringUtils.repeat(MagicColor.toShortString(color) + " ", p.getManaPool().getAmountOfColor(color)));
|
||||
}
|
||||
abMana.setExpressChoice(sb.toString().trim());
|
||||
}
|
||||
handleSpecialMana(chooser, abMana, sa, true);
|
||||
}
|
||||
|
||||
String mana = GameActionUtil.generatedMana(sa);
|
||||
@@ -261,7 +174,7 @@ public class ManaEffect extends SpellAbilityEffect {
|
||||
String msg = "AbilityFactoryMana::manaResolve() - special mana effect is empty for";
|
||||
|
||||
Breadcrumb bread = new Breadcrumb(msg);
|
||||
bread.setData("Card", card.getName());
|
||||
bread.setData("Card", host.getName());
|
||||
bread.setData("SA", sa.toString());
|
||||
Sentry.addBreadcrumb(bread);
|
||||
|
||||
@@ -281,6 +194,89 @@ public class ManaEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
public static void handleSpecialMana(Player chooser, AbilityManaPart abMana, SpellAbility sa, boolean resolve) {
|
||||
String type = abMana.getOrigProduced().split("Special ")[1];
|
||||
Card host = sa.getHostCard();
|
||||
|
||||
if (resolve) {
|
||||
if (type.equals("EnchantedManaCost")) {
|
||||
Card enchanted = host.getEnchantingCard();
|
||||
if (enchanted == null)
|
||||
return;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int generic = enchanted.getManaCost().getGenericCost();
|
||||
|
||||
for (ManaCostShard s : enchanted.getManaCost()) {
|
||||
ColorSet cs = ColorSet.fromMask(s.getColorMask());
|
||||
byte chosenColor;
|
||||
if (cs.isColorless())
|
||||
continue;
|
||||
if (s.isOr2Generic()) { // CR 106.8
|
||||
chosenColor = chooser.getController().chooseColorAllowColorless(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), host, cs);
|
||||
if (chosenColor == MagicColor.COLORLESS) {
|
||||
generic += 2;
|
||||
continue;
|
||||
}
|
||||
} else if (cs.isMonoColor())
|
||||
chosenColor = s.getColorMask();
|
||||
else /* (cs.isMulticolor()) */ {
|
||||
chosenColor = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs);
|
||||
}
|
||||
sb.append(MagicColor.toShortString(chosenColor));
|
||||
sb.append(' ');
|
||||
}
|
||||
if (generic > 0) {
|
||||
sb.append(generic);
|
||||
}
|
||||
|
||||
abMana.setExpressChoice(sb.toString().trim());
|
||||
} else if (type.startsWith("EachColoredManaSymbol")) {
|
||||
final String res = type.split("_")[1];
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Card c : AbilityUtils.getDefinedCards(host, res, sa)) {
|
||||
for (ManaCostShard s : c.getManaCost()) {
|
||||
ColorSet cs = ColorSet.fromMask(s.getColorMask());
|
||||
if (cs.isColorless())
|
||||
continue;
|
||||
sb.append(' ');
|
||||
if (cs.isMonoColor())
|
||||
sb.append(MagicColor.toShortString(s.getColorMask()));
|
||||
else /* (cs.isMulticolor()) */ {
|
||||
byte chosenColor = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs);
|
||||
sb.append(MagicColor.toShortString(chosenColor));
|
||||
}
|
||||
}
|
||||
}
|
||||
abMana.setExpressChoice(sb.toString().trim());
|
||||
} else if (type.startsWith("DoubleManaInPool")) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte color : ManaAtom.MANATYPES) {
|
||||
sb.append(StringUtils.repeat(MagicColor.toShortString(color) + " ", chooser.getManaPool().getAmountOfColor(color)));
|
||||
}
|
||||
abMana.setExpressChoice(sb.toString().trim());
|
||||
}
|
||||
} else if (type.equals("LastNotedType")) {
|
||||
// Jeweled Lotus
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (Object o : host.getRemembered()) {
|
||||
if (o instanceof String) {
|
||||
sb.append(o);
|
||||
}
|
||||
}
|
||||
String mana = sb.toString();
|
||||
if (mana.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
abMana.setExpressChoice(mana);
|
||||
} else if (type.startsWith("EachColorAmong")) {
|
||||
final String res = type.split("_")[1];
|
||||
ColorSet colors = CardUtil.getColorsFromCards(AbilityUtils.getDefinedCards(host, res, sa));
|
||||
if (colors.isColorless()) return;
|
||||
abMana.setExpressChoice(colors);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* manaStackDescription.
|
||||
|
||||
@@ -30,6 +30,7 @@ import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.ability.effects.ManaEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.cost.Cost;
|
||||
@@ -515,7 +516,11 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
}
|
||||
String produced = this.getOrigProduced();
|
||||
if (produced.contains("Chosen")) {
|
||||
produced = produced.replace("Chosen", getChosenColor(sa, sa.getHostCard().getChosenColors()));
|
||||
produced = produced.replace("Chosen", getChosenColor(sa));
|
||||
}
|
||||
if (isSpecialMana()) {
|
||||
ManaEffect.handleSpecialMana(sa.getActivatingPlayer(), this, sa, false);
|
||||
produced = getExpressChoice();
|
||||
}
|
||||
return produced;
|
||||
}
|
||||
@@ -651,7 +656,7 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
}
|
||||
// replace Chosen for Combo colors
|
||||
if (origProduced.contains("Chosen")) {
|
||||
origProduced = origProduced.replace("Chosen", getChosenColor(sa, sa.getHostCard().getChosenColors()));
|
||||
origProduced = origProduced.replace("Chosen", getChosenColor(sa));
|
||||
}
|
||||
// replace Chosen for Spire colors
|
||||
if (origProduced.contains("ColorID")) {
|
||||
@@ -701,14 +706,14 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
return sb.length() == 0 ? "" : sb.substring(0, sb.length() - 1);
|
||||
}
|
||||
|
||||
public String getChosenColor(SpellAbility sa, Iterable<String> colors) {
|
||||
public String getChosenColor(SpellAbility sa) {
|
||||
if (sa == null) {
|
||||
return "";
|
||||
}
|
||||
Card card = sa.getHostCard();
|
||||
if (card != null) {
|
||||
StringBuilder values = new StringBuilder();
|
||||
for (String c : colors) {
|
||||
for (String c : card.getChosenColors()) {
|
||||
values.append(MagicColor.toShortString(c)).append(" ");
|
||||
}
|
||||
return values.toString().trim();
|
||||
|
||||
@@ -2,6 +2,6 @@ Name:Bloom Tender
|
||||
ManaCost:1 G
|
||||
Types:Creature Elf Druid
|
||||
PT:1/1
|
||||
A:AB$ Mana | Cost$ T | Produced$ Special EachColorAmong_Permanent.YouCtrl | SpellDescription$ For each color among permanents you control, add one mana of that color.
|
||||
A:AB$ Mana | Cost$ T | Produced$ Special EachColorAmong_Valid Permanent.YouCtrl | SpellDescription$ For each color among permanents you control, add one mana of that color.
|
||||
AI:RemoveDeck:All
|
||||
Oracle:{T}: For each color among permanents you control, add one mana of that color.
|
||||
|
||||
@@ -5,6 +5,6 @@ PT:0/0
|
||||
K:Vigilance
|
||||
S:Mode$ Continuous | Affected$ Card.Self | AddPower$ X | AddToughness$ X | Description$ CARDNAME gets +1/+1 for each color among permanents you control.
|
||||
SVar:X:Count$Valid Permanent.YouCtrl$Colors
|
||||
A:AB$ Mana | Cost$ T | Produced$ Special EachColorAmong_Permanent.YouCtrl | SpellDescription$ For each color among permanents you control, add one mana of that color.
|
||||
AI:RemoveDeck:All
|
||||
A:AB$ Mana | Cost$ T | Produced$ Special EachColorAmong_Valid Permanent.YouCtrl | SpellDescription$ For each color among permanents you control, add one mana of that color.
|
||||
SVar:NoZeroToughnessAI:True
|
||||
Oracle:Vigilance\nFaeburrow Elder gets +1/+1 for each color among permanents you control.\n{T}: For each color among permanents you control, add one mana of that color.
|
||||
|
||||
@@ -4,5 +4,4 @@ Types:Artifact
|
||||
A:AB$ PutCounter | Cost$ 1 T | RememberCostMana$ True | CounterType$ CHARGE | CounterNum$ 1 | CheckSVar$ X | SVarCompare$ EQ0 | SpellDescription$ Put a charge counter on CARDNAME. Note the type of mana spent to pay this activation cost. Activate only if there are no charge counters on CARDNAME.
|
||||
SVar:X:Count$CardCounters.CHARGE
|
||||
A:AB$ Mana | Cost$ T SubCounter<1/CHARGE> | Produced$ Special LastNotedType | SpellDescription$ Add one mana of CARDNAME's last noted type.
|
||||
AI:RemoveDeck:All
|
||||
Oracle:{1}, {T}: Put a charge counter on Jeweled Amulet. Note the type of mana spent to pay this activation cost. Activate only if there are no charge counters on Jeweled Amulet.\n{T}, Remove a charge counter from Jeweled Amulet: Add one mana of Jeweled Amulet's last noted type.
|
||||
|
||||
@@ -21,5 +21,5 @@ K:Vigilance
|
||||
K:Haste
|
||||
S:Mode$ Continuous | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ CARDNAME's power and toughness are each equal to the number of colors among the exiled cards used to craft it.
|
||||
SVar:X:ExiledWith$Colors
|
||||
A:AB$ Mana | Cost$ T | Produced$ Special EachColorAmongDefined_ExiledWith | SpellDescription$ For each color among the exiled cards used to craft CARDNAME, add one mana of that color.
|
||||
A:AB$ Mana | Cost$ T | Produced$ Special EachColorAmong_ExiledWith | SpellDescription$ For each color among the exiled cards used to craft CARDNAME, add one mana of that color.
|
||||
Oracle:Flying, vigilance, haste\nSunbird Effigy's power and toughness are each equal to the number of colors among the exiled cards used to craft it.\n{T}: For each color among the exiled cards used to craft Sunbird Effigy, add one mana of that color.
|
||||
|
||||
@@ -6,5 +6,5 @@ SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True
|
||||
K:ETBReplacement:Other:ChooseColor
|
||||
SVar:ChooseColor:DB$ ChooseColor | Defined$ You | AILogic$ MostProminentInComputerDeck | SpellDescription$ As CARDNAME enters, choose a color.
|
||||
A:AB$ Mana | Cost$ T | Produced$ Chosen | SpellDescription$ Add one mana of the chosen color.
|
||||
A:AB$ Mana | Cost$ 1 T | Produced$ Special EachColorAmong_Permanent.YouCtrl+MonoColor | SpellDescription$ For each color among monocolored permanents you control, add one mana of that color.
|
||||
A:AB$ Mana | Cost$ 1 T | Produced$ Special EachColorAmong_Valid Permanent.YouCtrl+MonoColor | SpellDescription$ For each color among monocolored permanents you control, add one mana of that color.
|
||||
Oracle:Tarnation Vista enters tapped. As it enters, choose a color.\n{T}: Add one mana of the chosen color.\n{1}, {T}: For each color among monocolored permanents you control, add one mana of that color.
|
||||
|
||||
@@ -4,6 +4,5 @@ Types:Creature Elf Druid
|
||||
PT:2/2
|
||||
A:AB$ Mana | Cost$ T | Produced$ Any | Amount$ X | SpellDescription$ Add X mana of any one color, where X is the number of Elves on the battlefield.
|
||||
SVar:X:Count$Valid Elf
|
||||
AI:RemoveDeck:All
|
||||
AI:RemoveDeck:Random
|
||||
DeckHints:Type$Elf
|
||||
Oracle:{T}: Add X mana of any one color, where X is the number of Elves on the battlefield.
|
||||
|
||||
Reference in New Issue
Block a user