Merge branch 'aifix' into 'master'

Fix Muse Vessel + Hexproof

See merge request core-developers/forge!5587
This commit is contained in:
Michael Kamensky
2021-10-19 11:31:47 +00:00
6 changed files with 48 additions and 30 deletions

View File

@@ -35,7 +35,11 @@ public class ActivateAbilityAi extends SpellAbilityAi {
} }
} else { } else {
sa.resetTargets(); sa.resetTargets();
if (sa.canTarget(opp)) {
sa.getTargets().add(opp); sa.getTargets().add(opp);
} else {
return false;
}
} }
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
@@ -57,7 +61,6 @@ public class ActivateAbilityAi extends SpellAbilityAi {
return defined.contains(opp); return defined.contains(opp);
} }
} else { } else {
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(opp); sa.getTargets().add(opp);

View File

@@ -344,6 +344,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(ai); sa.getTargets().add(ai);
} }
if (!sa.isTargetNumberValid()) {
return false;
}
pDefined = sa.getTargets().getTargetPlayers(); pDefined = sa.getTargets().getTargetPlayers();
} else { } else {
if (sa.hasParam("DefinedPlayer")) { if (sa.hasParam("DefinedPlayer")) {

View File

@@ -1,6 +1,7 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
@@ -138,13 +139,17 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
return true; return true;
} else { } else {
// search targetable Opponents // search targetable Opponents
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(), PlayerPredicates.isTargetableBy(sa)); final List<Player> oppList = Lists.newArrayList(Iterables.filter(ai.getOpponents(), PlayerPredicates.isTargetableBy(sa)));
if (oppList.isEmpty()) {
return false;
}
// get the one with the most handsize // get the one with the most handsize
Player oppTarget = Collections.max(Lists.newArrayList(oppList), PlayerPredicates.compareByZoneSize(origin)); Player oppTarget = Collections.max(oppList, PlayerPredicates.compareByZoneSize(origin));
// set the target // set the target
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) { if (!oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) {
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(oppTarget); sa.getTargets().add(oppTarget);
} else { } else {
@@ -152,24 +157,26 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
} }
} }
} else if (origin.equals(ZoneType.Battlefield)) { } else if (origin.equals(ZoneType.Battlefield)) {
// this statement is assuming the AI is trying to use this spell // this statement is assuming the AI is trying to use this spell offensively
// offensively // if the AI is using it defensively, then something else needs to occur
// if the AI is using it defensively, then something else needs to
// occur
// if only creatures are affected evaluate both lists and pass only // if only creatures are affected evaluate both lists and pass only
// if human creatures are more valuable // if human creatures are more valuable
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
// search targetable Opponents // search targetable Opponents
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(), final List<Player> oppList = Lists.newArrayList(Iterables.filter(ai.getOpponents(),
PlayerPredicates.isTargetableBy(sa)); PlayerPredicates.isTargetableBy(sa)));
if (oppList.isEmpty()) {
return false;
}
// get the one with the most in graveyard // get the one with the most in graveyard
// zone is visible so evaluate which would be hurt the most // zone is visible so evaluate which would be hurt the most
Player oppTarget = Collections.max(Lists.newArrayList(oppList), Player oppTarget = Collections.max(oppList,
PlayerPredicates.compareByZoneSize(origin)); PlayerPredicates.compareByZoneSize(origin));
// set the target // set the target
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) { if (oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(oppTarget); sa.getTargets().add(oppTarget);
} else { } else {
@@ -234,7 +241,7 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa)); AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
// set the target // set the target
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) { if (!oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(oppTarget); sa.getTargets().add(oppTarget);
} else { } else {
@@ -380,15 +387,19 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) { if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
// search targetable Opponents // search targetable Opponents
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(), final List<Player> oppList = Lists.newArrayList(Iterables.filter(ai.getOpponents(),
PlayerPredicates.isTargetableBy(sa)); PlayerPredicates.isTargetableBy(sa)));
if (oppList.isEmpty()) {
return false;
}
// get the one with the most handsize // get the one with the most handsize
Player oppTarget = Collections.max(Lists.newArrayList(oppList), Player oppTarget = Collections.max(oppList,
PlayerPredicates.compareByZoneSize(origin)); PlayerPredicates.compareByZoneSize(origin));
// set the target // set the target
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) { if (!oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) {
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(oppTarget); sa.getTargets().add(oppTarget);
} else { } else {
@@ -418,16 +429,20 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
} else if (origin.equals(ZoneType.Graveyard)) { } else if (origin.equals(ZoneType.Graveyard)) {
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
// search targetable Opponents // search targetable Opponents
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(), final List<Player> oppList = Lists.newArrayList(Iterables.filter(ai.getOpponents(),
PlayerPredicates.isTargetableBy(sa)); PlayerPredicates.isTargetableBy(sa)));
if (oppList.isEmpty()) {
return false;
}
// get the one with the most in graveyard // get the one with the most in graveyard
// zone is visible so evaluate which would be hurt the most // zone is visible so evaluate which would be hurt the most
Player oppTarget = Collections.max(Lists.newArrayList(oppList), Player oppTarget = Collections.max(oppList,
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa)); AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
// set the target // set the target
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) { if (!oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(oppTarget); sa.getTargets().add(oppTarget);
} else { } else {

View File

@@ -107,8 +107,7 @@ public class CountersPutAi extends CountersAi {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
if (sa.isOutlast()) { if (sa.isOutlast()) {
if (ph.is(PhaseType.MAIN2, ai)) { // applicable to non-attackers if (ph.is(PhaseType.MAIN2, ai)) { // applicable to non-attackers only
// only
float chance = 0.8f; float chance = 0.8f;
if (ComputerUtilCard.doesSpecifiedCreatureBlock(ai, source)) { if (ComputerUtilCard.doesSpecifiedCreatureBlock(ai, source)) {
return false; return false;
@@ -788,7 +787,7 @@ public class CountersPutAi extends CountersAi {
List<Player> playerList = Lists.newArrayList(Iterables.filter( List<Player> playerList = Lists.newArrayList(Iterables.filter(
sa.getTargetRestrictions().getAllCandidates(sa, true, true), Player.class)); sa.getTargetRestrictions().getAllCandidates(sa, true, true), Player.class));
if (playerList.isEmpty() && mandatory) { if (playerList.isEmpty()) {
return false; return false;
} }

View File

@@ -117,8 +117,7 @@ public class MillAi extends SpellAbilityAi {
// if it would mill none, try other one // if it would mill none, try other one
if (numCards <= 0) { if (numCards <= 0) {
if ((sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z"))) if ((sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z"))) {
{
if (source.getSVar("X").startsWith("Count$xPaid")) { if (source.getSVar("X").startsWith("Count$xPaid")) {
// Spell is PayX based // Spell is PayX based
} else if (source.getSVar("X").startsWith("Remembered$ChromaSource")) { } else if (source.getSVar("X").startsWith("Remembered$ChromaSource")) {
@@ -136,7 +135,7 @@ public class MillAi extends SpellAbilityAi {
continue; continue;
} }
// if that player can be miled, select this one. // if that player can be milled, select this one.
if (numCards >= pLibrary.size()) { if (numCards >= pLibrary.size()) {
sa.getTargets().add(o); sa.getTargets().add(o);
return true; return true;

View File

@@ -3,8 +3,7 @@ ManaCost:4 B B
Types:Legendary Creature Demon Spirit Types:Legendary Creature Demon Spirit
PT:6/4 PT:6/4
T:Mode$ SpellCast | ValidCard$ Spirit,Arcane | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigExile | TriggerDescription$ Whenever you cast a Spirit or Arcane spell, target opponent exiles a card from their hand. T:Mode$ SpellCast | ValidCard$ Spirit,Arcane | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigExile | TriggerDescription$ Whenever you cast a Spirit or Arcane spell, target opponent exiles a card from their hand.
#This needs Defined$ Opponent because ValidTgts$ Opponent lets Kyoki's controller select the card to be exiled SVar:TrigExile:DB$ChangeZone | Origin$ Hand | Destination$ Exile | ChangeType$ Card | ValidTgts$ Opponent | Chooser$ Targeted | TgtPrompt$ Select target opponent | ChangeNum$ 1 | Mandatory$ True
SVar:TrigExile:DB$ChangeZone | Origin$ Hand | Destination$ Exile | ChangeType$ Card | ValidTgts$ Opponent | Chooser$ Targeted | TgtPrompt$ Select target opponent | ChangeNum$ 1
AI:RemoveDeck:Random AI:RemoveDeck:Random
DeckHints:Type$Spirit|Arcane DeckHints:Type$Spirit|Arcane
SVar:Picture:http://www.wizards.com/global/images/magic/general/kyoki_sanitys_eclipse.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/kyoki_sanitys_eclipse.jpg