mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 20:28:00 +00:00
- The AI will now anticipate regeneration abilities.
- Improved the AI of curse pump etb abilities. - Improved the AI of untap triggers.
This commit is contained in:
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -791,7 +791,7 @@ res/cardsfolder/blinking_spirit.txt -text svneol=native#text/plain
|
|||||||
res/cardsfolder/blinkmoth_infusion.txt -text svneol=native#text/plain
|
res/cardsfolder/blinkmoth_infusion.txt -text svneol=native#text/plain
|
||||||
res/cardsfolder/blinkmoth_nexus.txt -text svneol=native#text/plain
|
res/cardsfolder/blinkmoth_nexus.txt -text svneol=native#text/plain
|
||||||
res/cardsfolder/blinkmoth_well.txt -text svneol=native#text/plain
|
res/cardsfolder/blinkmoth_well.txt -text svneol=native#text/plain
|
||||||
res/cardsfolder/blister_beetle.txt svneol=native#text/plain
|
res/cardsfolder/blister_beetle.txt -text svneol=native#text/plain
|
||||||
res/cardsfolder/blistergrub.txt -text svneol=native#text/plain
|
res/cardsfolder/blistergrub.txt -text svneol=native#text/plain
|
||||||
res/cardsfolder/blistering_barrier.txt -text svneol=native#text/plain
|
res/cardsfolder/blistering_barrier.txt -text svneol=native#text/plain
|
||||||
res/cardsfolder/blistering_dieflyn.txt -text svneol=native#text/plain
|
res/cardsfolder/blistering_dieflyn.txt -text svneol=native#text/plain
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Types:Creature Insect
|
|||||||
Text:no text
|
Text:no text
|
||||||
PT:1/1
|
PT:1/1
|
||||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPump | TriggerDescription$ When CARDNAME enters the battlefield, target creature gets -1/-1 until end of turn.
|
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPump | TriggerDescription$ When CARDNAME enters the battlefield, target creature gets -1/-1 until end of turn.
|
||||||
SVar:TrigPump:AB$Pump | Cost$ 0 | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ -1 | NumDef$ -1 | IsCurse$ True
|
SVar:TrigPump:DB$Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ -1 | NumDef$ -1 | IsCurse$ True
|
||||||
SVar:PlayMain1:TRUE
|
SVar:PlayMain1:TRUE
|
||||||
SVar:Rarity:Common
|
SVar:Rarity:Common
|
||||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/blister_beetle.jpg
|
SVar:Picture:http://www.wizards.com/global/images/magic/general/blister_beetle.jpg
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ K:Trample
|
|||||||
SVar:AltCost:W U B R G
|
SVar:AltCost:W U B R G
|
||||||
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigChange | TriggerDescription$ At the beginning of your upkeep, you may pay 2 life. If you do, search your library for a card, then shuffle your library and put that card on top of it.
|
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigChange | TriggerDescription$ At the beginning of your upkeep, you may pay 2 life. If you do, search your library for a card, then shuffle your library and put that card on top of it.
|
||||||
SVar:TrigChange:AB$ChangeZone | Cost$ PayLife<2> | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Card | ChangeNum$ 1
|
SVar:TrigChange:AB$ChangeZone | Cost$ PayLife<2> | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Card | ChangeNum$ 1
|
||||||
|
SVar:RemAIDeck:True
|
||||||
SVar:Rarity:Rare
|
SVar:Rarity:Rare
|
||||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/bringer_of_the_black_dawn.jpg
|
SVar:Picture:http://www.wizards.com/global/images/magic/general/bringer_of_the_black_dawn.jpg
|
||||||
SetInfo:5DN|Rare|http://magiccards.info/scans/en/5dn/43.jpg
|
SetInfo:5DN|Rare|http://magiccards.info/scans/en/5dn/43.jpg
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Types:Creature Spirit
|
|||||||
Text:no text
|
Text:no text
|
||||||
PT:2/2
|
PT:2/2
|
||||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPumpCurse | TriggerDescription$ When CARDNAME enters the battlefield, target creature gets -2/-2 until end of turn.
|
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPumpCurse | TriggerDescription$ When CARDNAME enters the battlefield, target creature gets -2/-2 until end of turn.
|
||||||
SVar:TrigPumpCurse:AB$Pump | Cost$ 0 | ValidTgts$ Creature | TgtPrompt$ Select target creature | IsCurse$ True | NumAtt$ -2 | NumDef$ -2
|
SVar:TrigPumpCurse:DB$Pump| ValidTgts$ Creature | TgtPrompt$ Select target creature | IsCurse$ True | NumAtt$ -2 | NumDef$ -2
|
||||||
K:Flying
|
K:Flying
|
||||||
SVar:PlayMain1:TRUE
|
SVar:PlayMain1:TRUE
|
||||||
SVar:Rarity:Uncommon
|
SVar:Rarity:Uncommon
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Types:Tribal Enchantment Treefolk Aura
|
|||||||
Text:no text
|
Text:no text
|
||||||
K:Enchant Creature Curse
|
K:Enchant Creature Curse
|
||||||
K:stAnimateEnchanted:Creature:0/4/Overwrite:Creature,Treefolk,Overwrite:no colors:Overwrite:No condition:Enchanted creature is a 0/4 Treefolk with no abilities.
|
K:stAnimateEnchanted:Creature:0/4/Overwrite:Creature,Treefolk,Overwrite:no colors:Overwrite:No condition:Enchanted creature is a 0/4 Treefolk with no abilities.
|
||||||
SVar:RemRandomDeck:True
|
SVar:RemAIDeck:True
|
||||||
SVar:Rarity:Common
|
SVar:Rarity:Common
|
||||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/lignify.jpg
|
SVar:Picture:http://www.wizards.com/global/images/magic/general/lignify.jpg
|
||||||
SetInfo:LRW|Common|http://magiccards.info/scans/en/lw/228.jpg
|
SetInfo:LRW|Common|http://magiccards.info/scans/en/lw/228.jpg
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
package forge;
|
package forge;
|
||||||
|
|
||||||
|
|
||||||
|
import static forge.error.ErrorViewer.showError;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
@@ -12,6 +14,7 @@ import com.esotericsoftware.minlog.Log;
|
|||||||
import forge.card.abilityFactory.AbilityFactory;
|
import forge.card.abilityFactory.AbilityFactory;
|
||||||
import forge.card.cardFactory.CardFactoryUtil;
|
import forge.card.cardFactory.CardFactoryUtil;
|
||||||
import forge.card.spellability.Ability;
|
import forge.card.spellability.Ability;
|
||||||
|
import forge.card.spellability.SpellAbility;
|
||||||
import forge.card.trigger.Trigger;
|
import forge.card.trigger.Trigger;
|
||||||
import forge.gui.GuiUtils;
|
import forge.gui.GuiUtils;
|
||||||
import forge.gui.input.Input_PayManaCost_Ability;
|
import forge.gui.input.Input_PayManaCost_Ability;
|
||||||
@@ -778,7 +781,7 @@ public class CombatUtil {
|
|||||||
// This calculates the amount of damage a blocker in a blockgang can take from the attacker (for trampling attackers)
|
// This calculates the amount of damage a blocker in a blockgang can take from the attacker (for trampling attackers)
|
||||||
public static int shieldDamage(Card attacker, Card defender) {
|
public static int shieldDamage(Card attacker, Card defender) {
|
||||||
|
|
||||||
if (!canDestroyBlocker(defender,attacker, null)) return 100;
|
if (!canDestroyBlocker(defender,attacker, null, false)) return 100;
|
||||||
|
|
||||||
int flankingMagnitude = 0;
|
int flankingMagnitude = 0;
|
||||||
if(attacker.getKeyword().contains("Flanking") && !defender.getKeyword().contains("Flanking")) {
|
if(attacker.getKeyword().contains("Flanking") && !defender.getKeyword().contains("Flanking")) {
|
||||||
@@ -813,7 +816,7 @@ public class CombatUtil {
|
|||||||
CardList blockers = AllZone.Combat.getBlockers(attacker);
|
CardList blockers = AllZone.Combat.getBlockers(attacker);
|
||||||
|
|
||||||
for (Card defender:blockers) {
|
for (Card defender:blockers) {
|
||||||
if(CombatUtil.canDestroyAttacker(attacker, defender, AllZone.Combat) &&
|
if(CombatUtil.canDestroyAttacker(attacker, defender, AllZone.Combat, true) &&
|
||||||
!(defender.getKeyword().contains("Wither") || defender.getKeyword().contains("Infect")))
|
!(defender.getKeyword().contains("Wither") || defender.getKeyword().contains("Infect")))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1061,8 +1064,30 @@ public class CombatUtil {
|
|||||||
return toughness;
|
return toughness;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean canRegenerate(Card card) {
|
||||||
|
Player controller = card.getController();
|
||||||
|
CardList l = AllZoneUtil.getCardsInPlay();
|
||||||
|
for(Card c:l)
|
||||||
|
for(SpellAbility sa:c.getSpellAbility())
|
||||||
|
// if SA is from AF_Counter don't add to getPlayable
|
||||||
|
//This try/catch should fix the "computer is thinking" bug
|
||||||
|
try {
|
||||||
|
if(sa.canPlay() && ComputerUtil.canPayCost(sa,controller) && sa.getAbilityFactory() != null && sa.isAbility()){
|
||||||
|
AbilityFactory af = sa.getAbilityFactory();
|
||||||
|
HashMap <String,String> mapParams = af.getMapParams();
|
||||||
|
if (mapParams.get("AB").equals("Regenerate"))
|
||||||
|
if (AbilityFactory.getDefinedCards(sa.getSourceCard(), mapParams.get("Defined"), sa).contains(card))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch(Exception ex) {
|
||||||
|
showError(ex, "There is an error in the card code for %s:%n", c.getName(), ex.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
//can the blocker destroy the attacker?
|
//can the blocker destroy the attacker?
|
||||||
public static boolean canDestroyAttacker(Card attacker, Card defender, Combat combat) {
|
public static boolean canDestroyAttacker(Card attacker, Card defender, Combat combat, boolean noRegen) {
|
||||||
|
|
||||||
if(attacker.getName().equals("Sylvan Basilisk") && !defender.getKeyword().contains("Indestructible")) return false;
|
if(attacker.getName().equals("Sylvan Basilisk") && !defender.getKeyword().contains("Indestructible")) return false;
|
||||||
|
|
||||||
@@ -1077,12 +1102,9 @@ public class CombatUtil {
|
|||||||
|
|
||||||
}//flanking
|
}//flanking
|
||||||
|
|
||||||
if(attacker.getKeyword().contains("Indestructible") &&
|
if((attacker.getKeyword().contains("Indestructible") || (canRegenerate(attacker) && !noRegen)) &&
|
||||||
!(defender.getKeyword().contains("Wither") || defender.getKeyword().contains("Infect"))) return false;
|
!(defender.getKeyword().contains("Wither") || defender.getKeyword().contains("Infect"))) return false;
|
||||||
|
|
||||||
//unused
|
|
||||||
//int attBushidoMagnitude = attacker.getKeywordMagnitude("Bushido");
|
|
||||||
|
|
||||||
int defenderDamage = defender.getNetAttack() + predictPowerBonusOfBlocker(attacker, defender);
|
int defenderDamage = defender.getNetAttack() + predictPowerBonusOfBlocker(attacker, defender);
|
||||||
int attackerDamage = attacker.getNetAttack() + predictPowerBonusOfAttacker(attacker, defender, combat);
|
int attackerDamage = attacker.getNetAttack() + predictPowerBonusOfAttacker(attacker, defender, combat);
|
||||||
if (AllZoneUtil.isCardInPlay("Doran, the Siege Tower")) {
|
if (AllZoneUtil.isCardInPlay("Doran, the Siege Tower")) {
|
||||||
@@ -1133,14 +1155,14 @@ public class CombatUtil {
|
|||||||
public static boolean blockerWouldBeDestroyed(Card blocker) {
|
public static boolean blockerWouldBeDestroyed(Card blocker) {
|
||||||
Card attacker = AllZone.Combat.getAttackerBlockedBy(blocker);
|
Card attacker = AllZone.Combat.getAttackerBlockedBy(blocker);
|
||||||
|
|
||||||
if(canDestroyBlocker(blocker, attacker, AllZone.Combat) &&
|
if(canDestroyBlocker(blocker, attacker, AllZone.Combat, true) &&
|
||||||
!(attacker.getKeyword().contains("Wither") || attacker.getKeyword().contains("Infect")))
|
!(attacker.getKeyword().contains("Wither") || attacker.getKeyword().contains("Infect")))
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//can the attacker destroy this blocker?
|
//can the attacker destroy this blocker?
|
||||||
public static boolean canDestroyBlocker(Card defender, Card attacker, Combat combat) {
|
public static boolean canDestroyBlocker(Card defender, Card attacker, Combat combat, boolean noRegen) {
|
||||||
|
|
||||||
int flankingMagnitude = 0;
|
int flankingMagnitude = 0;
|
||||||
if(attacker.getKeyword().contains("Flanking") && !defender.getKeyword().contains("Flanking")) {
|
if(attacker.getKeyword().contains("Flanking") && !defender.getKeyword().contains("Flanking")) {
|
||||||
@@ -1151,7 +1173,7 @@ public class CombatUtil {
|
|||||||
if((flankingMagnitude >= defender.getKillDamage()) && !defender.getKeyword().contains("Indestructible")) return true;
|
if((flankingMagnitude >= defender.getKillDamage()) && !defender.getKeyword().contains("Indestructible")) return true;
|
||||||
}//flanking
|
}//flanking
|
||||||
|
|
||||||
if(defender.getKeyword().contains("Indestructible") &&
|
if((defender.getKeyword().contains("Indestructible") || (canRegenerate(defender) && !noRegen)) &&
|
||||||
!(attacker.getKeyword().contains("Wither") || attacker.getKeyword().contains("Infect"))) return false;
|
!(attacker.getKeyword().contains("Wither") || attacker.getKeyword().contains("Infect"))) return false;
|
||||||
|
|
||||||
if(attacker.getName().equals("Sylvan Basilisk") && !defender.getKeyword().contains("Indestructible")) return true;
|
if(attacker.getName().equals("Sylvan Basilisk") && !defender.getKeyword().contains("Indestructible")) return true;
|
||||||
@@ -1190,7 +1212,7 @@ public class CombatUtil {
|
|||||||
&& !attacker.getKeyword().contains("Indestructible") && !attacker.getKeyword().contains("First Strike")) {
|
&& !attacker.getKeyword().contains("Indestructible") && !attacker.getKeyword().contains("First Strike")) {
|
||||||
|
|
||||||
if(defenderDamage >= attackerLife) return false;
|
if(defenderDamage >= attackerLife) return false;
|
||||||
if(defenderDamage > 0 && defender.getKeyword().contains("Deathtouch") ) return false;
|
if(defenderDamage > 0 && defender.getKeyword().contains("Deathtouch")) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(attacker.getKeyword().contains("Deathtouch") && attackerDamage > 0) return true;
|
if(attacker.getKeyword().contains("Deathtouch") && attackerDamage > 0) return true;
|
||||||
|
|||||||
@@ -357,6 +357,60 @@ public class ComputerUtil
|
|||||||
return false;
|
return false;
|
||||||
}//canPayCost()
|
}//canPayCost()
|
||||||
|
|
||||||
|
static public boolean canPayCost(SpellAbility sa, Player player)
|
||||||
|
{
|
||||||
|
Card card = sa.getSourceCard();
|
||||||
|
|
||||||
|
CardList land = getAvailableMana(player);
|
||||||
|
|
||||||
|
if(card.isLand() && sa.getPayCosts().getTap())
|
||||||
|
{
|
||||||
|
land.remove(card);
|
||||||
|
}
|
||||||
|
// Beached - Delete old
|
||||||
|
String mana = sa.getPayCosts() != null ? sa.getPayCosts().getTotalMana() : sa.getManaCost();
|
||||||
|
|
||||||
|
ManaCost cost = new ManaCost(mana);
|
||||||
|
|
||||||
|
// Tack xMana Payments into mana here if X is a set value
|
||||||
|
if (sa.getPayCosts() != null && cost.getXcounter() > 0){
|
||||||
|
String xSvar = card.getSVar("X").equals("Count$xPaid") ? "PayX" : "X";
|
||||||
|
// For Count$xPaid set PayX in the AFs then use that here
|
||||||
|
// Else calculate it as appropriate.
|
||||||
|
if (!card.getSVar(xSvar).equals("")){
|
||||||
|
int manaToAdd = AbilityFactory.calculateAmount(card, xSvar, sa) * cost.getXcounter();
|
||||||
|
cost.increaseColorlessMana(manaToAdd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cost = AllZone.GameAction.getSpellCostChange(sa, cost);
|
||||||
|
if(cost.isPaid())
|
||||||
|
return canPayAdditionalCosts(sa, player);
|
||||||
|
// Beached - Delete old
|
||||||
|
ArrayList<String> colors;
|
||||||
|
|
||||||
|
for(int i = 0; i < land.size(); i++)
|
||||||
|
{
|
||||||
|
colors = getColors(land.get(i));
|
||||||
|
int once = 0;
|
||||||
|
|
||||||
|
for(int j =0; j < colors.size(); j++)
|
||||||
|
{
|
||||||
|
if(cost.isNeeded(colors.get(j)) && once == 0)
|
||||||
|
{
|
||||||
|
cost.payMana(colors.get(j));
|
||||||
|
once++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cost.isPaid()) {
|
||||||
|
return canPayAdditionalCosts(sa, player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}//canPayCost()
|
||||||
|
|
||||||
|
|
||||||
static public int determineLeftoverMana(SpellAbility sa){
|
static public int determineLeftoverMana(SpellAbility sa){
|
||||||
// This function should mostly be called to determine how much mana AI has leftover to pay X costs
|
// This function should mostly be called to determine how much mana AI has leftover to pay X costs
|
||||||
// This function is basically getAvailableMana.size() - sa.getConvertedManaCost()
|
// This function is basically getAvailableMana.size() - sa.getConvertedManaCost()
|
||||||
@@ -570,6 +624,174 @@ public class ComputerUtil
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public boolean canPayAdditionalCosts(SpellAbility sa, Player player)
|
||||||
|
{
|
||||||
|
// Add additional cost checks here before attempting to activate abilities
|
||||||
|
Cost cost = sa.getPayCosts();
|
||||||
|
if (cost == null)
|
||||||
|
return true;
|
||||||
|
Card card = sa.getSourceCard();
|
||||||
|
|
||||||
|
if (cost.getTap() && (card.isTapped() || card.isSick()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (cost.getUntap() && (card.isUntapped() || card.isSick()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (cost.getTapXTypeCost())
|
||||||
|
{
|
||||||
|
CardList typeList = AllZoneUtil.getPlayerCardsInPlay(player);
|
||||||
|
typeList = typeList.getValidCards(cost.getTapXType().split(","),sa.getActivatingPlayer() ,sa.getSourceCard());
|
||||||
|
|
||||||
|
if (cost.getTap())
|
||||||
|
typeList.remove(sa.getSourceCard());
|
||||||
|
typeList = typeList.filter(AllZoneUtil.untapped);
|
||||||
|
|
||||||
|
if (cost.getTapXTypeAmount() > typeList.size())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cost.getSubCounter()){
|
||||||
|
Counters c = cost.getCounterType();
|
||||||
|
if (card.getCounters(c) - cost.getCounterNum() < 0 || !AllZoneUtil.isCardInPlay(card)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cost.getAddCounter()){
|
||||||
|
// this should always be true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cost.getLifeCost()){
|
||||||
|
if (player.getLife() <= cost.getLifeAmount())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cost.getDiscardCost()){
|
||||||
|
CardList handList = AllZoneUtil.getPlayerHand(player);
|
||||||
|
String discType = cost.getDiscardType();
|
||||||
|
int discAmount = cost.getDiscardAmount();
|
||||||
|
|
||||||
|
if (cost.getDiscardThis()){
|
||||||
|
if (!AllZone.getZone(card).getZoneName().equals(Constant.Zone.Hand))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if( discType.equals("LastDrawn")) {
|
||||||
|
//compy can't yet use this effectively
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (discType.equals("Hand")){
|
||||||
|
// this will always work
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
if (!discType.equals("Any") && !discType.equals("Random")){
|
||||||
|
String validType[] = discType.split(",");
|
||||||
|
handList = handList.getValidCards(validType, sa.getActivatingPlayer(), sa.getSourceCard());
|
||||||
|
}
|
||||||
|
if (discAmount > handList.size()){
|
||||||
|
// not enough cards in hand to pay
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cost.getSacCost()){
|
||||||
|
// if there's a sacrifice in the cost, just because we can Pay it doesn't mean we want to.
|
||||||
|
if (!cost.getSacThis()){
|
||||||
|
CardList typeList = AllZoneUtil.getPlayerCardsInPlay(player);
|
||||||
|
typeList = typeList.getValidCards(cost.getSacType().split(","), sa.getActivatingPlayer(), sa.getSourceCard());
|
||||||
|
Card target = sa.getTargetCard();
|
||||||
|
if (target != null && target.getController().isPlayer(player)) // don't sacrifice the card we're pumping
|
||||||
|
typeList.remove(target);
|
||||||
|
|
||||||
|
if (cost.getSacAmount() > typeList.size())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (cost.getSacThis() && !AllZoneUtil.isCardInPlay(card))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cost.getExileCost()){
|
||||||
|
// if there's an exile in the cost, just because we can Pay it doesn't mean we want to.
|
||||||
|
if (!cost.getExileThis()){
|
||||||
|
CardList typeList = AllZoneUtil.getPlayerCardsInPlay(player);
|
||||||
|
typeList = typeList.getValidCards(cost.getExileType().split(","), sa.getActivatingPlayer(), sa.getSourceCard());
|
||||||
|
Card target = sa.getTargetCard();
|
||||||
|
if (target != null && target.getController().isPlayer(player)) // don't exile the card we're pumping
|
||||||
|
typeList.remove(target);
|
||||||
|
|
||||||
|
if (cost.getExileAmount() > typeList.size())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (cost.getExileThis() && !AllZoneUtil.isCardInPlay(card))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cost.getExileFromHandCost()){
|
||||||
|
// if there's an exile in the cost, just because we can Pay it doesn't mean we want to.
|
||||||
|
if (!cost.getExileFromHandThis()){
|
||||||
|
CardList typeList = AllZoneUtil.getPlayerHand(player);
|
||||||
|
typeList = typeList.getValidCards(cost.getExileFromHandType().split(","), sa.getActivatingPlayer(), sa.getSourceCard());
|
||||||
|
Card target = sa.getTargetCard();
|
||||||
|
if (target != null && target.getController().isPlayer(player)) // don't exile the card we're pumping
|
||||||
|
typeList.remove(target);
|
||||||
|
|
||||||
|
if (cost.getExileFromHandAmount() > typeList.size())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (cost.getExileFromHandThis() && !AllZoneUtil.isCardInPlayerHand(player, card))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cost.getExileFromGraveCost()){
|
||||||
|
if (!cost.getExileFromGraveThis()){
|
||||||
|
CardList typeList = AllZoneUtil.getPlayerGraveyard(player);
|
||||||
|
typeList = typeList.getValidCards(cost.getExileFromGraveType().split(","), sa.getActivatingPlayer(), sa.getSourceCard());
|
||||||
|
Card target = sa.getTargetCard();
|
||||||
|
if (target != null && target.getController().isPlayer(player)) // don't exile the card we're pumping
|
||||||
|
typeList.remove(target);
|
||||||
|
|
||||||
|
if (cost.getExileFromGraveAmount() > typeList.size())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (cost.getExileFromGraveThis() && !AllZoneUtil.isCardInPlayerGraveyard(player, card))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cost.getExileFromTopCost()){
|
||||||
|
if(!cost.getExileFromTopThis()){
|
||||||
|
CardList typeList = AllZoneUtil.getPlayerCardsInLibrary(player);
|
||||||
|
typeList = typeList.getValidCards(cost.getExileFromTopType().split(","), sa.getActivatingPlayer(), sa.getSourceCard());
|
||||||
|
Card target = sa.getTargetCard();
|
||||||
|
if (target != null && target.getController().isPlayer(player)) // don't exile the card we're pumping
|
||||||
|
typeList.remove(target);
|
||||||
|
|
||||||
|
if (cost.getExileFromTopAmount() > typeList.size())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (cost.getExileFromTopThis() && !AllZoneUtil.isCardInPlayerLibrary(player, card))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cost.getReturnCost()){
|
||||||
|
// if there's a return in the cost, just because we can Pay it doesn't mean we want to.
|
||||||
|
if (!cost.getReturnThis()){
|
||||||
|
CardList typeList = AllZoneUtil.getPlayerCardsInPlay(player);
|
||||||
|
typeList = typeList.getValidCards(cost.getReturnType().split(","), sa.getActivatingPlayer(), sa.getSourceCard());
|
||||||
|
Card target = sa.getTargetCard();
|
||||||
|
if (target != null && target.getController().isPlayer(player)) // don't bounce the card we're pumping
|
||||||
|
typeList.remove(target);
|
||||||
|
|
||||||
|
if (cost.getReturnAmount() > typeList.size())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (!AllZoneUtil.isCardInPlay(card))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static public boolean canPayCost(String cost)
|
static public boolean canPayCost(String cost)
|
||||||
{
|
{
|
||||||
if(cost.equals(("0")))
|
if(cost.equals(("0")))
|
||||||
@@ -749,6 +971,41 @@ public class ComputerUtil
|
|||||||
|
|
||||||
}//getAvailableMana()
|
}//getAvailableMana()
|
||||||
|
|
||||||
|
static public CardList getAvailableMana(final Player player)
|
||||||
|
{
|
||||||
|
CardList list = AllZoneUtil.getPlayerCardsInPlay(player);
|
||||||
|
CardList mana = list.filter(new CardListFilter()
|
||||||
|
{
|
||||||
|
public boolean addCard(Card c)
|
||||||
|
{
|
||||||
|
for (Ability_Mana am : c.getAIPlayableMana()) {
|
||||||
|
am.setActivatingPlayer(player);
|
||||||
|
if (am.canPlay()) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});//CardListFilter
|
||||||
|
|
||||||
|
CardList sortedMana = new CardList();
|
||||||
|
|
||||||
|
for (int i=0; i<mana.size();i++)
|
||||||
|
{
|
||||||
|
Card card = mana.get(i);
|
||||||
|
if (card.isBasicLand()){
|
||||||
|
sortedMana.add(card);
|
||||||
|
mana.remove(card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int j=0; j<mana.size();j++)
|
||||||
|
{
|
||||||
|
sortedMana.add(mana.get(j));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortedMana;
|
||||||
|
}//getAvailableMana()
|
||||||
|
|
||||||
|
|
||||||
//plays a land if one is available
|
//plays a land if one is available
|
||||||
static public boolean chooseLandsToPlay()
|
static public boolean chooseLandsToPlay()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -515,7 +515,7 @@ public class ComputerUtil_Attack2 {
|
|||||||
for (Card defender:defenders) {
|
for (Card defender:defenders) {
|
||||||
if(CombatUtil.canBlock(attacker, defender)){ //, combat )) {
|
if(CombatUtil.canBlock(attacker, defender)){ //, combat )) {
|
||||||
canBeBlocked = true;
|
canBeBlocked = true;
|
||||||
if(CombatUtil.canDestroyAttacker(attacker, defender, combat)) {
|
if(CombatUtil.canDestroyAttacker(attacker, defender, combat, false)) {
|
||||||
canBeKilledByOne = true; // there is a single creature on the battlefield that can kill the creature
|
canBeKilledByOne = true; // there is a single creature on the battlefield that can kill the creature
|
||||||
// see if the defending creature is of higher or lower value. We don't want to attack only to lose value
|
// see if the defending creature is of higher or lower value. We don't want to attack only to lose value
|
||||||
if(CardFactoryUtil.evaluateCreature(defender) <= CardFactoryUtil.evaluateCreature(attacker)){
|
if(CardFactoryUtil.evaluateCreature(defender) <= CardFactoryUtil.evaluateCreature(attacker)){
|
||||||
@@ -523,7 +523,7 @@ public class ComputerUtil_Attack2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// see if this attacking creature can destroy this defender, if not record that it can't kill everything
|
// see if this attacking creature can destroy this defender, if not record that it can't kill everything
|
||||||
if(!CombatUtil.canDestroyBlocker(defender, attacker, combat)){
|
if(!CombatUtil.canDestroyBlocker(defender, attacker, combat, false)){
|
||||||
canKillAll = false;
|
canKillAll = false;
|
||||||
if(defender.getKeyword().contains("Wither") || defender.getKeyword().contains("Infect")){
|
if(defender.getKeyword().contains("Wither") || defender.getKeyword().contains("Infect")){
|
||||||
canKillAllDangerous = false; // there is a dangerous creature that can survive an attack from this creature
|
canKillAllDangerous = false; // there is a dangerous creature that can survive an attack from this creature
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public class ComputerUtil_Block2
|
|||||||
CardList blockers = new CardList();
|
CardList blockers = new CardList();
|
||||||
|
|
||||||
for(Card b : blockersLeft) {
|
for(Card b : blockersLeft) {
|
||||||
if(!CombatUtil.canDestroyBlocker(b,attacker, combat)) blockers.add(b);
|
if(!CombatUtil.canDestroyBlocker(b,attacker, combat, false)) blockers.add(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
return blockers;
|
return blockers;
|
||||||
@@ -43,7 +43,7 @@ public class ComputerUtil_Block2
|
|||||||
CardList blockers = new CardList();
|
CardList blockers = new CardList();
|
||||||
|
|
||||||
for(Card b : blockersLeft) {
|
for(Card b : blockersLeft) {
|
||||||
if(CombatUtil.canDestroyAttacker(attacker,b,combat)) blockers.add(b);
|
if(CombatUtil.canDestroyAttacker(attacker, b, combat, false)) blockers.add(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
return blockers;
|
return blockers;
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ public class RunTest
|
|||||||
Card card = cf.getCard("Sylvan Basilisk", null);
|
Card card = cf.getCard("Sylvan Basilisk", null);
|
||||||
Card card2 = cf.getCard("Exalted Angel", null);
|
Card card2 = cf.getCard("Exalted Angel", null);
|
||||||
|
|
||||||
check("121a", !CombatUtil.canDestroyAttacker(card, card2, null));
|
check("121a", !CombatUtil.canDestroyAttacker(card, card2, null, false));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
check("122", CardUtil.getConvertedManaCost("0") == 0);
|
check("122", CardUtil.getConvertedManaCost("0") == 0);
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ public class AbilityFactory_PermanentState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean untapTrigger(AbilityFactory af, SpellAbility sa, boolean mandatory){
|
public static boolean untapTrigger(AbilityFactory af, SpellAbility sa, boolean mandatory){
|
||||||
|
HashMap<String,String> params = af.getMapParams();
|
||||||
if (!ComputerUtil.canPayCost(sa))
|
if (!ComputerUtil.canPayCost(sa))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -190,6 +191,9 @@ public class AbilityFactory_PermanentState {
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
// TODO: use Defined to determine, if this is an unfavorable result
|
// TODO: use Defined to determine, if this is an unfavorable result
|
||||||
|
ArrayList<Card> pDefined = AbilityFactory.getDefinedCards(sa.getSourceCard(), params.get("Defined"), sa);
|
||||||
|
if (pDefined != null && pDefined.get(0).isUntapped())
|
||||||
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ public class AbilityFactory_Pump {
|
|||||||
|
|
||||||
if (AllZone.Stack.size() == 0){
|
if (AllZone.Stack.size() == 0){
|
||||||
// If the cost is tapping, don't activate before declare attack/block
|
// If the cost is tapping, don't activate before declare attack/block
|
||||||
if (sa.getPayCosts().getTap()){
|
if (sa.getPayCosts() != null && sa.getPayCosts().getTap()){
|
||||||
if (AllZone.Phase.isBefore(Constant.Phase.Combat_Declare_Attackers) && AllZone.Phase.isPlayerTurn(AllZone.ComputerPlayer))
|
if (AllZone.Phase.isBefore(Constant.Phase.Combat_Declare_Attackers) && AllZone.Phase.isPlayerTurn(AllZone.ComputerPlayer))
|
||||||
list.remove(sa.getSourceCard());
|
list.remove(sa.getSourceCard());
|
||||||
if (AllZone.Phase.isBefore(Constant.Phase.Combat_Declare_Blockers) && AllZone.Phase.isPlayerTurn(AllZone.HumanPlayer))
|
if (AllZone.Phase.isBefore(Constant.Phase.Combat_Declare_Blockers) && AllZone.Phase.isPlayerTurn(AllZone.HumanPlayer))
|
||||||
@@ -541,8 +541,6 @@ public class AbilityFactory_Pump {
|
|||||||
if (sa.getTarget() == null){
|
if (sa.getTarget() == null){
|
||||||
if (mandatory)
|
if (mandatory)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
return doTgtAI(sa, defense, attack, mandatory);
|
return doTgtAI(sa, defense, attack, mandatory);
|
||||||
|
|||||||
Reference in New Issue
Block a user