diff --git a/res/cardsfolder/c/crumbling_colossus.txt b/res/cardsfolder/c/crumbling_colossus.txt index 63844bb3f65..ea92de7ce88 100644 --- a/res/cardsfolder/c/crumbling_colossus.txt +++ b/res/cardsfolder/c/crumbling_colossus.txt @@ -7,7 +7,6 @@ K:Trample T:Mode$ Attacks | ValidCard$ Card.Self | DelayedTrigger$ DelTrig | TriggerDescription$ When CARDNAME attacks, sacrifice it at end of combat. SVar:DelTrig:Mode$ Phase | Phase$ EndCombat | ValidPlayer$ Player | Execute$ TrigSacrifice | TriggerDescription$ Sacrifice CARDNAME at end of combat. SVar:TrigSacrifice:AB$ Sacrifice | Cost$ 0 | Defined$ Self -SVar:RemAIDeck:True SVar:Rarity:Uncommon SVar:Picture:http://www.wizards.com/global/images/magic/general/crumbling_colossus.jpg SetInfo:M12|Uncommon|http://magiccards.info/scans/en/m12/204.jpg diff --git a/res/cardsfolder/d/diabolic_tutor.txt b/res/cardsfolder/d/diabolic_tutor.txt index 1029882b7fe..ff425bffb93 100644 --- a/res/cardsfolder/d/diabolic_tutor.txt +++ b/res/cardsfolder/d/diabolic_tutor.txt @@ -3,7 +3,6 @@ ManaCost:2 B B Types:Sorcery Text:no text A:SP$ ChangeZone | Cost$ 2 B B | Origin$ Library | Destination$ Hand | ChangeType$ Card | ChangeNum$ 1 | Mandatory$ True | SpellDescription$ Search your library for a card and put that card into your hand. Then shuffle your library. -SVar:RemAIDeck:True SVar:Rarity:Uncommon SVar:Picture:http://www.wizards.com/global/images/magic/general/diabolic_tutor.jpg SetInfo:8ED|Uncommon|http://magiccards.info/scans/en/8e/128.jpg diff --git a/res/cardsfolder/r/reclaim.txt b/res/cardsfolder/r/reclaim.txt index e143329f607..e31cb10f196 100644 --- a/res/cardsfolder/r/reclaim.txt +++ b/res/cardsfolder/r/reclaim.txt @@ -3,7 +3,6 @@ ManaCost:G Types:Instant Text:no text A:SP$ ChangeZone | Cost$ G | Origin$ Graveyard | Destination$ Library | LibraryPosition$ 0 | TgtPrompt$ Choose target card in your graveyard | ValidTgts$ Card.YouCtrl | SpellDescription$ Put target card from your graveyard on top of your library. -SVar:RemAIDeck:True SVar:Rarity:Common SVar:Picture:http://www.wizards.com/global/images/magic/general/reclaim.jpg SetInfo:EXO|Common|http://magiccards.info/scans/en/ex/120.jpg diff --git a/src/main/java/forge/card/abilityfactory/AbilityFactoryChangeZone.java b/src/main/java/forge/card/abilityfactory/AbilityFactoryChangeZone.java index 59b16edf9d3..393f81a39bc 100644 --- a/src/main/java/forge/card/abilityfactory/AbilityFactoryChangeZone.java +++ b/src/main/java/forge/card/abilityfactory/AbilityFactoryChangeZone.java @@ -43,9 +43,11 @@ import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbilityStackInstance; import forge.card.spellability.SpellPermanent; import forge.card.spellability.Target; +import forge.game.phase.Combat; import forge.game.phase.CombatUtil; import forge.game.phase.PhaseType; import forge.game.player.ComputerUtil; +import forge.game.player.ComputerUtilBlock; import forge.game.player.Player; import forge.game.zone.PlayerZone; import forge.game.zone.ZoneType; @@ -1111,12 +1113,14 @@ public final class AbilityFactoryChangeZone { } // Improve the AI for fetching. - Card c; + Card c = null; if (params.containsKey("AtRandom")) { c = CardUtil.getRandom(fetchList.toArray()); } else if (defined) { c = fetchList.get(0); } else { + fetchList.shuffle(); + // Save a card as a default, in case we can't find anything suitable. Card first = fetchList.get(0); fetchList = fetchList.filter(new CardListFilter() { @Override @@ -1130,19 +1134,15 @@ public final class AbilityFactoryChangeZone { } }); if (type.contains("Basic")) { - c = AbilityFactoryChangeZone.basicManaFixing(fetchList); + c = AbilityFactoryChangeZone.basicManaFixing(fetchList); } else if (AbilityFactoryChangeZone.areAllBasics(type)) { c = AbilityFactoryChangeZone.basicManaFixing(fetchList, type); } else if (fetchList.getNotType("Creature").size() == 0) { - c = CardFactoryUtil.getBestCreatureAI(fetchList); // if only - // creatures - // take the - // best + c = AbilityFactoryChangeZone.chooseCreature(fetchList); } else if (ZoneType.Battlefield.equals(destination) || ZoneType.Graveyard.equals(destination)) { c = CardFactoryUtil.getMostExpensivePermanentAI(fetchList, sa, false); } else if (ZoneType.Exile.equals(destination)) { - // Exiling your own stuff, if Exiling opponents stuff choose - // best + // Exiling your own stuff, if Exiling opponents stuff choose best if (destZone.getPlayer().isHuman()) { c = CardFactoryUtil.getMostExpensivePermanentAI(fetchList, sa, false); } else { @@ -1153,13 +1153,49 @@ public final class AbilityFactoryChangeZone { if (origin.contains(ZoneType.Library) && !fetchList.getNotName(card.getName()).isEmpty()) { fetchList = fetchList.getNotName(card.getName()); } - fetchList.shuffle(); - c = fetchList.get(0); + Player ai = AllZone.getComputerPlayer(); + // Does AI need a land? + CardList hand = ai.getCardsIn(ZoneType.Hand); + System.out.println("Lands in hand = " + hand.filter(CardListFilter.LANDS).size() + ", on battlefield = " + ai.getCardsIn(ZoneType.Battlefield).filter(CardListFilter.LANDS).size()); + if (hand.filter(CardListFilter.LANDS).size() == 0 && ai.getCardsIn(ZoneType.Battlefield).filter(CardListFilter.LANDS).size() < 4) { + boolean canCastSomething = false; + for (Card cardInHand : hand) { + canCastSomething |= ComputerUtil.payManaCost(cardInHand.getFirstSpellAbility(), AllZone.getComputerPlayer(), true, 0, false); + } + if (!canCastSomething) { + System.out.println("Pulling a land as there are none in hand, less than 4 on the board, and nothing in hand is castable."); + c = basicManaFixing(fetchList); + } + } + if (c == null) { + System.out.println("Don't need a land or none available; trying for a creature."); + fetchList = fetchList.getNotType("Land"); + // Prefer to pull a creature, generally more useful for AI. + c = chooseCreature(fetchList.getType("Creature")); + } + if (c == null) { // Could not find a creature. + if (ai.getLife() <= 5) { // Desperate? + // Get something AI can cast soon. + System.out.println("5 Life or less, trying to find something castable."); + CardListUtil.sortByMostExpensive(fetchList); + for (Card potentialCard : fetchList) { + if (ComputerUtil.payManaCost(potentialCard.getFirstSpellAbility(), AllZone.getComputerPlayer(), true, 0, false)) { + c = potentialCard; + break; + } + } + } else { + // Get the best card in there. + System.out.println("No creature and lots of life, finding something good."); + c = CardFactoryUtil.getBestAI(fetchList); + } + } } if (c == null) { c = first; } } + System.out.println("Chose " + c.toString()); fetched.add(c); fetchList.remove(c); @@ -1336,6 +1372,38 @@ public final class AbilityFactoryChangeZone { return true; } + + /** + * Some logic for picking a creature card from a list. + * @param list + * @return Card + */ + private static Card chooseCreature(CardList list) { + Card card = null; + Combat combat = new Combat(); + combat.initiatePossibleDefenders(AllZone.getComputerPlayer()); + CardList attackers = AllZoneUtil.getCreaturesInPlay(AllZone.getHumanPlayer()); + for (Card att : attackers) { + combat.addAttacker(att); + } + combat = ComputerUtilBlock.getBlockers(combat, AllZoneUtil.getCreaturesInPlay(AllZone.getComputerPlayer())); + + System.out.println("Life would remain = " + CombatUtil.lifeThatWouldRemain(combat)); + if (CombatUtil.lifeInDanger(combat)) { + // need something AI can cast now + CardListUtil.sortByEvaluateCreature(list); + for (Card c : list) { + if (ComputerUtil.payManaCost(c.getFirstSpellAbility(), AllZone.getComputerPlayer(), true, 0, false)) { + card = c; + break; + } + } + } else { + // not urgent, get the largest creature possible + card = CardFactoryUtil.getBestCreatureAI(list); + } + return card; + } // ************************************************************************************* // **************** Known Origin (Battlefield/Graveyard/Exile) @@ -1508,6 +1576,7 @@ public final class AbilityFactoryChangeZone { CardList list = AllZoneUtil.getCardsIn(origin); list = list.getValidCards(tgt.getValidTgts(), AllZone.getComputerPlayer(), source); + list = list.getNotName(source.getName()); // Don't get the same card back. if (list.size() < tgt.getMinTargets(sa.getSourceCard(), sa)) { return false; @@ -1523,7 +1592,6 @@ public final class AbilityFactoryChangeZone { aiPermanents = aiPermanents.filter(new CardListFilter() { @Override public boolean addCard(final Card c) { - System.out.println("Not Changing Zone"); return !c.getSVar("Targeting").equals("Dies"); } }); @@ -1574,15 +1642,8 @@ public final class AbilityFactoryChangeZone { } // counters TODO check good and // bad counters - return SpellPermanent.checkETBEffects(c, null, null); // checks - // only - // if - // there - // is - // a - // dangerous - // ETB - // effect + // checks only if there is a dangerous ETB effect + return SpellPermanent.checkETBEffects(c, null, null); } }); if (!aiPermanents.isEmpty()) { @@ -1666,11 +1727,34 @@ public final class AbilityFactoryChangeZone { } else { choice = mostExpensive; } + } else if (destination.equals(ZoneType.Hand) || destination.equals(ZoneType.Library)) { + CardList nonLands = list.getNotType("Land"); + // Prefer to pull a creature, generally more useful for AI. + choice = chooseCreature(nonLands.getType("Creature")); + if (choice == null) { // Could not find a creature. + if (AllZone.getComputerPlayer().getLife() <= 5) { // Desperate? + // Get something AI can cast soon. + System.out.println("5 Life or less, trying to find something castable."); + CardListUtil.sortByMostExpensive(nonLands); + for (Card potentialCard : nonLands) { + if (ComputerUtil.payManaCost(potentialCard.getFirstSpellAbility(), AllZone.getComputerPlayer(), true, 0, false)) { + choice = potentialCard; + break; + } + } + } else { + // Get the best card in there. + System.out.println("No creature and lots of life, finding something good."); + choice = CardFactoryUtil.getBestAI(nonLands); + } + } + if (choice == null) { + // No creatures or spells? + list.shuffle(); + choice = list.get(0); + } } else { - // TODO AI needs more improvement to it's retrieval (reuse - // some code from spReturn here) - list.shuffle(); - choice = list.get(0); + choice = CardFactoryUtil.getBestAI(list); } } if (choice == null) { // can't find anything left @@ -1680,7 +1764,9 @@ public final class AbilityFactoryChangeZone { } return false; } else { - // TODO is this good enough? for up to amounts? + if (!ComputerUtil.shouldCastLessThanMax(source)) { + return false; + } break; } } @@ -1691,7 +1777,7 @@ public final class AbilityFactoryChangeZone { return true; } - + /** *
* changeKnownUnpreferredTarget. @@ -1754,11 +1840,34 @@ public final class AbilityFactoryChangeZone { choice = CardFactoryUtil.getBestCreatureToBounceAI(list); } else if (destination.equals(ZoneType.Battlefield) || origin.equals(ZoneType.Battlefield)) { choice = CardFactoryUtil.getMostExpensivePermanentAI(list, sa, false); + } else if (destination.equals(ZoneType.Hand) || destination.equals(ZoneType.Library)) { + CardList nonLands = list.getNotType("Land"); + // Prefer to pull a creature, generally more useful for AI. + choice = chooseCreature(nonLands.getType("Creature")); + if (choice == null) { // Could not find a creature. + if (AllZone.getComputerPlayer().getLife() <= 5) { // Desperate? + // Get something AI can cast soon. + System.out.println("5 Life or less, trying to find something castable."); + CardListUtil.sortByMostExpensive(nonLands); + for (Card potentialCard : nonLands) { + if (ComputerUtil.payManaCost(potentialCard.getFirstSpellAbility(), AllZone.getComputerPlayer(), true, 0, false)) { + choice = potentialCard; + break; + } + } + } else { + // Get the best card in there. + System.out.println("No creature and lots of life, finding something good."); + choice = CardFactoryUtil.getBestAI(nonLands); + } + } + if (choice == null) { + // No creatures or spells? + list.shuffle(); + choice = list.get(0); + } } else { - // TODO AI needs more improvement to it's retrieval (reuse - // some code from spReturn here) - list.shuffle(); - choice = list.get(0); + choice = CardFactoryUtil.getBestAI(list); } } if (choice == null) { // can't find anything left @@ -1766,7 +1875,9 @@ public final class AbilityFactoryChangeZone { tgt.resetTargets(); return false; } else { - // TODO is this good enough? for up to amounts? + if (!ComputerUtil.shouldCastLessThanMax(source)) { + return false; + } break; } } diff --git a/src/main/java/forge/card/abilityfactory/AbilityFactoryPermanentState.java b/src/main/java/forge/card/abilityfactory/AbilityFactoryPermanentState.java index 9e55b3fd3f0..c5d368e316e 100644 --- a/src/main/java/forge/card/abilityfactory/AbilityFactoryPermanentState.java +++ b/src/main/java/forge/card/abilityfactory/AbilityFactoryPermanentState.java @@ -36,12 +36,9 @@ import forge.card.spellability.AbilitySub; import forge.card.spellability.Spell; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; -import forge.game.phase.Combat; -import forge.game.phase.CombatUtil; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.ComputerUtil; -import forge.game.player.ComputerUtilBlock; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiUtils; @@ -1018,7 +1015,7 @@ public class AbilityFactoryPermanentState { } return false; } else { - if (!tapCastLessThanMax(source)) { + if (!ComputerUtil.shouldCastLessThanMax(source)) { return false; } break; @@ -1044,7 +1041,7 @@ public class AbilityFactoryPermanentState { } return false; } else { - if (!tapCastLessThanMax(source)) { + if (!ComputerUtil.shouldCastLessThanMax(source)) { return false; } break; @@ -1058,31 +1055,6 @@ public class AbilityFactoryPermanentState { return true; } - /** - * Is it OK to cast this for less than the Max Targets? - * @param source - */ - private static boolean tapCastLessThanMax(final Card source) { - boolean ret = true; - if (source.getManaCost().countX() > 0) { - // If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by mana available. - } else { - // Otherwise, if life is possibly in danger, then this is fine. - Combat combat = new Combat(); - combat.initiatePossibleDefenders(AllZone.getComputerPlayer()); - CardList attackers = AllZoneUtil.getCreaturesInPlay(AllZone.getHumanPlayer()); - for (Card att : attackers) { - combat.addAttacker(att); - } - combat = ComputerUtilBlock.getBlockers(combat, AllZoneUtil.getCreaturesInPlay(AllZone.getComputerPlayer())); - if (!CombatUtil.lifeInDanger(combat)) { - // Otherwise, return false. Do not play now. - ret = false; - } - } - return ret; - } - /** *
* tapUnpreferredTargeting.
@@ -1168,7 +1140,7 @@ public class AbilityFactoryPermanentState {
}
return false;
} else {
- if (!tapCastLessThanMax(source)) {
+ if (!ComputerUtil.shouldCastLessThanMax(source)) {
return false;
}
break;
@@ -1189,7 +1161,7 @@ public class AbilityFactoryPermanentState {
}
return false;
} else {
- if (!tapCastLessThanMax(source)) {
+ if (!ComputerUtil.shouldCastLessThanMax(source)) {
return false;
}
break;
diff --git a/src/main/java/forge/card/cardfactory/CardFactoryInstants.java b/src/main/java/forge/card/cardfactory/CardFactoryInstants.java
index 22d4267d312..a65c6af5964 100644
--- a/src/main/java/forge/card/cardfactory/CardFactoryInstants.java
+++ b/src/main/java/forge/card/cardfactory/CardFactoryInstants.java
@@ -266,7 +266,7 @@ public class CardFactoryInstants {
public boolean canPlayAI() {
final CardList graveList = AllZone.getHumanPlayer().getCardsIn(ZoneType.Graveyard);
- final int maxX = ComputerUtil.getAvailableMana().size() - 1;
+ final int maxX = ComputerUtil.getAvailableMana(true).size() - 1;
return (maxX >= 3) && (graveList.size() > 0);
}
};
diff --git a/src/main/java/forge/card/cardfactory/CardFactorySorceries.java b/src/main/java/forge/card/cardfactory/CardFactorySorceries.java
index 5744ca77ee7..fcb92e14208 100644
--- a/src/main/java/forge/card/cardfactory/CardFactorySorceries.java
+++ b/src/main/java/forge/card/cardfactory/CardFactorySorceries.java
@@ -393,7 +393,7 @@ public class CardFactorySorceries {
// the computer will at least destroy 2 more human creatures
return ((computer.size() < (human.size() - 1)) || ((AllZone.getComputerPlayer().getLife() < 7) && !human
- .isEmpty())) && (ComputerUtil.getAvailableMana().size() >= 7);
+ .isEmpty())) && (ComputerUtil.getAvailableMana(true).size() >= 7);
}
}; // SpellAbility
@@ -905,7 +905,7 @@ public class CardFactorySorceries {
@Override
public boolean canPlayAI() {
- final int maxX = ComputerUtil.getAvailableMana().size() - 1;
+ final int maxX = ComputerUtil.getAvailableMana(true).size() - 1;
final int humanLife = AllZone.getHumanPlayer().getLife();
if (maxX >= humanLife) {
targetPlayers.add(AllZone.getHumanPlayer());
diff --git a/src/main/java/forge/game/player/ComputerUtil.java b/src/main/java/forge/game/player/ComputerUtil.java
index a25de470a68..73912ab4a00 100644
--- a/src/main/java/forge/game/player/ComputerUtil.java
+++ b/src/main/java/forge/game/player/ComputerUtil.java
@@ -125,7 +125,7 @@ public class ComputerUtil {
if (sa instanceof AbilityStatic) {
final Cost cost = sa.getPayCosts();
- if (cost == null && ComputerUtil.payManaCost(sa, AllZone.getComputerPlayer(), false, 0)) {
+ if (cost == null && ComputerUtil.payManaCost(sa, AllZone.getComputerPlayer(), false, 0, true)) {
sa.resolve();
} else {
final CostPayment pay = new CostPayment(cost, sa);
@@ -473,7 +473,7 @@ public class ComputerUtil {
* @return a boolean.
*/
public static boolean canPayCost(final SpellAbility sa, final Player player) {
- if (!ComputerUtil.payManaCost(sa, player, true, 0)) {
+ if (!ComputerUtil.payManaCost(sa, player, true, 0, true)) {
return false;
}
@@ -510,7 +510,7 @@ public class ComputerUtil {
int xMana = 0;
for (int i = 1; i < 99; i++) {
- if (!ComputerUtil.payManaCost(sa, player, true, i)) {
+ if (!ComputerUtil.payManaCost(sa, player, true, i, true)) {
break;
}
xMana = i;
@@ -563,7 +563,7 @@ public class ComputerUtil {
* a {@link forge.card.spellability.SpellAbility} object.
*/
public static void payManaCost(final SpellAbility sa) {
- ComputerUtil.payManaCost(sa, AllZone.getComputerPlayer(), false, 0);
+ ComputerUtil.payManaCost(sa, AllZone.getComputerPlayer(), false, 0, true);
}
/**
@@ -579,67 +579,17 @@ public class ComputerUtil {
* (is for canPayCost, if true does not change the game state)
* @param extraMana
* a int.
+ * @param checkPlayable
+ * should we check if playable? use for hypothetical "can AI play this"
* @return a boolean.
* @since 1.0.15
*/
public static boolean payManaCost(final SpellAbility sa, final Player player, final boolean test,
- final int extraMana) {
- final String mana = sa.getPayCosts() != null ? sa.getPayCosts().getTotalMana() : sa.getManaCost();
-
- ManaCost cost = new ManaCost(mana);
-
- cost = Singletons.getModel().getGameAction().getSpellCostChange(sa, cost);
+ final int extraMana, boolean checkPlayable) {
+ ManaCost cost = calculateManaCost(sa, test, extraMana);
final ManaPool manapool = player.getManaPool();
- final Card card = sa.getSourceCard();
- // Tack xMana Payments into mana here if X is a set value
- if ((sa.getPayCosts() != null) && (cost.getXcounter() > 0)) {
-
- int manaToAdd = 0;
- if (test && (extraMana > 0)) {
- manaToAdd = extraMana * cost.getXcounter();
- } else {
- // For Count$xPaid set PayX in the AFs then use that here
- // Else calculate it as appropriate.
- final String xSvar = card.getSVar("X").startsWith("Count$xPaid") ? "PayX" : "X";
- if (!card.getSVar(xSvar).equals("")) {
- if (xSvar.equals("PayX")) {
- manaToAdd = Integer.parseInt(card.getSVar(xSvar)) * cost.getXcounter(); // X
- } else {
- manaToAdd = AbilityFactory.calculateAmount(card, xSvar, sa) * cost.getXcounter();
- }
- }
- }
-
- cost.increaseColorlessMana(manaToAdd);
- if (!test) {
- card.setXManaCostPaid(manaToAdd);
- }
- }
-
- // Make mana needed to avoid negative effect a mandatory cost for the AI
- if (card.getSVar("ManaNeededToAvoidNegativeEffect") != "") {
- final String[] negEffects = card.getSVar("ManaNeededToAvoidNegativeEffect").split(",");
- int amountAdded = 0;
- for (int nStr = 0; nStr < negEffects.length; nStr++) {
- // convert long color strings to short color strings
- if (negEffects[nStr].length() > 1) {
- negEffects[nStr] = InputPayManaCostUtil.getShortColorString(negEffects[nStr]);
- }
- // make mana mandatory for AI
- if (!cost.isColor(negEffects[nStr])) {
- cost.combineManaCost(negEffects[nStr]);
- amountAdded++;
- }
- }
- cost.setManaNeededToAvoidNegativeEffect(negEffects);
- // TODO: should it be an error condition if amountAdded is greater
- // than the colorless in the original cost? (ArsenalNut - 120102)
- // adjust colorless amount to account for added mana
- cost.decreaseColorlessMana(amountAdded);
- }
-
cost = manapool.payManaFromPool(sa, cost);
if (cost.isPaid()) {
@@ -649,82 +599,12 @@ public class ComputerUtil {
}
// get map of mana abilities
- final HashMap
* getProduceableColors.
@@ -914,11 +943,12 @@ public class ComputerUtil {
*
* getAvailableMana.
*