mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-15 10:18:01 +00:00
- AI improvements: restored playability of Exhaustion, made Cursed Scroll AI-playable, added some additional code support for programmable Exert logic (Ahn-Crop Champion is an example), added a bit of threshold to Living Death AI, moved Momir Vig Avatar logic to SpecialCardAi.
This commit is contained in:
@@ -29,6 +29,7 @@ import forge.ai.ability.AnimateAi;
|
||||
import forge.card.CardTypeView;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.ProtectEffect;
|
||||
import forge.game.card.*;
|
||||
@@ -40,6 +41,7 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Expressions;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.collect.FCollectionView;
|
||||
|
||||
@@ -1212,18 +1214,51 @@ public class AiAttackController {
|
||||
}
|
||||
if (sa.usesTargeting()) {
|
||||
sa.setActivatingPlayer(c.getController());
|
||||
if (CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa).isEmpty()) {
|
||||
List<Card> validTargets = CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa);
|
||||
if (validTargets.isEmpty()) {
|
||||
missTarget = true;
|
||||
break;
|
||||
} else if (sa.isCurse() && CardLists.filter(validTargets,
|
||||
CardPredicates.isControlledByAnyOf(c.getController().getOpponents())).isEmpty()) {
|
||||
// e.g. Ahn-Crop Crasher - the effect is only good when aimed at opponent's creatures
|
||||
missTarget = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (missTarget) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (random.nextBoolean()) {
|
||||
// A specific AI condition for Exert: if specified on the card, the AI will always
|
||||
// exert creatures that meet this condition
|
||||
if (c.hasSVar("AIExertCondition")) {
|
||||
if (!c.getSVar("AIExertCondition").isEmpty()) {
|
||||
final String needsToExert = c.getSVar("AIExertCondition");
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
String sVar = needsToExert.split(" ")[0];
|
||||
String comparator = needsToExert.split(" ")[1];
|
||||
String compareTo = comparator.substring(2);
|
||||
try {
|
||||
x = Integer.parseInt(sVar);
|
||||
} catch (final NumberFormatException e) {
|
||||
x = CardFactoryUtil.xCount(c, c.getSVar(sVar));
|
||||
}
|
||||
try {
|
||||
y = Integer.parseInt(compareTo);
|
||||
} catch (final NumberFormatException e) {
|
||||
y = CardFactoryUtil.xCount(c, c.getSVar(compareTo));
|
||||
}
|
||||
if (Expressions.compare(x, comparator, y)) {
|
||||
shouldExert = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldExert && random.nextBoolean()) {
|
||||
// TODO Improve when the AI wants to use Exert powers
|
||||
shouldExert = true;
|
||||
}
|
||||
|
||||
@@ -892,6 +892,8 @@ public class PlayerControllerAi extends PlayerController {
|
||||
} else if (logic.equals("MostProminentSpellInComputerDeck")) {
|
||||
CardCollectionView cards = CardLists.getValidCards(aiLibrary, "Card.Instant,Card.Sorcery", player, sa.getHostCard());
|
||||
return ComputerUtilCard.getMostProminentCardName(cards);
|
||||
} else if (logic.equals("CursedScroll")) {
|
||||
return SpecialCardAi.CursedScroll.chooseCard(player, sa);
|
||||
}
|
||||
} else {
|
||||
CardCollectionView list = CardLists.filterControlledBy(game.getCardsInGame(), player.getOpponents());
|
||||
|
||||
@@ -176,6 +176,35 @@ public class SpecialCardAi {
|
||||
}
|
||||
}
|
||||
|
||||
// Cursed Scroll
|
||||
public static class CursedScroll {
|
||||
public static boolean consider(Player ai, SpellAbility sa) {
|
||||
CardCollectionView hand = ai.getCardsIn(ZoneType.Hand);
|
||||
if (hand.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// For now, see if all cards in hand have the same name, and then proceed if true
|
||||
return CardLists.filter(hand, CardPredicates.nameEquals(hand.getFirst().getName())).size() == hand.size();
|
||||
}
|
||||
|
||||
public static String chooseCard(Player ai, SpellAbility sa) {
|
||||
int maxCount = 0;
|
||||
Card best = null;
|
||||
CardCollectionView hand = ai.getCardsIn(ZoneType.Hand);
|
||||
|
||||
for (Card c : ai.getCardsIn(ZoneType.Hand)) {
|
||||
int count = CardLists.filter(hand, CardPredicates.nameEquals(c.getName())).size();
|
||||
if (count > maxCount) {
|
||||
maxCount = count;
|
||||
best = c;
|
||||
}
|
||||
}
|
||||
|
||||
return best.getName();
|
||||
}
|
||||
}
|
||||
|
||||
// Desecration Demon
|
||||
public static class DesecrationDemon {
|
||||
private static final int demonSacThreshold = Integer.MAX_VALUE; // if we're in dire conditions, sac everything from worst to best hoping to find an answer
|
||||
@@ -360,6 +389,8 @@ public class SpecialCardAi {
|
||||
public static class LivingDeath {
|
||||
public static boolean consider(Player ai, SpellAbility sa) {
|
||||
int aiBattlefieldPower = 0, aiGraveyardPower = 0;
|
||||
int threshold = 320; // approximately a 4/4 Flying creature worth of extra value
|
||||
|
||||
CardCollection aiCreaturesInGY = CardLists.filter(ai.getZone(ZoneType.Graveyard).getCards(), CardPredicates.Presets.CREATURES);
|
||||
|
||||
if (aiCreaturesInGY.isEmpty()) {
|
||||
@@ -396,7 +427,7 @@ public class SpecialCardAi {
|
||||
}
|
||||
|
||||
// if we get more value out of this than our opponent does (hopefully), go for it
|
||||
return (aiGraveyardPower - aiBattlefieldPower) > (oppGraveyardPower - oppBattlefieldPower);
|
||||
return (aiGraveyardPower - aiBattlefieldPower) > (oppGraveyardPower - oppBattlefieldPower + threshold);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,6 +459,32 @@ public class SpecialCardAi {
|
||||
}
|
||||
}
|
||||
|
||||
// Momir Vig, Simic Visionary Avatar
|
||||
public static class MomirVigAvatar {
|
||||
public static boolean consider(Player ai, SpellAbility sa) {
|
||||
Card source = sa.getHostCard();
|
||||
|
||||
if (source.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN1)) {
|
||||
return false;
|
||||
}
|
||||
// Set PayX here to maximum value.
|
||||
int tokenSize = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
|
||||
// Some basic strategy for Momir
|
||||
if (tokenSize < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tokenSize > 11) {
|
||||
tokenSize = 11;
|
||||
}
|
||||
|
||||
source.setSVar("PayX", Integer.toString(tokenSize));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Necropotence
|
||||
public static class Necropotence {
|
||||
public static boolean consider(Player ai, SpellAbility sa) {
|
||||
|
||||
@@ -6,10 +6,7 @@ import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.StaticData;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.card.CardDb;
|
||||
import forge.card.CardRules;
|
||||
import forge.card.CardSplitType;
|
||||
@@ -36,22 +33,9 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
||||
|
||||
String logic = sa.getParam("AILogic");
|
||||
if (logic.equals("MomirAvatar")) {
|
||||
if (source.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN1)) {
|
||||
return false;
|
||||
}
|
||||
// Set PayX here to maximum value.
|
||||
int tokenSize = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
|
||||
// Some basic strategy for Momir
|
||||
if (tokenSize < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tokenSize > 11) {
|
||||
tokenSize = 11;
|
||||
}
|
||||
|
||||
source.setSVar("PayX", Integer.toString(tokenSize));
|
||||
return SpecialCardAi.MomirVigAvatar.consider(ai, sa);
|
||||
} else if (logic.equals("CursedScroll")) {
|
||||
return SpecialCardAi.CursedScroll.consider(ai, sa);
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
@@ -78,6 +62,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
|
||||
return ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
@@ -18,6 +19,7 @@ import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -49,6 +51,19 @@ public class EffectAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("KeepOppCreatsLandsTapped")) {
|
||||
for (Player opp : ai.getOpponents()) {
|
||||
boolean worthHolding = false;
|
||||
if (CardLists.filter(opp.getCardsIn(ZoneType.Battlefield),
|
||||
Predicates.and(Predicates.or(CardPredicates.Presets.LANDS, CardPredicates.Presets.CREATURES),
|
||||
CardPredicates.Presets.TAPPED)).size() >= 3) {
|
||||
worthHolding = true;
|
||||
}
|
||||
if (!worthHolding) {
|
||||
return false;
|
||||
}
|
||||
randomReturn = true;
|
||||
}
|
||||
} else if (logic.equals("Fog")) {
|
||||
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||
return false;
|
||||
|
||||
@@ -5,4 +5,6 @@ PT:4/4
|
||||
K:You may exert CARDNAME as it attacks.
|
||||
T:Mode$ Exerted | ValidCard$ Card.Self | Execute$ TrigUntapAll | TriggerDescription$ When you exert CARDNAME, untap all other creatures you control.
|
||||
SVar:TrigUntapAll:DB$ UntapAll | ValidCards$ Creature.YouCtrl+Other
|
||||
SVar:AIExertCondition:NumCreats GE3
|
||||
SVar:NumCreats:Count$Valid Creature.YouCtrl+tapped
|
||||
Oracle:You may exert Ahn-Crop Champion as it attacks. When you do, untap all other creatures you control. (An exerted creature won't untap during your next untap step.)
|
||||
@@ -1,10 +1,9 @@
|
||||
Name:Cursed Scroll
|
||||
ManaCost:1
|
||||
Types:Artifact
|
||||
A:AB$ NameCard | Cost$ 3 T | Defined$ You | SubAbility$ DBReveal | SpellDescription$ Choose a card name, then reveal a card at random from your hand. If that card has the chosen name, CARDNAME deals 2 damage to target creature or player.
|
||||
A:AB$ NameCard | Cost$ 3 T | Defined$ You | SubAbility$ DBReveal | AILogic$ CursedScroll | SpellDescription$ Choose a card name, then reveal a card at random from your hand. If that card has the chosen name, CARDNAME deals 2 damage to target creature or player.
|
||||
SVar:DBReveal:DB$ Reveal | Random$ True | RememberRevealed$ True | Defined$ You | SubAbility$ DBDamage
|
||||
SVar:DBDamage:DB$ DealDamage | NumDmg$ 2 | ValidTgts$ Creature,Player | TgtPrompt$ Select target creature or player | ConditionDefined$ Remembered | ConditionPresent$ Card.NamedCard | ConditionCompare$ EQ1 | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
SVar:RemAIDeck:True
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/cursed_scroll.jpg
|
||||
Oracle:{3}, {T}: Choose a card name, then reveal a card at random from your hand. If that card has the chosen name, Cursed Scroll deals 2 damage to target creature or player.
|
||||
|
||||
Reference in New Issue
Block a user