mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 11:48:02 +00:00
Add more AI for TurboFog cards
This commit is contained in:
@@ -1497,7 +1497,7 @@ public class ComputerUtil {
|
||||
}
|
||||
}
|
||||
|
||||
all.addAll(ai.getCardsActivableInExternalZones(true));
|
||||
all.addAll(ai.getCardsActivatableInExternalZones(true));
|
||||
all.addAll(ai.getCardsIn(ZoneType.Hand));
|
||||
|
||||
for (final Card c : all) {
|
||||
@@ -1538,7 +1538,7 @@ public class ComputerUtil {
|
||||
public static boolean hasAFogEffect(final Player defender, final Player ai, boolean checkingOther) {
|
||||
final CardCollection all = new CardCollection(defender.getCardsIn(ZoneType.Battlefield));
|
||||
|
||||
all.addAll(defender.getCardsActivableInExternalZones(true));
|
||||
all.addAll(defender.getCardsActivatableInExternalZones(true));
|
||||
// TODO check if cards can be viewed instead
|
||||
if (!checkingOther) {
|
||||
all.addAll(defender.getCardsIn(ZoneType.Hand));
|
||||
@@ -1590,7 +1590,7 @@ public class ComputerUtil {
|
||||
public static int possibleNonCombatDamage(final Player ai, final Player enemy) {
|
||||
int damage = 0;
|
||||
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
||||
all.addAll(ai.getCardsActivableInExternalZones(true));
|
||||
all.addAll(ai.getCardsActivatableInExternalZones(true));
|
||||
all.addAll(CardLists.filter(ai.getCardsIn(ZoneType.Hand), Predicates.not(Presets.PERMANENTS)));
|
||||
|
||||
for (final Card c : all) {
|
||||
|
||||
@@ -1299,6 +1299,8 @@ public class PlayerControllerAi extends PlayerController {
|
||||
name = ComputerUtilCard.getMostProminentCardName(cards);
|
||||
} else if (logic.equals("CursedScroll")) {
|
||||
name = SpecialCardAi.CursedScroll.chooseCard(player, sa);
|
||||
} else if (logic.equals("PithingNeedle")) {
|
||||
name = SpecialCardAi.PithingNeedle.chooseCard(player, sa);
|
||||
}
|
||||
|
||||
if (!StringUtils.isBlank(name)) {
|
||||
|
||||
@@ -293,6 +293,47 @@ public class SpecialCardAi {
|
||||
}
|
||||
}
|
||||
|
||||
public static class PithingNeedle {
|
||||
public static String chooseCard(final Player ai, final SpellAbility sa) {
|
||||
// TODO Remove names of cards already named by other Pithing Needles
|
||||
Card best = null;
|
||||
|
||||
CardCollection oppPerms = CardLists.getValidCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), "Card.OppCtrl+hasNonmanaAbilities", ai, sa.getHostCard(), sa);
|
||||
if (!oppPerms.isEmpty()) {
|
||||
return chooseCardFromList(oppPerms).getName();
|
||||
}
|
||||
|
||||
CardCollection visibleZones = CardLists.getValidCards(ai.getOpponents().getCardsIn(Lists.newArrayList(ZoneType.Graveyard, ZoneType.Exile)), "Card.OppCtrl+hasNonmanaAbilities", ai, sa.getHostCard(), sa);
|
||||
if (!visibleZones.isEmpty()) {
|
||||
// If nothing on the battlefield has a nonmana ability choose something
|
||||
return chooseCardFromList(visibleZones).getName();
|
||||
}
|
||||
|
||||
return chooseNonBattlefieldName();
|
||||
}
|
||||
|
||||
static public Card chooseCardFromList(CardCollection cardlist) {
|
||||
Card best = ComputerUtilCard.getBestPlaneswalkerAI(cardlist);
|
||||
if (best != null) {
|
||||
// No planeswalkers choose something!
|
||||
return best;
|
||||
}
|
||||
|
||||
best = ComputerUtilCard.getBestCreatureAI(cardlist);
|
||||
if (best == null) {
|
||||
// If nothing on the battlefield has a nonmana ability choose something
|
||||
Collections.shuffle(cardlist);
|
||||
best = cardlist.getFirst();
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
static public String chooseNonBattlefieldName() {
|
||||
return "Liliana of the Veil";
|
||||
}
|
||||
}
|
||||
|
||||
// Deathgorge Scavenger
|
||||
public static class DeathgorgeScavenger {
|
||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||
|
||||
@@ -1068,7 +1068,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
CardCollection cards = new CardCollection();
|
||||
cards.addAll(ai.getCardsIn(ZoneType.Hand));
|
||||
cards.addAll(ai.getCardsIn(ZoneType.Battlefield));
|
||||
cards.addAll(ai.getCardsActivableInExternalZones(true));
|
||||
cards.addAll(ai.getCardsActivatableInExternalZones(true));
|
||||
for (Card c : cards) {
|
||||
if (c.getZone().getPlayer() != null && c.getZone().getPlayer() != ai && c.mayPlay(ai).isEmpty()) {
|
||||
continue;
|
||||
|
||||
@@ -13,7 +13,6 @@ import forge.ai.AiCardMemory;
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
@@ -82,21 +81,11 @@ public class EffectAi extends SpellAbilityAi {
|
||||
randomReturn = true;
|
||||
}
|
||||
} else if (logic.equals("Fog")) {
|
||||
if (phase.isPlayerTurn(sa.getActivatingPlayer())) {
|
||||
return false;
|
||||
}
|
||||
if (!phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return false;
|
||||
}
|
||||
if (!game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
|
||||
FogAi fogAi = new FogAi();
|
||||
if (!fogAi.canPlayAI(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
@@ -258,6 +247,21 @@ public class EffectAi extends SpellAbilityAi {
|
||||
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false, false)) {
|
||||
return false;
|
||||
}
|
||||
} else if (logic.equals("PeaceTalks")) {
|
||||
Player nextPlayer = game.getNextPlayerAfter(ai);
|
||||
|
||||
// If opponent doesn't have creatures, preventing attacks don't mean as much
|
||||
if (nextPlayer.getCreaturesInPlay().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only cast Peace Talks after you attack just in case you have creatures
|
||||
if (!phase.is(PhaseType.MAIN2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a pseudo combat and see if my life is in danger
|
||||
return randomReturn;
|
||||
} else if (logic.equals("Bribe")) {
|
||||
Card host = sa.getHostCard();
|
||||
Combat combat = game.getCombat();
|
||||
|
||||
@@ -10,13 +10,15 @@ import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Aggregates;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class FogAi extends SpellAbilityAi {
|
||||
|
||||
@@ -27,12 +29,70 @@ public class FogAi extends SpellAbilityAi {
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Game game = ai.getGame();
|
||||
final Card hostCard = sa.getHostCard();
|
||||
final Combat combat = game.getCombat();
|
||||
|
||||
// Don't cast it, if the effect is already in place
|
||||
if (game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO Test if we can even Fog successfully
|
||||
if (handleMemoryCheck(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Only cast when Stack is empty, so Human uses spells/abilities first
|
||||
if (!game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO Only cast outside of combat if I won't be able to cast inside of combat
|
||||
if (combat == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// AI should only activate this during Opponents Declare Blockers phase
|
||||
if (!game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai) ||
|
||||
!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
// TODO Be careful of effects that don't let you cast spells during combat
|
||||
return false;
|
||||
}
|
||||
|
||||
int remainingLife = ComputerUtilCombat.lifeThatWouldRemain(ai, combat);
|
||||
int dmg = ai.getLife() - remainingLife;
|
||||
|
||||
// Count the number of Fog spells in hand
|
||||
int fogs = countAvailableFogs(ai);
|
||||
if (fogs > 2 && dmg > 2) {
|
||||
// Playing a fog deck. If you got them play them.
|
||||
return true;
|
||||
}
|
||||
if (dmg > 2 &&
|
||||
hostCard.hasKeyword(Keyword.BUYBACK) &&
|
||||
CardLists.count(ai.getCardsIn(ZoneType.Battlefield), Card::isLand) > 3) {
|
||||
// Constant mists sacrifices a land to buyback. But if AI is running it, they are probably ok sacrificing some lands
|
||||
return true;
|
||||
}
|
||||
|
||||
if ("SeriousDamage".equals(sa.getParam("AILogic"))) {
|
||||
if (dmg > ai.getLife() / 4) {
|
||||
return true;
|
||||
} else if (dmg >= 5) {
|
||||
return true;
|
||||
} else if (ai.getLife() < ai.getStartingLife() / 3) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// TODO Compare to poison counters?
|
||||
|
||||
// Cast it if life is in danger
|
||||
return ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
|
||||
}
|
||||
|
||||
private boolean handleMemoryCheck(Player ai, SpellAbility sa) {
|
||||
Card hostCard = sa.getHostCard();
|
||||
Game game = ai.getGame();
|
||||
|
||||
// if card would be destroyed, react and use immediately if it's not own turn
|
||||
if ((AiCardMemory.isRememberedCard(ai, hostCard, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT))
|
||||
&& (!game.getStack().isEmpty())
|
||||
@@ -54,42 +114,31 @@ public class FogAi extends SpellAbilityAi {
|
||||
AiCardMemory.rememberCard(ai, hostCard, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT);
|
||||
}
|
||||
}
|
||||
|
||||
// AI should only activate this during Human's Declare Blockers phase
|
||||
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||
return false;
|
||||
}
|
||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only cast when Stack is empty, so Human uses spells/abilities first
|
||||
if (!game.getStack().isEmpty()) {
|
||||
return false;
|
||||
private int countAvailableFogs(Player ai) {
|
||||
int fogs = 0;
|
||||
for (Card c : ai.getCardsActivatableInExternalZones(false)) {
|
||||
for (SpellAbility ability : c.getSpellAbilities()) {
|
||||
if (ability.getApi().equals(ApiType.Fog)) {
|
||||
fogs++;
|
||||
break;
|
||||
}
|
||||
|
||||
if ("SeriousDamage".equals(sa.getParam("AILogic")) && game.getCombat() != null) {
|
||||
int dmg = 0;
|
||||
for (Card atk : game.getCombat().getAttackersOf(ai)) {
|
||||
if (game.getCombat().isUnblocked(atk)) {
|
||||
dmg += atk.getNetCombatDamage();
|
||||
} else if (atk.hasKeyword(Keyword.TRAMPLE)) {
|
||||
dmg += atk.getNetCombatDamage() - Aggregates.sum(game.getCombat().getBlockers(atk), CardPredicates.Accessors.fnGetNetToughness);
|
||||
}
|
||||
}
|
||||
|
||||
if (dmg > ai.getLife() / 4) {
|
||||
return true;
|
||||
} else if (dmg >= 5) {
|
||||
return true;
|
||||
} else if (ai.getLife() < ai.getStartingLife() / 3) {
|
||||
return true;
|
||||
for (Card c : ai.getCardsIn(ZoneType.Hand)) {
|
||||
for (SpellAbility ability : c.getSpellAbilities()) {
|
||||
if (ability.getApi().equals(ApiType.Fog)) {
|
||||
fogs++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return fogs;
|
||||
}
|
||||
|
||||
// Cast it if life is in danger
|
||||
return ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.card.Card;
|
||||
@@ -9,6 +10,7 @@ import forge.game.card.CardLists;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
/**
|
||||
* AbilityFactory for Creature Spells.
|
||||
@@ -18,7 +20,31 @@ public class PermanentNoncreatureAi extends PermanentAi {
|
||||
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
return !"Never".equals(aiLogic) && !"DontCast".equals(aiLogic);
|
||||
if ("Never".equals(aiLogic) || "DontCast".equals(aiLogic)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Game game = ai.getGame();
|
||||
|
||||
if ("PithingNeedle".equals(aiLogic)) {
|
||||
// Make sure theres something in play worth Needlings.
|
||||
// Planeswalker or equipment or something
|
||||
|
||||
CardCollection oppPerms = CardLists.getValidCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), "Card.OppCtrl+hasNonmanaAbilities", ai, sa.getHostCard(), sa);
|
||||
if (oppPerms.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Card card = ComputerUtilCard.getBestPlaneswalkerAI(oppPerms);
|
||||
if (card != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 5 percent chance to cast per opposing card with a non mana ability
|
||||
return MyRandom.getRandom().nextFloat() <= .05 * oppPerms.size();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -374,7 +374,7 @@ public class UntapAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (game.getPhaseHandler().getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
// Blockers already set. Are there any dangerous unblocked creatures? Sort by creature that will deal the most damage?
|
||||
Card card = ComputerUtilCombat.mostDangerousAttacker(list, ai, activeCombat, true);
|
||||
|
||||
|
||||
@@ -1091,6 +1091,18 @@ public class CardProperty {
|
||||
if (!property.startsWith("without") && !card.hasStartOfUnHiddenKeyword(property.substring(4))) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("hasNonmanaAbilities")) {
|
||||
boolean hasAbilities = false;
|
||||
for(SpellAbility sa : card.getSpellAbilities()) {
|
||||
if (sa.isActivatedAbility() && !sa.isManaAbility()) {
|
||||
hasAbilities = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasAbilities) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("activated")) {
|
||||
if (!card.activatedThisTurn()) {
|
||||
return false;
|
||||
|
||||
@@ -1336,7 +1336,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return cards;
|
||||
}
|
||||
else if (zoneType == ZoneType.Flashback) {
|
||||
return getCardsActivableInExternalZones(true);
|
||||
return getCardsActivatableInExternalZones(true);
|
||||
}
|
||||
|
||||
PlayerZone zone = getZone(zoneType);
|
||||
@@ -1379,7 +1379,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return CardLists.filter(getCardsIn(zone), CardPredicates.nameEquals(cardName));
|
||||
}
|
||||
|
||||
public CardCollectionView getCardsActivableInExternalZones(boolean includeCommandZone) {
|
||||
public CardCollectionView getCardsActivatableInExternalZones(boolean includeCommandZone) {
|
||||
final CardCollection cl = new CardCollection();
|
||||
|
||||
cl.addAll(getZone(ZoneType.Graveyard).getCardsPlayerCanActivate(this));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Name:Peace Talks
|
||||
ManaCost:1 W
|
||||
Types:Sorcery
|
||||
A:SP$ Effect | StaticAbilities$ STCantAttack,STCantTarget,STCantTargetPlayer | Duration$ ThisTurnAndNextTurn | SpellDescription$ This turn and next turn, creatures can't attack, and players and permanents can't be the targets of spells or activated abilities.
|
||||
A:SP$ Effect | AILogic$ PeaceTalks | Stackable$ False | StaticAbilities$ STCantAttack,STCantTarget,STCantTargetPlayer | Duration$ ThisTurnAndNextTurn | SpellDescription$ This turn and next turn, creatures can't attack, and players and permanents can't be the targets of spells or activated abilities.
|
||||
SVar:STCantAttack:Mode$ CantAttack | EffectZone$ Command | ValidCard$ Creature | Description$ Creatures can't attack.
|
||||
SVar:STCantTarget:Mode$ CantTarget | ValidCard$ Permanent | EffectZone$ Command | ValidSA$ Spell,Activated | Description$ Permanents can't be the targets of spells or activated abilities.
|
||||
SVar:STCantTargetPlayer:Mode$ CantTarget | ValidPlayer$ Player | EffectZone$ Command | ValidSA$ Spell,Activated | Description$ Players can't be the targets of spells or activated abilities.
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
Name:Pithing Needle
|
||||
ManaCost:1
|
||||
Types:Artifact
|
||||
A:SP$ PermanentNoncreature | AILogic$ PithingNeedle
|
||||
K:ETBReplacement:Other:DBNameCard
|
||||
SVar:DBNameCard:DB$ NameCard | Defined$ You | SpellDescription$ As CARDNAME enters the battlefield, choose a card name.
|
||||
SVar:DBNameCard:DB$ NameCard | Defined$ You | AILogic$ PithingNeedle | SpellDescription$ As CARDNAME enters the battlefield, choose a card name.
|
||||
S:Mode$ CantBeActivated | ValidCard$ Card.NamedCard | ValidSA$ Activated.nonManaAbility | Description$ Activated abilities of sources with the chosen name can't be activated unless they're mana abilities.
|
||||
AI:RemoveDeck:Random
|
||||
Oracle:As Pithing Needle enters the battlefield, choose a card name.\nActivated abilities of sources with the chosen name can't be activated unless they're mana abilities.
|
||||
|
||||
@@ -223,7 +223,11 @@ public final class FModel {
|
||||
magicDb.setBrawlPredicate(formats.get("Brawl").getFilterRules());
|
||||
|
||||
magicDb.setFilteredHandsEnabled(preferences.getPrefBoolean(FPref.FILTERED_HANDS));
|
||||
try {
|
||||
magicDb.setMulliganRule(MulliganDefs.MulliganRule.valueOf(preferences.getPref(FPref.MULLIGAN_RULE)));
|
||||
} catch(Exception e) {
|
||||
magicDb.setMulliganRule(MulliganDefs.MulliganRule.London);
|
||||
}
|
||||
|
||||
blocks = new StorageBase<>("Block definitions", new CardBlock.Reader(ForgeConstants.BLOCK_DATA_DIR + "blocks.txt", magicDb.getEditions()));
|
||||
//setblockLands
|
||||
|
||||
Reference in New Issue
Block a user