...
 
Commits (1236)

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

......@@ -2,8 +2,9 @@
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry combineaccessrules="false" kind="src" path="/forge-core"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>
\ No newline at end of file
</classpath>
eclipse.preferences.version=1
encoding//src/main/java=ISO-8859-1
encoding//src/test/java=ISO-8859-1
encoding/<project>=UTF-8
......@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.9-SNAPSHOT</version>
<version>1.6.11-SNAPSHOT</version>
</parent>
<artifactId>forge-ai</artifactId>
......
......@@ -44,20 +44,24 @@ public class AiCardMemory {
private final Set<Card> memTrickAttackers;
private final Set<Card> memHeldManaSources;
private final Set<Card> memHeldManaSourcesForCombat;
private final Set<Card> memHeldManaSourcesForEnemyCombat;
private final Set<Card> memAttachedThisTurn;
private final Set<Card> memAnimatedThisTurn;
private final Set<Card> memBouncedThisTurn;
private final Set<Card> memActivatedThisTurn;
private final Set<Card> memChosenFogEffect;
public AiCardMemory() {
this.memMandatoryAttackers = new HashSet<>();
this.memHeldManaSources = new HashSet<>();
this.memHeldManaSourcesForCombat = new HashSet<>();
this.memHeldManaSourcesForEnemyCombat = new HashSet<>();
this.memAttachedThisTurn = new HashSet<>();
this.memAnimatedThisTurn = new HashSet<>();
this.memBouncedThisTurn = new HashSet<>();
this.memActivatedThisTurn = new HashSet<>();
this.memTrickAttackers = new HashSet<>();
this.memChosenFogEffect = new HashSet<>();
}
/**
......@@ -70,10 +74,12 @@ public class AiCardMemory {
TRICK_ATTACKERS,
HELD_MANA_SOURCES_FOR_MAIN2,
HELD_MANA_SOURCES_FOR_DECLBLK,
HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK,
ATTACHED_THIS_TURN,
ANIMATED_THIS_TURN,
BOUNCED_THIS_TURN,
ACTIVATED_THIS_TURN,
CHOSEN_FOG_EFFECT,
//REVEALED_CARDS // stub, not linked to AI code yet
}
......@@ -87,6 +93,8 @@ public class AiCardMemory {
return memHeldManaSources;
case HELD_MANA_SOURCES_FOR_DECLBLK:
return memHeldManaSourcesForCombat;
case HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK:
return memHeldManaSourcesForEnemyCombat;
case ATTACHED_THIS_TURN:
return memAttachedThisTurn;
case ANIMATED_THIS_TURN:
......@@ -95,6 +103,8 @@ public class AiCardMemory {
return memBouncedThisTurn;
case ACTIVATED_THIS_TURN:
return memActivatedThisTurn;
case CHOSEN_FOG_EFFECT:
return memChosenFogEffect;
//case REVEALED_CARDS:
// return memRevealedCards;
default:
......@@ -267,10 +277,12 @@ public class AiCardMemory {
clearMemorySet(MemorySet.TRICK_ATTACKERS);
clearMemorySet(MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
clearMemorySet(MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK);
clearMemorySet(MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK);
clearMemorySet(MemorySet.ATTACHED_THIS_TURN);
clearMemorySet(MemorySet.ANIMATED_THIS_TURN);
clearMemorySet(MemorySet.BOUNCED_THIS_TURN);
clearMemorySet(MemorySet.ACTIVATED_THIS_TURN);
clearMemorySet(MemorySet.CHOSEN_FOG_EFFECT);
}
// Static functions to simplify access to AI card memory of a given AI player.
......
......@@ -42,6 +42,7 @@ import forge.game.card.CardPredicates.Presets;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.cost.*;
import forge.game.keyword.Keyword;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
......@@ -159,15 +160,15 @@ public class AiController {
if ("NonActive".equals(curse) && !player.equals(game.getPhaseHandler().getPlayerTurn())) {
return true;
} else if ("DestroyCreature".equals(curse) && sa.isSpell() && host.isCreature()
&& !sa.getHostCard().hasKeyword("Indestructible")) {
&& !sa.getHostCard().hasKeyword(Keyword.INDESTRUCTIBLE)) {
return true;
} else if ("CounterEnchantment".equals(curse) && sa.isSpell() && host.isEnchantment()
&& !sa.getHostCard().hasKeyword("CARDNAME can't be countered.")) {
&& CardFactoryUtil.isCounterable(host)) {
return true;
} else if ("ChaliceOfTheVoid".equals(curse) && sa.isSpell() && !host.hasKeyword("CARDNAME can't be countered.")
} else if ("ChaliceOfTheVoid".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)
&& host.getCMC() == c.getCounters(CounterType.CHARGE)) {
return true;
} else if ("BazaarOfWonders".equals(curse) && sa.isSpell() && !host.hasKeyword("CARDNAME can't be countered.")) {
} else if ("BazaarOfWonders".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)) {
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
if (!card.isToken() && card.getName().equals(host.getName())) {
return true;
......@@ -581,10 +582,10 @@ public class AiController {
}
public void reserveManaSources(SpellAbility sa) {
reserveManaSources(sa, PhaseType.MAIN2);
reserveManaSources(sa, PhaseType.MAIN2, false);
}
public void reserveManaSources(SpellAbility sa, PhaseType phaseType) {
public void reserveManaSources(SpellAbility sa, PhaseType phaseType, boolean enemy) {
ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0);
CardCollection manaSources = ComputerUtilMana.getManaSourcesToPayCost(cost, sa, player);
......@@ -595,7 +596,8 @@ public class AiController {
memSet = AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2;
break;
case COMBAT_DECLARE_BLOCKERS:
memSet = AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK;
memSet = enemy ? AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK
: AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK;
break;
default:
System.out.println("Warning: unsupported mana reservation phase specified for reserveManaSources: "
......@@ -792,7 +794,7 @@ public class AiController {
}
}
// if the profile specifies it, deprioritize Storm spells in an attempt to build up storm count
if (source.hasKeyword("Storm") && ai.getController() instanceof PlayerControllerAi) {
if (source.hasKeyword(Keyword.STORM) && ai.getController() instanceof PlayerControllerAi) {
p -= (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.PRIORITY_REDUCTION_FOR_STORM_SPELLS));
}
}
......@@ -1145,15 +1147,14 @@ public class AiController {
}
CardCollection landsWannaPlay = ComputerUtilAbility.getAvailableLandsToPlay(game, player);
CardCollection playBeforeLand = CardLists.filter(player.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return "true".equalsIgnoreCase(card.getSVar("PlayBeforeLandDrop"));
}
});
CardCollection playBeforeLand = CardLists.filter(
player.getCardsIn(ZoneType.Hand), CardPredicates.hasSVar("PlayBeforeLandDrop")
);
if (!playBeforeLand.isEmpty()) {
SpellAbility wantToPlayBeforeLand = chooseSpellAbilityToPlayFromList(ComputerUtilAbility.getSpellAbilities(playBeforeLand, player), false);
SpellAbility wantToPlayBeforeLand = chooseSpellAbilityToPlayFromList(
ComputerUtilAbility.getSpellAbilities(playBeforeLand, player), false
);
if (wantToPlayBeforeLand != null) {
return singleSpellAbilityList(wantToPlayBeforeLand);
}
......@@ -1163,14 +1164,28 @@ public class AiController {
landsWannaPlay = filterLandsToPlay(landsWannaPlay);
Log.debug("Computer " + game.getPhaseHandler().getPhase().nameForUi);
if (landsWannaPlay != null && !landsWannaPlay.isEmpty() && player.canPlayLand(null)) {
// TODO search for other land it might want to play?
Card land = chooseBestLandToPlay(landsWannaPlay);
if (ComputerUtil.getDamageFromETB(player, land) < player.getLife() || !player.canLoseLife()
|| player.cantLoseForZeroOrLessLife() ) {
if (!game.getPhaseHandler().is(PhaseType.MAIN1) || !isSafeToHoldLandDropForMain2(land)) {
game.PLAY_LAND_SURROGATE.setHostCard(land);
final List<SpellAbility> abilities = Lists.newArrayList();
abilities.add(game.PLAY_LAND_SURROGATE);
return abilities;
LandAbility la = new LandAbility(land, player, null);
if (la.canPlay()) {
abilities.add(la);
}
// add mayPlay option
for (CardPlayOption o : land.mayPlay(player)) {
la = new LandAbility(land, player, o.getAbility());
if (la.canPlay()) {
abilities.add(la);
}
}
if (!abilities.isEmpty()) {
return abilities;
}
}
}
}
......@@ -1180,6 +1195,13 @@ public class AiController {
}
private boolean isSafeToHoldLandDropForMain2(Card landToPlay) {
boolean hasMomir = !CardLists.filter(player.getCardsIn(ZoneType.Command),
CardPredicates.nameEquals("Momir Vig, Simic Visionary Avatar")).isEmpty();
if (hasMomir) {
// Don't do this in Momir variants since it messes with the AI decision making for the avatar.
return false;
}
if (!MyRandom.percentTrue(getIntProperty(AiProps.HOLD_LAND_DROP_FOR_MAIN2_IF_UNUSED))) {
// check against the chance specified in the profile
return false;
......@@ -1314,7 +1336,7 @@ public class AiController {
continue;
}
if (sa.getHostCard().hasKeyword("Storm")
if (sa.getHostCard().hasKeyword(Keyword.STORM)
&& sa.getApi() != ApiType.Counter // AI would suck at trying to deliberately proc a Storm counterspell
&& CardLists.filter(player.getCardsIn(ZoneType.Hand), Predicates.not(Predicates.or(CardPredicates.Presets.LANDS, CardPredicates.hasKeyword("Storm")))).size() > 0) {
if (game.getView().getStormCount() < this.getIntProperty(AiProps.MIN_COUNT_FOR_STORM_SPELLS)) {
......@@ -1542,11 +1564,11 @@ public class AiController {
// and exaclty one counter of the specifice type gets high priority to keep the card
if (allies.contains(crd.getController())) {
// except if its a Chronozoa, because it WANTS to be removed to make more
if (crd.hasKeyword("Vanishing") && !"Chronozoa".equals(crd.getName())) {
if (crd.hasKeyword(Keyword.VANISHING) && !"Chronozoa".equals(crd.getName())) {
if (crd.getCounters(CounterType.TIME) == 1) {
return CounterType.TIME;
}
} else if (crd.hasKeyword("Fading")) {
} else if (crd.hasKeyword(Keyword.FADING)) {
if (crd.getCounters(CounterType.FADE) == 1) {
return CounterType.FADE;
}
......
......@@ -467,6 +467,10 @@ public class AiCostDecision extends CostDecisionMakerBase {
ability.getActivatingPlayer(), ability.getHostCard(), ability);
typeList = CardLists.filter(typeList, Presets.UNTAPPED);
c = typeList.size();
// account for the fact that the activated card may be tapped in the process
if (ability.getPayCosts().hasTapCost() && typeList.contains(ability.getHostCard())) {
c--;
}
source.setSVar("ChosenX", "Number$" + Integer.toString(c));
} else {
if (!isVehicle) {
......@@ -510,6 +514,15 @@ public class AiCostDecision extends CostDecisionMakerBase {
Integer c = cost.convertAmount();
if (c == null) {
if (ability.getSVar(cost.getAmount()).equals("XChoice")) {
if ("SacToReduceCost".equals(ability.getParam("AILogic"))) {
// e.g. Torgaar, Famine Incarnate
// TODO: currently returns an empty list, so the AI doesn't sacrifice anything. Trying to make
// the AI decide on creatures to sac makes the AI sacrifice them, but the cost is not reduced and the
// AI pays the full mana cost anyway (despite sacrificing creatures).
return PaymentDecision.card(new CardCollection());
}
// Other cards are assumed to be flagged RemAIDeck for now
return null;
}
......
......@@ -104,6 +104,9 @@ public enum AiProps { /** */
INTUITION_ALTERNATIVE_LOGIC ("false"), /** */
EXPLORE_MAX_CMC_DIFF_TO_PUT_IN_GRAVEYARD ("2"),
EXPLORE_NUM_LANDS_TO_STILL_NEED_MORE("2"), /** */
MOJHOSTO_NUM_LANDS_TO_ACTIVATE_JHOIRA("5"), /** */
MOJHOSTO_CHANCE_TO_PREFER_JHOIRA_OVER_MOMIR ("50"), /** */
MOJHOSTO_CHANCE_TO_USE_JHOIRA_COPY_INSTANT ("20"), /** */
// Experimental features, must be removed after extensive testing and, ideally, defaulting
// <-- There are no experimental options here -->
AI_IN_DANGER_THRESHOLD("4"),
......
......@@ -37,6 +37,7 @@ import forge.game.card.CardPredicates.Presets;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.cost.*;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
......@@ -789,7 +790,7 @@ public class ComputerUtil {
}
if (destroy) {
final CardCollection indestructibles = CardLists.getKeyword(remaining, "Indestructible");
final CardCollection indestructibles = CardLists.getKeyword(remaining, Keyword.INDESTRUCTIBLE);
if (!indestructibles.isEmpty()) {
return indestructibles.get(0);
}
......@@ -826,7 +827,7 @@ public class ComputerUtil {
}
public static boolean canRegenerate(Player ai, final Card card) {
if (card.hasKeyword("CARDNAME can't be regenerated.")) {
if (!card.canBeShielded()) {
return false;
}
......@@ -939,7 +940,7 @@ public class ComputerUtil {
}
// try not to cast Raid creatures in main 1 if an attack is likely
if ("Count$AttackersDeclared".equals(card.getSVar("RaidTest")) && !card.hasKeyword("Haste")) {
if ("Count$AttackersDeclared".equals(card.getSVar("RaidTest")) && !card.hasKeyword(Keyword.HASTE)) {
for (Card potentialAtkr: ai.getCreaturesInPlay()) {
if (ComputerUtilCard.doesCreatureAttackAI(ai, potentialAtkr)) {
return false;
......@@ -951,11 +952,12 @@ public class ComputerUtil {
return true;
}
if (card.isCreature() && !card.hasKeyword("Defender") && (card.hasKeyword("Haste") || ComputerUtil.hasACardGivingHaste(ai) || sa.isDash())) {
if (card.isCreature() && !card.hasKeyword(Keyword.DEFENDER)
&& (card.hasKeyword(Keyword.HASTE) || ComputerUtil.hasACardGivingHaste(ai, true) || sa.isDash())) {
return true;
}
if (card.hasKeyword("Exalted")) {
if (card.hasKeyword(Keyword.EXALTED)) {
return true;
}