mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 11:18:01 +00:00
- Highly experimental: added a way for the AI to reserve mana for casting a Main 2 phase spell when deciding whether to pump a creature or not. This may not be optimal and probably needs refactoring as well. It's a somewhat drastic changes so bugs may arise (though the main use cases were tested). Please assist if possible.
This commit is contained in:
@@ -32,12 +32,14 @@ import com.esotericsoftware.minlog.Log;
|
||||
import com.google.common.base.Function;
|
||||
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.Lists;
|
||||
|
||||
import forge.card.CardType;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.card.mana.ManaCostShard;
|
||||
import forge.deck.CardPool;
|
||||
import forge.deck.Deck;
|
||||
import forge.deck.DeckSection;
|
||||
@@ -60,6 +62,7 @@ import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostDiscard;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
@@ -634,6 +637,33 @@ public class AiController {
|
||||
return bestSA;
|
||||
} // playCounterSpell()
|
||||
|
||||
public SpellAbility evaluateSpellInMain2(ApiType exceptSA) {
|
||||
final List<Card> cards = getAvailableCards();
|
||||
|
||||
ArrayList<SpellAbility> all = getSpellAbilities(cards);
|
||||
Collections.sort(all, saComparator); // put best spells first
|
||||
|
||||
for (final SpellAbility sa : getOriginalAndAltCostAbilities(all)) {
|
||||
if (sa.getApi() == ApiType.Counter || sa.getApi() == exceptSA) {
|
||||
continue;
|
||||
}
|
||||
sa.setActivatingPlayer(player);
|
||||
if (sa.canPlay() && !ComputerUtil.castPermanentInMain1(player, sa) && sa.getHostCard() != null && !sa.getHostCard().getType().contains("Land") && ComputerUtilCost.canPayCost(sa, player)) {
|
||||
return sa;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void reserveManaSourcesForMain2(SpellAbility sa) {
|
||||
ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0);
|
||||
ArrayList<Card> manaSources = ComputerUtilMana.getManaSourcesToPayCost(cost, sa, player);
|
||||
for (Card c : manaSources) {
|
||||
((PlayerControllerAi)player.getController()).getAi().getCardMemory().rememberCard(c, AiCardMemory.MemorySet.HELD_MANA_SOURCES);
|
||||
}
|
||||
}
|
||||
|
||||
// This is for playing spells regularly (no Cascade/Ripple etc.)
|
||||
private AiPlayDecision canPlayAndPayFor(final SpellAbility sa) {
|
||||
if (!sa.canPlay()) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import forge.game.mana.Mana;
|
||||
import forge.game.mana.ManaCostAdjustment;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.mana.ManaPool;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.spellability.AbilityManaPart;
|
||||
@@ -230,6 +231,121 @@ public class ComputerUtilMana {
|
||||
|
||||
}
|
||||
|
||||
public static ArrayListMultimap<ManaCostShard, SpellAbility> getManaSourcesToPayCost(final Player ai, final ManaCostBeingPaid cost) {
|
||||
final ArrayListMultimap<Integer, SpellAbility> manaAbilityMap = ComputerUtilMana.groupSourcesByManaColor(ai, true);
|
||||
ArrayListMultimap<ManaCostShard, SpellAbility> sourcesForShards = ComputerUtilMana.groupAndOrderToPayShards(ai, manaAbilityMap, cost);
|
||||
sortManaAbilities(sourcesForShards);
|
||||
return sourcesForShards;
|
||||
}
|
||||
|
||||
public static ArrayList<Card> getManaSourcesToPayCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai) {
|
||||
ArrayList<Card> manaSources = new ArrayList<Card>();
|
||||
|
||||
adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai);
|
||||
List<Mana> manaSpentToPay = new ArrayList<Mana>();
|
||||
|
||||
final ManaPool manapool = ai.getManaPool();
|
||||
List<ManaCostShard> unpaidShards = cost.getUnpaidShards();
|
||||
Collections.sort(unpaidShards); // most difficult shards must come first
|
||||
for (ManaCostShard part : unpaidShards) {
|
||||
if (part != ManaCostShard.X) {
|
||||
if (cost.isPaid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get a mana of this type from floating, bail if none available
|
||||
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction());
|
||||
if (mana != null) {
|
||||
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana)) {
|
||||
manaSpentToPay.add(0, mana);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cost.isPaid()) {
|
||||
// refund any mana taken from mana pool when test
|
||||
refundMana(manaSpentToPay, ai, sa);
|
||||
|
||||
handleOfferingsAI(sa, true, cost.isPaid());
|
||||
return manaSources;
|
||||
}
|
||||
|
||||
// arrange all mana abilities by color produced.
|
||||
final ArrayListMultimap<Integer, SpellAbility> manaAbilityMap = ComputerUtilMana.groupSourcesByManaColor(ai, true);
|
||||
if (manaAbilityMap.isEmpty()) {
|
||||
refundMana(manaSpentToPay, ai, sa);
|
||||
|
||||
handleOfferingsAI(sa, true, cost.isPaid());
|
||||
return manaSources;
|
||||
}
|
||||
|
||||
// select which abilities may be used for each shard
|
||||
ArrayListMultimap<ManaCostShard, SpellAbility> sourcesForShards = ComputerUtilMana.groupAndOrderToPayShards(ai, manaAbilityMap, cost);
|
||||
|
||||
sortManaAbilities(sourcesForShards);
|
||||
|
||||
ManaCostShard toPay = null;
|
||||
// Loop over mana needed
|
||||
while (!cost.isPaid()) {
|
||||
toPay = getNextShardToPay(cost);
|
||||
|
||||
Collection<SpellAbility> saList = sourcesForShards.get(toPay);
|
||||
SpellAbility saPayment = null;
|
||||
if (saList != null) {
|
||||
for (final SpellAbility ma : saList) {
|
||||
if (ma.getHostCard() == sa.getHostCard()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String typeRes = cost.getSourceRestriction();
|
||||
if (StringUtils.isNotBlank(typeRes) && !ma.getHostCard().isType(typeRes)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (canPayShardWithSpellAbility(toPay, ai, ma, sa, true)) {
|
||||
saPayment = ma;
|
||||
manaSources.add(saPayment.getHostCard());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if (saPayment == null) {
|
||||
if (!toPay.isPhyrexian() || !ai.canPayLife(2)) {
|
||||
break; // cannot pay
|
||||
}
|
||||
|
||||
cost.payPhyrexian();
|
||||
continue;
|
||||
}
|
||||
|
||||
setExpressColorChoice(sa, ai, cost, toPay, saPayment);
|
||||
|
||||
String manaProduced = toPay.isSnow() ? "S" : GameActionUtil.generatedMana(saPayment);
|
||||
manaProduced = AbilityManaPart.applyManaReplacement(saPayment, manaProduced);
|
||||
//System.out.println(manaProduced);
|
||||
payMultipleMana(cost, manaProduced, ai);
|
||||
|
||||
// remove from available lists
|
||||
Iterator<SpellAbility> itSa = sourcesForShards.values().iterator();
|
||||
while (itSa.hasNext()) {
|
||||
SpellAbility srcSa = itSa.next();
|
||||
if (srcSa.getHostCard().equals(saPayment.getHostCard())) {
|
||||
itSa.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleOfferingsAI(sa, true, cost.isPaid());
|
||||
|
||||
refundMana(manaSpentToPay, ai, sa);
|
||||
|
||||
return manaSources;
|
||||
} // getManaSourcesToPayCost()
|
||||
|
||||
private static boolean payManaCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean test, boolean checkPlayable) {
|
||||
adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai);
|
||||
List<Mana> manaSpentToPay = test ? new ArrayList<Mana>() : sa.getPayingMana();
|
||||
@@ -514,7 +630,11 @@ public class ComputerUtilMana {
|
||||
}
|
||||
|
||||
private static boolean canPayShardWithSpellAbility(ManaCostShard toPay, Player ai, SpellAbility ma, SpellAbility sa, boolean checkCosts) {
|
||||
final Card sourceCard = ma.getHostCard();
|
||||
Card sourceCard = ma.getHostCard();
|
||||
|
||||
if (isManaSourceReserved(ai, sourceCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (toPay.isSnow() && !sourceCard.isSnow()) { return false; }
|
||||
|
||||
@@ -556,6 +676,18 @@ public class ComputerUtilMana {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isManaSourceReserved(Player ai, Card sourceCard) {
|
||||
if (ai.getGame().getPhaseHandler().getPhase() == PhaseType.MAIN2) {
|
||||
((PlayerControllerAi)ai.getController()).getAi().getCardMemory().clearRememberedManaSources();
|
||||
} else {
|
||||
if (((PlayerControllerAi)ai.getController()).getAi().getCardMemory().isRememberedCard(sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES)) {
|
||||
// This mana source is held elsewhere for a Main Phase 2 spell.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private static ManaCostShard getNextShardToPay(ManaCostBeingPaid cost) {
|
||||
// mind the priorities
|
||||
|
||||
@@ -5,6 +5,7 @@ import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
@@ -48,6 +49,11 @@ public class PumpAi extends PumpAiBase {
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
SpellAbility futureSpell = ((PlayerControllerAi)ai.getController()).getAi().evaluateSpellInMain2(ApiType.Pump);
|
||||
if (futureSpell != null && futureSpell.getHostCard() != null) {
|
||||
((PlayerControllerAi)ai.getController()).getAi().reserveManaSourcesForMain2(futureSpell);
|
||||
}
|
||||
|
||||
final Cost cost = sa.getPayCosts();
|
||||
final Game game = ai.getGame();
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
|
||||
Reference in New Issue
Block a user