AI: Account for transformable cards (e.g. Incubator tokens) when considering defenders (#3232)

* - AI: Account for transformable cards (e.g. Incubator tokens) when looking for possible defenders.

* - Check if the transformed card is actually a creature

* - Some recommended improvements to the logic
- Comment out a block of code which might not be as useful under the current rules and with contemporary cards anymore

* - Style fix

* - Attempt to count the total mana spent on transforming stuff when determining how many extra defenders are available.

* - Check pay costs for Animate as well as for Transform.
- NPE guard for pay costs check.
This commit is contained in:
Agetian
2023-06-11 12:14:12 +03:00
committed by GitHub
parent cda99e3f38
commit a27f3a9550
2 changed files with 33 additions and 20 deletions

View File

@@ -17,33 +17,17 @@
*/
package forge.ai;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import forge.game.staticability.StaticAbility;
import forge.game.staticability.StaticAbilityAssignCombatDamageAsUnblocked;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ability.AnimateAi;
import forge.card.CardTypeView;
import forge.game.GameEntity;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.ability.effects.ProtectEffect;
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.combat.GlobalAttackRestrictions;
@@ -53,6 +37,8 @@ import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityPredicates;
import forge.game.staticability.StaticAbility;
import forge.game.staticability.StaticAbilityAssignCombatDamageAsUnblocked;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.game.zone.Zone;
@@ -62,6 +48,12 @@ import forge.util.Expressions;
import forge.util.MyRandom;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView;
import org.apache.commons.lang3.tuple.Pair;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
@@ -135,6 +127,8 @@ public class AiAttackController {
public static List<Card> getOpponentCreatures(final Player defender) {
List<Card> defenders = defender.getCreaturesInPlay();
int totalMana = ComputerUtilMana.getAvailableManaEstimate(defender, true);
int manaReserved = 0; // for paying the cost to transform
Predicate<Card> canAnimate = new Predicate<Card>() {
@Override
public boolean apply(Card c) {
@@ -142,14 +136,33 @@ public class AiAttackController {
}
};
for (Card c : CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), canAnimate)) {
if (c.isToken() && c.getCopiedPermanent() == null) {
/* TODO: is the commented out code still necessary for anything?
if (c.isToken() && c.getCopiedPermanent() == null && !c.canTransform(null)) {
continue;
}
*/
for (SpellAbility sa : Iterables.filter(c.getSpellAbilities(), SpellAbilityPredicates.isApi(ApiType.Animate))) {
if (ComputerUtilCost.canPayCost(sa, defender, false)
&& sa.getRestrictions().checkOtherRestrictions(c, sa, defender)) {
Card animatedCopy = AnimateAi.becomeAnimated(c, sa);
defenders.add(animatedCopy);
int saCMC = sa.getPayCosts() != null && sa.getPayCosts().hasManaCost() ?
sa.getPayCosts().getTotalMana().getCMC() : 0; // FIXME: imprecise, only works 100% for colorless mana
if (totalMana - manaReserved >= saCMC) {
manaReserved += saCMC;
defenders.add(animatedCopy);
}
}
}
// Transform (e.g. Incubator tokens)
for (SpellAbility sa : Iterables.filter(c.getSpellAbilities(), SpellAbilityPredicates.isApi(ApiType.SetState))) {
Card transformedCopy = ComputerUtilCombat.canTransform(c);
if (transformedCopy.isCreature()) {
int saCMC = sa.getPayCosts() != null && sa.getPayCosts().hasManaCost() ?
sa.getPayCosts().getTotalMana().getCMC() : 0; // FIXME: imprecise, only works 100% for colorless mana
if (totalMana - manaReserved >= saCMC) {
manaReserved += saCMC;
defenders.add(transformedCopy);
}
}
}
}

View File

@@ -2307,7 +2307,7 @@ public class ComputerUtilCombat {
* @param original original creature
* @return transform creature if possible, original creature otherwise
*/
private final static Card canTransform(Card original) {
public final static Card canTransform(Card original) {
if (original.isTransformable() && !original.isInAlternateState()) {
for (SpellAbility sa : original.getSpellAbilities()) {
if (sa.getApi() == ApiType.SetState && ComputerUtilCost.canPayCost(sa, original.getController(), false)) {