From fd2d425d23dad8cca5b6c098f78fd0f79a2f6e16 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Sun, 26 Feb 2023 18:29:45 +0100 Subject: [PATCH] Logic tweak --- .../main/java/forge/ai/ComputerUtilCard.java | 49 ++++++------- .../main/java/forge/ai/ability/CharmAi.java | 7 +- .../java/forge/ai/ability/CountersPutAi.java | 70 ++++++++++--------- .../game/spellability/AbilityStatic.java | 6 -- forge-gui/res/cardsfolder/s/subtle_strike.txt | 2 +- 5 files changed, 70 insertions(+), 64 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index 9725ba51afd..ac9f829abf6 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -1312,7 +1312,6 @@ public class ComputerUtilCard { final int power, final List keywords) { return shouldPumpCard(ai, sa, c, toughness, power, keywords, false); } - public static boolean shouldPumpCard(final Player ai, final SpellAbility sa, final Card c, final int toughness, final int power, final List keywords, boolean immediately) { final Game game = ai.getGame(); @@ -1546,35 +1545,37 @@ public class ComputerUtilCard { || ("PumpForTrample".equals(sa.getParam("AILogic")))) { return true; } - } - // try to determine if pumping a creature for more power will give lethal on board - // considering all unblocked creatures after the blockers are already declared - if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && pumpedDmg > dmg) { - int totalPowerUnblocked = 0; - for (Card atk : combat.getAttackers()) { - if (combat.isBlocked(atk) && !atk.hasKeyword(Keyword.TRAMPLE)) { - continue; - } - if (atk == c) { - totalPowerUnblocked += pumpedDmg; // this accounts for Trample by now - } else { - totalPowerUnblocked += ComputerUtilCombat.damageIfUnblocked(atk, opp, combat, true); - if (combat.isBlocked(atk)) { - // consider Trample damage properly for a blocked creature - for (Card blk : combat.getBlockers(atk)) { - totalPowerUnblocked -= ComputerUtilCombat.getDamageToKill(blk, false); + + // try to determine if pumping a creature for more power will give lethal on board + // considering all unblocked creatures after the blockers are already declared + if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { + int totalPowerUnblocked = 0; + for (Card atk : combat.getAttackers()) { + if (combat.isBlocked(atk) && !atk.hasKeyword(Keyword.TRAMPLE)) { + continue; + } + if (atk == c) { + totalPowerUnblocked += pumpedDmg; // this accounts for Trample by now + } else { + totalPowerUnblocked += ComputerUtilCombat.damageIfUnblocked(atk, opp, combat, true); + if (combat.isBlocked(atk)) { + // consider Trample damage properly for a blocked creature + for (Card blk : combat.getBlockers(atk)) { + totalPowerUnblocked -= ComputerUtilCombat.getDamageToKill(blk, false); + } } } } - } - if (totalPowerUnblocked >= opp.getLife()) { - return true; - } else if (totalPowerUnblocked > dmg && sa.getHostCard() != null && sa.getHostCard().isInPlay()) { - if (sa.getPayCosts().hasNoManaCost()) { - return true; // always activate abilities which cost no mana and which can increase unblocked damage + if (totalPowerUnblocked >= opp.getLife()) { + return true; + } else if (totalPowerUnblocked > dmg && sa.getHostCard() != null && sa.getHostCard().isInPlay()) { + if (sa.getPayCosts().hasNoManaCost()) { + return true; // always activate abilities which cost no mana and which can increase unblocked damage + } } } } + float value = 1.0f * (pumpedDmg - dmg); if (c == sa.getHostCard() && power > 0) { int divisor = sa.getPayCosts().getTotalMana().getCMC(); diff --git a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java index 5d9c7167484..b1e981d9d6a 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java @@ -14,6 +14,7 @@ import forge.ai.SpellAbilityAi; import forge.game.ability.AbilityUtils; import forge.game.ability.effects.CharmEffect; import forge.game.card.Card; +import forge.game.keyword.Keyword; import forge.game.player.Player; import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; @@ -26,7 +27,6 @@ public class CharmAi extends SpellAbilityAi { protected boolean checkApiLogic(Player ai, SpellAbility sa) { final Card source = sa.getHostCard(); List choices = CharmEffect.makePossibleOptions(sa); - Collections.shuffle(choices); final int num; final int min; @@ -37,6 +37,11 @@ public class CharmAi extends SpellAbilityAi { min = sa.hasParam("MinCharmNum") ? AbilityUtils.calculateAmount(source, sa.getParam("MinCharmNum"), sa) : num; } + // only randomize if not all possible together + if (num < choices.size() || source.hasKeyword(Keyword.ESCALATE)) { + Collections.shuffle(choices); + } + boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now? // Reset the chosen list otherwise it will be locked in forever by earlier calls diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java index 661b70a7828..cfc66522a27 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java @@ -45,6 +45,7 @@ import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; import forge.game.player.PlayerCollection; import forge.game.player.PlayerPredicates; +import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; import forge.game.spellability.TargetRestrictions; import forge.game.trigger.Trigger; @@ -456,25 +457,24 @@ public class CountersPutAi extends CountersAi { } } - if (!ai.getGame().getStack().isEmpty() && !SpellAbilityAi.isSorcerySpeed(sa, ai)) { - // only evaluates case where all tokens are placed on a single target - if (sa.usesTargeting() && sa.getMinTargets() < 2) { - if (ComputerUtilCard.canPumpAgainstRemoval(ai, sa)) { - Card c = sa.getTargetCard(); - if (sa.getTargets().size() > 1) { - sa.resetTargets(); - sa.getTargets().add(c); + if (sa.usesTargeting()) { + if (!ai.getGame().getStack().isEmpty() && !SpellAbilityAi.isSorcerySpeed(sa, ai)) { + // only evaluates case where all tokens are placed on a single target + if (sa.getMinTargets() < 2) { + if (ComputerUtilCard.canPumpAgainstRemoval(ai, sa)) { + Card c = sa.getTargetCard(); + if (sa.getTargets().size() > 1) { + sa.resetTargets(); + sa.getTargets().add(c); + } + sa.addDividedAllocation(c, amount); + return true; + } else { + return false; } - sa.addDividedAllocation(c, amount); - return true; - } else { - return false; } } - } - // Targeting - if (sa.usesTargeting()) { sa.resetTargets(); final boolean sacSelf = ComputerUtilCost.isSacrificeSelfCost(abCost); @@ -482,7 +482,7 @@ public class CountersPutAi extends CountersAi { if (sa.isCurse()) { list = ai.getOpponents().getCardsIn(ZoneType.Battlefield); } else { - list = new CardCollection(ai.getCardsIn(ZoneType.Battlefield)); + list = ComputerUtil.getSafeTargets(ai, sa, ai.getCardsIn(ZoneType.Battlefield)); } list = CardLists.filter(list, new Predicate() { @@ -530,8 +530,7 @@ public class CountersPutAi extends CountersAi { for (int i = 1; i < amount + 1; i++) { int left = amount; for (Card c : list) { - if (ComputerUtilCard.shouldPumpCard(ai, sa, c, i, i, - Lists.newArrayList())) { + if (ComputerUtilCard.shouldPumpCard(ai, sa, c, i, i, Lists.newArrayList())) { sa.getTargets().add(c); sa.addDividedAllocation(c, i); left -= i; @@ -553,7 +552,7 @@ public class CountersPutAi extends CountersAi { // target loop while (sa.canAddMoreTarget()) { if (list.isEmpty()) { - if (!sa.isTargetNumberValid() || (sa.getTargets().size() == 0)) { + if (!sa.isTargetNumberValid() || sa.getTargets().isEmpty()) { sa.resetTargets(); return false; } else { @@ -567,34 +566,42 @@ public class CountersPutAi extends CountersAi { } else { if (type.equals("P1P1") && !SpellAbilityAi.isSorcerySpeed(sa, ai)) { for (Card c : list) { - if (ComputerUtilCard.shouldPumpCard(ai, sa, c, amount, amount, - Lists.newArrayList())) { - choice = c; - break; - } else if (!sa.getRestrictions().isSorcerySpeed() && !ComputerUtilCard.isUselessCreature(ai, c)) { + if (ComputerUtilCard.shouldPumpCard(ai, sa, c, amount, amount, Lists.newArrayList())) { choice = c; break; } } - if (!source.isSpell()) { // does not cost a card - if (choice == null) { // find generic target - if (abCost == null + + if (choice == null) { + // try to use as cheap kill + choice = ComputerUtil.getKilledByTargeting(sa, CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa)); + } + + if (choice == null) { + // find generic target + boolean increasesCharmOutcome = false; + if (sa.getRootAbility().getApi() == ApiType.Charm && source.getStaticAbilities().isEmpty()) { + List choices = Lists.newArrayList(sa.getRootAbility().getAdditionalAbilityList("Choices")); + choices.remove(sa); + // check if other choice will already be played + increasesCharmOutcome = !choices.get(0).getTargets().isEmpty(); + } + if (!source.isSpell() || increasesCharmOutcome // does not cost a card or can buff charm for no expense + || ph.getTurn() - source.getTurnInZone() >= source.getGame().getPlayers().size() * 2) { + if (abCost == null || abCost == Cost.Zero || (ph.is(PhaseType.END_OF_TURN) && ph.getPlayerTurn().isOpponentOf(ai))) { // only use at opponent EOT unless it is free choice = chooseBoonTarget(list, type); } } } - if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Dromoka's Command")) { - choice = chooseBoonTarget(list, type); - } } else { choice = chooseBoonTarget(list, type); } } if (choice == null) { // can't find anything left - if (!sa.isTargetNumberValid() || sa.getTargets().size() == 0) { + if (!sa.isTargetNumberValid() || sa.getTargets().isEmpty()) { sa.resetTargets(); return false; } else { @@ -910,7 +917,6 @@ public class CountersPutAi extends CountersAi { // Didn't want to choose anything? list.clear(); } - } } return true; diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityStatic.java b/forge-game/src/main/java/forge/game/spellability/AbilityStatic.java index 726c45ec097..330b4e5ff0d 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityStatic.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityStatic.java @@ -20,7 +20,6 @@ package forge.game.spellability; import forge.card.mana.ManaCost; import forge.game.card.Card; import forge.game.cost.Cost; -import forge.game.player.Player; /** *

@@ -51,11 +50,6 @@ public abstract class AbilityStatic extends Ability implements Cloneable { } @Override public boolean canPlay() { - Player player = getActivatingPlayer(); - if (player == null) { - player = this.getHostCard().getController(); - } - final Card c = this.getHostCard(); return this.getRestrictions().canPlay(c, this); diff --git a/forge-gui/res/cardsfolder/s/subtle_strike.txt b/forge-gui/res/cardsfolder/s/subtle_strike.txt index 1cbf89afd50..20f3502730b 100644 --- a/forge-gui/res/cardsfolder/s/subtle_strike.txt +++ b/forge-gui/res/cardsfolder/s/subtle_strike.txt @@ -3,5 +3,5 @@ ManaCost:1 B Types:Instant A:SP$ Charm | Cost$ 1 B | MinCharmNum$ 1 | CharmNum$ 2 | Choices$ DBPump,DBPutCounter SVar:DBPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature (-1/-1) | NumAtt$ -1 | NumDef$ -1 | IsCurse$ True | SpellDescription$ Target creature gets -1/-1 until end of turn. -SVar:DBPutCounter:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature (+1/+1 counter) | AILogic$ Good | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a +1/+1 counter on target creature. +SVar:DBPutCounter:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature (+1/+1 counter) | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a +1/+1 counter on target creature. Oracle:Choose one or both —\n• Target creature gets -1/-1 until end of turn.\n• Put a +1/+1 counter on target creature.