From 8d2e56e4dc0c37c2b7c6d4392b0146403b953a76 Mon Sep 17 00:00:00 2001 From: TRT <> Date: Wed, 14 Dec 2022 17:01:09 +0100 Subject: [PATCH] Improve damage check logic for losing + performance --- .../src/main/java/forge/ai/AiController.java | 73 +++++++++---------- .../java/forge/ai/ability/DamageAiBase.java | 9 +-- .../main/java/forge/game/player/Player.java | 2 +- .../game/spellability/TargetRestrictions.java | 2 +- 4 files changed, 39 insertions(+), 47 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 465f6c86b91..d5520580341 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -814,9 +814,6 @@ public class AiController { } public AiPlayDecision canPlaySa(SpellAbility sa) { - final Card card = sa.getHostCard(); - final boolean isRightTiming = sa.canCastTiming(player); - if (!checkAiSpecificRestrictions(sa)) { return AiPlayDecision.CantPlayAi; } @@ -824,6 +821,12 @@ public class AiController { return canPlaySa(((WrappedAbility) sa).getWrappedAbility()); } + if (!sa.canCastTiming(player)) { + return AiPlayDecision.AnotherTime; + } + + final Card card = sa.getHostCard(); + // Trying to play a card that has Buyback without a Buyback cost, look for possible additional considerations if (getBooleanProperty(AiProps.TRY_TO_PRESERVE_BUYBACK_SPELLS)) { if (card.hasKeyword(Keyword.BUYBACK) && !sa.isBuyBackAbility() && !canPlaySpellWithoutBuyback(card, sa)) { @@ -898,9 +901,6 @@ public class AiController { return AiPlayDecision.AnotherTime; } if (sa instanceof SpellPermanent) { - if (!isRightTiming) { - return AiPlayDecision.AnotherTime; - } return canPlayFromEffectAI((SpellPermanent)sa, false, true); } if (sa.usesTargeting()) { @@ -912,18 +912,13 @@ public class AiController { } } if (sa instanceof Spell) { - if (ComputerUtil.getDamageForPlaying(player, sa) >= player.getLife() - && !player.cantLoseForZeroOrLessLife() && player.canLoseLife()) { + if (!player.cantLoseForZeroOrLessLife() && player.canLoseLife() && + ComputerUtil.getDamageForPlaying(player, sa) >= player.getLife()) { return AiPlayDecision.CurseEffects; } - if (!isRightTiming) { - return AiPlayDecision.AnotherTime; - } return canPlaySpellBasic(card, sa); } - if (!isRightTiming) { - return AiPlayDecision.AnotherTime; - } + return AiPlayDecision.WillPlay; } @@ -1411,8 +1406,8 @@ public class AiController { if (!checkETBEffects(card, spell, null)) { return AiPlayDecision.BadEtbEffects; } - if (damage + ComputerUtil.getDamageFromETB(player, card) >= player.getLife() - && !player.cantLoseForZeroOrLessLife() && player.canLoseLife()) { + if (!player.cantLoseForZeroOrLessLife() && player.canLoseLife() + && damage + ComputerUtil.getDamageFromETB(player, card) >= player.getLife()) { return AiPlayDecision.BadEtbEffects; } } @@ -1493,38 +1488,36 @@ public class AiController { if (landsWannaPlay != null) { landsWannaPlay = filterLandsToPlay(landsWannaPlay); Log.debug("Computer " + game.getPhaseHandler().getPhase().nameForUi); - if (landsWannaPlay != null && !landsWannaPlay.isEmpty()) { + if (!landsWannaPlay.isEmpty()) { // TODO search for other land it might want to play? Card land = chooseBestLandToPlay(landsWannaPlay); - if (ComputerUtil.getDamageFromETB(player, land) < player.getLife() || !player.canLoseLife() - || player.cantLoseForZeroOrLessLife() ) { - if (!game.getPhaseHandler().is(PhaseType.MAIN1) || !isSafeToHoldLandDropForMain2(land)) { - final List abilities = Lists.newArrayList(); + if ((!player.canLoseLife() || player.cantLoseForZeroOrLessLife() || ComputerUtil.getDamageFromETB(player, land) < player.getLife()) + && (!game.getPhaseHandler().is(PhaseType.MAIN1) || !isSafeToHoldLandDropForMain2(land))) { + final List abilities = Lists.newArrayList(); - // TODO extend this logic to evaluate MDFC with both sides land - // this can only happen if its a MDFC land - if (!land.isLand()) { - land.setState(CardStateName.Modal, true); - land.setBackSide(true); - } + // TODO extend this logic to evaluate MDFC with both sides land + // this can only happen if its a MDFC land + if (!land.isLand()) { + land.setState(CardStateName.Modal, true); + land.setBackSide(true); + } - LandAbility la = new LandAbility(land, player, null); + LandAbility la = new LandAbility(land, player, null); + la.setCardState(land.getCurrentState()); + if (la.canPlay()) { + abilities.add(la); + } + + // add mayPlay option + for (CardPlayOption o : land.mayPlay(player)) { + la = new LandAbility(land, player, o.getAbility()); la.setCardState(land.getCurrentState()); if (la.canPlay()) { abilities.add(la); } - - // add mayPlay option - for (CardPlayOption o : land.mayPlay(player)) { - la = new LandAbility(land, player, o.getAbility()); - la.setCardState(land.getCurrentState()); - if (la.canPlay()) { - abilities.add(la); - } - } - if (!abilities.isEmpty()) { - return abilities; - } + } + if (!abilities.isEmpty()) { + return abilities; } } } diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java b/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java index 748cdb44d46..dc2a3632c1f 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java @@ -100,20 +100,19 @@ public abstract class DamageAiBase extends SpellAbilityAi { return false; } + if (!enemy.canLoseLife()) { + return false; + } + if (!noPrevention) { restDamage = ComputerUtilCombat.predictDamageTo(enemy, restDamage, hostcard, false); } else { restDamage = enemy.staticReplaceDamage(restDamage, hostcard, false); } - if (restDamage == 0) { return false; } - if (!enemy.canLoseLife()) { - return false; - } - final CardCollectionView hand = comp.getCardsIn(ZoneType.Hand); if ((enemy.getLife() - restDamage) < 5) { diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index 922af3691e2..2f873c7e08c 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -1922,7 +1922,7 @@ public class Player extends GameEntity implements Comparable { } public final boolean cantLoseForZeroOrLessLife() { - return hasKeyword("You don't lose the game for having 0 or less life."); + return cantLose() || hasKeyword("You don't lose the game for having 0 or less life."); } public final boolean cantWin() { diff --git a/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java b/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java index 0f4c4a7cc12..a9f6a82cf6c 100644 --- a/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java +++ b/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java @@ -585,7 +585,7 @@ public class TargetRestrictions { final Card srcCard = sa.getHostCard(); // should there be OrginalHost at any moment? for (final Card c : game.getCardsIn(this.tgtZone)) { - if (c.isValid(this.validTgts, srcCard.getController(), srcCard, sa) + if (c.isValid(this.validTgts, sa.getActivatingPlayer(), srcCard, sa) && (!isTargeted || sa.canTarget(c)) && !sa.getTargets().contains(c)) { candidates.add(c);