mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 03:38:01 +00:00
Merge branch 'aifix' into 'master'
Fix targeting of some effects (e.g. Axis of Mortality) See merge request core-developers/forge!5866
This commit is contained in:
@@ -218,7 +218,7 @@ public class ComputerUtilAbility {
|
||||
public static boolean isFullyTargetable(SpellAbility sa) {
|
||||
SpellAbility sub = sa;
|
||||
while (sub != null) {
|
||||
if (sub.usesTargeting() && !sub.getTargetRestrictions().hasCandidates(sub)) {
|
||||
if (sub.usesTargeting() && sub.getTargetRestrictions().getNumCandidates(sub, true) < sub.getMinTargets()) {
|
||||
return false;
|
||||
}
|
||||
sub = sub.getSubAbility();
|
||||
|
||||
@@ -54,7 +54,7 @@ public class AddTurnAi extends SpellAbilityAi {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!sa.getTargetRestrictions().isMinTargetsChosen(sa.getHostCard(), sa) && sa.canTarget(opp)) {
|
||||
if (!sa.getTargetRestrictions().isMinTargetsChosen(sa.getHostCard(), sa) && opp != null) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
return false;
|
||||
|
||||
@@ -21,14 +21,13 @@ public class DamageEachAi extends DamageAiBase {
|
||||
Player weakestOpp = targetableOpps.min(PlayerPredicates.compareByLife());
|
||||
|
||||
if (sa.usesTargeting() && weakestOpp != null) {
|
||||
if ("MadSarkhanUltimate".equals(logic) && !SpecialCardAi.SarkhanTheMad.considerUltimate(ai, sa, weakestOpp)) {
|
||||
return false;
|
||||
}
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(weakestOpp);
|
||||
}
|
||||
|
||||
if ("MadSarkhanUltimate".equals(logic)) {
|
||||
return SpecialCardAi.SarkhanTheMad.considerUltimate(ai, sa, weakestOpp);
|
||||
}
|
||||
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
final int iDmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||
return shouldTgtP(ai, sa, iDmg, false);
|
||||
|
||||
@@ -47,7 +47,7 @@ public class DigAi extends SpellAbilityAi {
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (!opp.canBeTargetedBy(sa)) {
|
||||
if (!sa.canTarget(opp)) {
|
||||
return false;
|
||||
}
|
||||
sa.getTargets().add(opp);
|
||||
|
||||
@@ -62,7 +62,6 @@ public final class EncodeAi extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
@@ -18,14 +19,15 @@ public class LifeExchangeAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
final int myLife = aiPlayer.getLife();
|
||||
Player opponent = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||
final int hLife = opponent.getLife();
|
||||
|
||||
if (!aiPlayer.canGainLife()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int myLife = aiPlayer.getLife();
|
||||
final PlayerCollection targetableOpps = aiPlayer.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
final Player opponent = targetableOpps.max(PlayerPredicates.compareByLife());
|
||||
final int hLife = opponent == null ? 0 : opponent.getLife();
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
@@ -36,24 +38,23 @@ public class LifeExchangeAi extends SpellAbilityAi {
|
||||
*/
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (opponent.canBeTargetedBy(sa)) {
|
||||
if (opponent != null && opponent.canLoseLife()) {
|
||||
// never target self, that would be silly for exchange
|
||||
sa.getTargets().add(opponent);
|
||||
if (!opponent.canLoseLife()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// if life is in danger, always activate
|
||||
if ((myLife < 5) && (hLife > myLife)) {
|
||||
if (myLife < 5 && hLife > myLife) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// cost includes sacrifice probably, so make sure it's worth it
|
||||
chance &= (hLife > (myLife + 8));
|
||||
|
||||
return ((MyRandom.getRandom().nextFloat() < .6667) && chance);
|
||||
return MyRandom.getRandom().nextFloat() < .6667 && chance;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,13 +71,16 @@ public class LifeExchangeAi extends SpellAbilityAi {
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
|
||||
final boolean mandatory) {
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
Player opp = targetableOpps.max(PlayerPredicates.compareByLife());
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
||||
sa.getTargets().add(opp);
|
||||
if (sa.canAddMoreTarget()) {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -149,8 +149,7 @@ public class LifeExchangeVariantAi extends SpellAbilityAi {
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
|
||||
final boolean mandatory) {
|
||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class LifeSetAi extends SpellAbilityAi {
|
||||
@@ -17,14 +23,11 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final int myLife = ai.getLife();
|
||||
final Player opponent = ai.getStrongestOpponent();
|
||||
final int hlife = opponent.getLife();
|
||||
final PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
final Player opponent = targetableOpps.max(PlayerPredicates.compareByLife());
|
||||
final int hlife = opponent == null ? 0 : opponent.getLife();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
|
||||
if (!ai.canGainLife()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't use setLife before main 2 if possible
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||
&& !sa.hasParam("ActivationPhases")) {
|
||||
@@ -55,20 +58,20 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
sa.getTargets().add(opponent);
|
||||
// if we can only target the human, and the Human's life
|
||||
// would go up, don't play it.
|
||||
// possibly add a combo here for Magister Sphinx and
|
||||
// Higedetsu's (sp?) Second Rite
|
||||
if ((amount > hlife) || !opponent.canLoseLife()) {
|
||||
if (opponent == null || amount > hlife || !opponent.canLoseLife()) {
|
||||
return false;
|
||||
}
|
||||
sa.getTargets().add(opponent);
|
||||
} else {
|
||||
if ((amount > myLife) && (myLife <= 10)) {
|
||||
if (amount > myLife && myLife <= 10 && ai.canGainLife()) {
|
||||
sa.getTargets().add(ai);
|
||||
} else if (hlife > amount) {
|
||||
sa.getTargets().add(opponent);
|
||||
} else if (amount > myLife) {
|
||||
} else if (amount > myLife && ai.canGainLife()) {
|
||||
sa.getTargets().add(ai);
|
||||
} else {
|
||||
return false;
|
||||
@@ -90,18 +93,19 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// if life is in danger, always activate
|
||||
if ((myLife < 3) && (amount > myLife)) {
|
||||
if (myLife < 3 && amount > myLife && ai.canGainLife()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ((MyRandom.getRandom().nextFloat() < .6667) && chance);
|
||||
return MyRandom.getRandom().nextFloat() < .6667 && chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final int myLife = ai.getLife();
|
||||
final Player opponent = ai.getStrongestOpponent();
|
||||
final int hlife = opponent.getLife();
|
||||
final PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
final Player opponent = targetableOpps.max(PlayerPredicates.compareByLife());
|
||||
final int hlife = opponent == null ? 0 : opponent.getLife();
|
||||
final Card source = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
|
||||
@@ -118,13 +122,13 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// special cases when amount can't be calculated without targeting first
|
||||
if (amount == 0 && "TargetedPlayer$StartingLife/HalfDown".equals(source.getSVar(amountStr))) {
|
||||
if (amount == 0 && opponent != null && "TargetedPlayer$StartingLife/HalfDown".equals(source.getSVar(amountStr))) {
|
||||
// e.g. Torgaar, Famine Incarnate
|
||||
return doHalfStartingLifeLogic(ai, opponent, sa);
|
||||
}
|
||||
|
||||
if (sourceName.equals("Eternity Vessel")
|
||||
&& (opponent.isCardInPlay("Vampire Hexmage") || (source.getCounters(CounterEnumType.CHARGE) == 0))) {
|
||||
&& (Iterables.any(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Vampire Hexmage")) || (source.getCounters(CounterEnumType.CHARGE) == 0))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -134,13 +138,16 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
if (opponent == null) {
|
||||
return false;
|
||||
}
|
||||
sa.getTargets().add(opponent);
|
||||
} else {
|
||||
if (amount > myLife && myLife <= 10) {
|
||||
sa.getTargets().add(ai);
|
||||
} else if (hlife > amount) {
|
||||
sa.getTargets().add(opponent);
|
||||
} else if (amount > myLife) {
|
||||
} else if (amount > myLife || mandatory) {
|
||||
sa.getTargets().add(ai);
|
||||
} else {
|
||||
return false;
|
||||
|
||||
@@ -25,8 +25,7 @@ public class PoisonAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
return !ph.getPhase().isBefore(PhaseType.MAIN2)
|
||||
|| sa.hasParam("ActivationPhases");
|
||||
return !ph.getPhase().isBefore(PhaseType.MAIN2) || sa.hasParam("ActivationPhases");
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -31,11 +31,10 @@ public class PowerExchangeAi extends SpellAbilityAi {
|
||||
|
||||
List<Card> list =
|
||||
CardLists.getValidCards(ai.getGame().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
// AI won't try to grab cards that are filtered out of AI decks on purpose
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return !ComputerUtilCard.isCardRemAIDeck(c) && c.canBeTargetedBy(sa) && c.getController() != ai;
|
||||
return c.canBeTargetedBy(sa) && c.getController() != ai;
|
||||
}
|
||||
});
|
||||
CardLists.sortByPowerAsc(list);
|
||||
|
||||
@@ -26,7 +26,6 @@ public class ProtectAllAi extends SpellAbilityAi {
|
||||
return false;
|
||||
} // protectAllCanPlayAI()
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return true;
|
||||
|
||||
@@ -53,7 +53,7 @@ public class RearrangeTopOfLibraryAi extends SpellAbilityAi {
|
||||
PlayerCollection targetableOpps = aiPlayer.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
Player opp = targetableOpps.min(PlayerPredicates.compareByLife());
|
||||
final boolean canTgtAI = sa.canTarget(aiPlayer);
|
||||
final boolean canTgtHuman = opp != null && sa.canTarget(opp);
|
||||
final boolean canTgtHuman = sa.canTarget(opp);
|
||||
|
||||
if (canTgtHuman && canTgtAI) {
|
||||
// TODO: maybe some other consideration rather than random?
|
||||
|
||||
@@ -138,12 +138,11 @@ public class RegenerateAi extends SpellAbilityAi {
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
boolean chance = false;
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt == null) {
|
||||
if (sa.usesTargeting()) {
|
||||
chance = regenMandatoryTarget(ai, sa, mandatory);
|
||||
} else {
|
||||
// If there's no target on the trigger, just say yes.
|
||||
chance = true;
|
||||
} else {
|
||||
chance = regenMandatoryTarget(ai, sa, mandatory);
|
||||
}
|
||||
|
||||
return chance;
|
||||
@@ -164,7 +163,7 @@ public class RegenerateAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mandatory && (compTargetables.size() == 0)) {
|
||||
if (!mandatory && compTargetables.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
@@ -15,6 +16,8 @@ import forge.game.card.CardPredicates;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
@@ -22,14 +25,14 @@ public class SacrificeAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
return sacrificeTgtAI(ai, sa);
|
||||
return sacrificeTgtAI(ai, sa, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
// AI should only activate this during Human's turn
|
||||
|
||||
return sacrificeTgtAI(ai, sa);
|
||||
return sacrificeTgtAI(ai, sa, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -48,21 +51,24 @@ public class SacrificeAi extends SpellAbilityAi {
|
||||
// Eventually, we can call the trigger of ETB abilities with not
|
||||
// mandatory as part of the checks to cast something
|
||||
|
||||
return sacrificeTgtAI(ai, sa) || mandatory;
|
||||
return sacrificeTgtAI(ai, sa, mandatory) || mandatory;
|
||||
}
|
||||
|
||||
private boolean sacrificeTgtAI(final Player ai, final SpellAbility sa) {
|
||||
private boolean sacrificeTgtAI(final Player ai, final SpellAbility sa, boolean mandatory) {
|
||||
final Card source = sa.getHostCard();
|
||||
final boolean destroy = sa.hasParam("Destroy");
|
||||
|
||||
Player opp = ai.getStrongestOpponent();
|
||||
final PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
final Player opp = Collections.max(targetableOpps, PlayerPredicates.compareByLife());
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (!opp.canBeTargetedBy(sa)) {
|
||||
if (opp == null) {
|
||||
return false;
|
||||
}
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
final String valid = sa.getParam("SacValid");
|
||||
String num = sa.getParamOrDefault("Amount" , "1");
|
||||
final int amount = AbilityUtils.calculateAmount(source, num, sa);
|
||||
@@ -79,7 +85,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
||||
|
||||
for (Card c : list) {
|
||||
if (c.hasSVar("SacMe") && Integer.parseInt(c.getSVar("SacMe")) > 3) {
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!destroy) {
|
||||
@@ -131,7 +137,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
||||
|
||||
List<Card> humanList = null;
|
||||
try {
|
||||
humanList = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), source, sa);
|
||||
humanList = CardLists.getValidCards(ai.getStrongestOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), source, sa);
|
||||
} catch (NullPointerException e) {
|
||||
return false;
|
||||
} finally {
|
||||
|
||||
@@ -16,6 +16,8 @@ import forge.game.card.CardPredicates;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
@@ -101,10 +103,14 @@ public class TapAllAi extends SpellAbilityAi {
|
||||
CardCollectionView validTappables = getTapAllTargets(valid, source, sa);
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
final PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
Player target = targetableOpps.max(PlayerPredicates.compareByLife());
|
||||
if (target == null && mandatory) {
|
||||
target = ai;
|
||||
}
|
||||
sa.resetTargets();
|
||||
Player opp = ai.getStrongestOpponent();
|
||||
sa.getTargets().add(opp);
|
||||
validTappables = opp.getCardsIn(ZoneType.Battlefield);
|
||||
sa.getTargets().add(target);
|
||||
validTappables = target.getCardsIn(ZoneType.Battlefield);
|
||||
}
|
||||
|
||||
if (mandatory) {
|
||||
|
||||
Reference in New Issue
Block a user