mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 19:28:01 +00:00
- Improved Yawgmoth's Will AI.
- Moved it to SpecialCardAi and named the AI logic after the card for now because it looks like the effect is rather unique in the card pool.
This commit is contained in:
@@ -38,6 +38,8 @@ import forge.game.phase.PhaseType;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerPredicates;
|
import forge.game.player.PlayerPredicates;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.SpellAbilityRestriction;
|
||||||
|
import forge.game.spellability.SpellPermanent;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -459,4 +461,58 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Yawgmoth's Will (can potentially be expanded for other broadly similar effects too)
|
||||||
|
public static class YawgmothsWill {
|
||||||
|
public static boolean consider(Player ai, SpellAbility sa) {
|
||||||
|
CardCollectionView cardsInGY = ai.getCardsIn(ZoneType.Graveyard);
|
||||||
|
if (cardsInGY.size() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int minManaAdj = 2; // we want the AI to have some spare mana for possible other spells to cast
|
||||||
|
float minCastableInGY = 3.0f; // we want the AI to have several castable cards in GY before attempting this effect
|
||||||
|
List<SpellAbility> saList = ComputerUtilAbility.getSpellAbilities(cardsInGY, ai);
|
||||||
|
int selfCMC = sa.getPayCosts().getCostMana().getMana().getCMC();
|
||||||
|
|
||||||
|
float numCastable = 0.0f;
|
||||||
|
for (SpellAbility ab : saList) {
|
||||||
|
final Card src = ab.getHostCard();
|
||||||
|
|
||||||
|
if (ab.getApi() == ApiType.Counter) {
|
||||||
|
// cut short considering to play counterspells via Yawgmoth's Will
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ab.getHostCard().getName().equals(sa.getHostCard().getName())) {
|
||||||
|
// prevent infinitely recursing own ability when testing AI play decision
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check to see if the AI is willing to play this card
|
||||||
|
final SpellAbility testAb = ab.copy();
|
||||||
|
testAb.getRestrictions().setZone(ZoneType.Graveyard);
|
||||||
|
testAb.setActivatingPlayer(ai);
|
||||||
|
|
||||||
|
boolean willPlayAb = ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(testAb) == AiPlayDecision.WillPlay;
|
||||||
|
|
||||||
|
// Land drops are generally made by the AI in main 1 before casting spells, so testing for them is iffy.
|
||||||
|
if (!src.getType().isLand() && willPlayAb) {
|
||||||
|
int CMC = ab.getPayCosts().getTotalMana() != null ? ab.getPayCosts().getTotalMana().getCMC() : 0;
|
||||||
|
int Xcount = ab.getPayCosts().getTotalMana() != null ? ab.getPayCosts().getTotalMana().countX() : 0;
|
||||||
|
|
||||||
|
if ((Xcount == 0 && CMC == 0) || ComputerUtilMana.canPayManaCost(ab, ai, selfCMC + minManaAdj)) {
|
||||||
|
if (src.isInstant() || src.isSorcery()) {
|
||||||
|
// instants and sorceries are one-shot, so only treat them as 1/2 value for the purpose of meeting minimum
|
||||||
|
// castable cards in graveyard requirements
|
||||||
|
numCastable += 0.5f;
|
||||||
|
} else {
|
||||||
|
numCastable += 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return numCastable >= minCastableInGY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import forge.ai.ComputerUtil;
|
|||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCombat;
|
import forge.ai.ComputerUtilCombat;
|
||||||
|
import forge.ai.ComputerUtilDeck;
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.ai.ComputerUtilMana;
|
||||||
|
import forge.ai.SpecialCardAi;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.ai.SpellApiToAi;
|
import forge.ai.SpellApiToAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -216,34 +218,8 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
// for DamageDeal sub-abilities (eg. Wild Slash, Skullcrack)
|
// for DamageDeal sub-abilities (eg. Wild Slash, Skullcrack)
|
||||||
SpellAbility burn = sa.getSubAbility();
|
SpellAbility burn = sa.getSubAbility();
|
||||||
return SpellApiToAi.Converter.get(burn.getApi()).canPlayAIWithSubs(ai, burn);
|
return SpellApiToAi.Converter.get(burn.getApi()).canPlayAIWithSubs(ai, burn);
|
||||||
} else if (logic.equals("CastFromGraveyardUntilEOT")) {
|
} else if (logic.equals("YawgmothsWill")) {
|
||||||
// Effects that allow you to play stuff from graveyard until end of turn (e.g. Yawgmoth's Will)
|
return SpecialCardAi.YawgmothsWill.consider(ai, sa);
|
||||||
CardCollectionView cardsInGY = ai.getCardsIn(ZoneType.Graveyard);
|
|
||||||
if (cardsInGY.size() == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int minManaAdj = 2; // we want the AI to have some spare mana for possible other spells to cast from GY/hand
|
|
||||||
int minCastableInGY = 3; // we want the AI to have several castable cards in GY before attempting an effect
|
|
||||||
List<SpellAbility> saList = ComputerUtilAbility.getSpellAbilities(cardsInGY, ai);
|
|
||||||
int selfCMC = sa.getPayCosts().getCostMana().getMana().getCMC();
|
|
||||||
|
|
||||||
// Currently limited to considering nonland permanents, since instants and sorceries may be very contextual
|
|
||||||
// and land drops are generally made by the AI in main 1 before casting spells, so testing for them is iffy.
|
|
||||||
int numCastable = 0;
|
|
||||||
for (SpellAbility ab : saList) {
|
|
||||||
final Card src = ab.getHostCard();
|
|
||||||
if (ab instanceof SpellPermanent && !src.getType().isLand()) {
|
|
||||||
int CMC = ab.getPayCosts().getTotalMana() != null ? ab.getPayCosts().getTotalMana().getCMC() : 0;
|
|
||||||
int Xcount = ab.getPayCosts().getTotalMana() != null ? ab.getPayCosts().getTotalMana().countX() : 0;
|
|
||||||
|
|
||||||
if ((Xcount == 0 && CMC == 0) || ComputerUtilMana.canPayManaCost(ab, ai, selfCMC + minManaAdj)) {
|
|
||||||
numCastable++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return numCastable >= minCastableInGY;
|
|
||||||
}
|
}
|
||||||
} else { //no AILogic
|
} else { //no AILogic
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Name:Yawgmoth's Will
|
Name:Yawgmoth's Will
|
||||||
ManaCost:2 B
|
ManaCost:2 B
|
||||||
Types:Sorcery
|
Types:Sorcery
|
||||||
A:SP$ Effect | Cost$ 2 B | Name$ Yawgmoth's Will Effect | ReplacementEffects$ GraveToExile | StaticAbilities$ STPlay | SVars$ Exile | AILogic$ CastFromGraveyardUntilEOT | SpellDescription$ Until end of turn, you may play cards from your graveyard. If a card would be put into your graveyard from anywhere this turn, exile that card instead.
|
A:SP$ Effect | Cost$ 2 B | Name$ Yawgmoth's Will Effect | ReplacementEffects$ GraveToExile | StaticAbilities$ STPlay | SVars$ Exile | AILogic$ YawgmothsWill | SpellDescription$ Until end of turn, you may play cards from your graveyard. If a card would be put into your graveyard from anywhere this turn, exile that card instead.
|
||||||
SVar:STPlay:Mode$ Continuous | EffectZone$ Command | Affected$ Card.YouCtrl | AffectedZone$ Graveyard | MayPlay$ True | Description$ You may play cards from your graveyard.
|
SVar:STPlay:Mode$ Continuous | EffectZone$ Command | Affected$ Card.YouCtrl | AffectedZone$ Graveyard | MayPlay$ True | Description$ You may play cards from your graveyard.
|
||||||
SVar:GraveToExile:Event$ Moved | ActiveZones$ Command | Destination$ Graveyard | ValidCard$ Card.nonToken+YouOwn | ReplaceWith$ Exile | Description$ If a card would be put into your graveyard from anywhere, exile it instead.
|
SVar:GraveToExile:Event$ Moved | ActiveZones$ Command | Destination$ Graveyard | ValidCard$ Card.nonToken+YouOwn | ReplaceWith$ Exile | Description$ If a card would be put into your graveyard from anywhere, exile it instead.
|
||||||
SVar:Exile:AB$ ChangeZone | Cost$ 0 | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard
|
SVar:Exile:AB$ ChangeZone | Cost$ 0 | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard
|
||||||
|
|||||||
Reference in New Issue
Block a user