Logic tweak

This commit is contained in:
tool4EvEr
2023-02-26 18:29:45 +01:00
parent 459416630b
commit fd2d425d23
5 changed files with 70 additions and 64 deletions

View File

@@ -1312,7 +1312,6 @@ public class ComputerUtilCard {
final int power, final List<String> keywords) { final int power, final List<String> keywords) {
return shouldPumpCard(ai, sa, c, toughness, power, keywords, false); 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, public static boolean shouldPumpCard(final Player ai, final SpellAbility sa, final Card c, final int toughness,
final int power, final List<String> keywords, boolean immediately) { final int power, final List<String> keywords, boolean immediately) {
final Game game = ai.getGame(); final Game game = ai.getGame();
@@ -1546,35 +1545,37 @@ public class ComputerUtilCard {
|| ("PumpForTrample".equals(sa.getParam("AILogic")))) { || ("PumpForTrample".equals(sa.getParam("AILogic")))) {
return true; return true;
} }
}
// try to determine if pumping a creature for more power will give lethal on board // 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 // considering all unblocked creatures after the blockers are already declared
if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && pumpedDmg > dmg) { if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
int totalPowerUnblocked = 0; int totalPowerUnblocked = 0;
for (Card atk : combat.getAttackers()) { for (Card atk : combat.getAttackers()) {
if (combat.isBlocked(atk) && !atk.hasKeyword(Keyword.TRAMPLE)) { if (combat.isBlocked(atk) && !atk.hasKeyword(Keyword.TRAMPLE)) {
continue; continue;
} }
if (atk == c) { if (atk == c) {
totalPowerUnblocked += pumpedDmg; // this accounts for Trample by now totalPowerUnblocked += pumpedDmg; // this accounts for Trample by now
} else { } else {
totalPowerUnblocked += ComputerUtilCombat.damageIfUnblocked(atk, opp, combat, true); totalPowerUnblocked += ComputerUtilCombat.damageIfUnblocked(atk, opp, combat, true);
if (combat.isBlocked(atk)) { if (combat.isBlocked(atk)) {
// consider Trample damage properly for a blocked creature // consider Trample damage properly for a blocked creature
for (Card blk : combat.getBlockers(atk)) { for (Card blk : combat.getBlockers(atk)) {
totalPowerUnblocked -= ComputerUtilCombat.getDamageToKill(blk, false); totalPowerUnblocked -= ComputerUtilCombat.getDamageToKill(blk, false);
}
} }
} }
} }
} if (totalPowerUnblocked >= opp.getLife()) {
if (totalPowerUnblocked >= opp.getLife()) { return true;
return true; } else if (totalPowerUnblocked > dmg && sa.getHostCard() != null && sa.getHostCard().isInPlay()) {
} else if (totalPowerUnblocked > dmg && sa.getHostCard() != null && sa.getHostCard().isInPlay()) { if (sa.getPayCosts().hasNoManaCost()) {
if (sa.getPayCosts().hasNoManaCost()) { return true; // always activate abilities which cost no mana and which can increase unblocked damage
return true; // always activate abilities which cost no mana and which can increase unblocked damage }
} }
} }
} }
float value = 1.0f * (pumpedDmg - dmg); float value = 1.0f * (pumpedDmg - dmg);
if (c == sa.getHostCard() && power > 0) { if (c == sa.getHostCard() && power > 0) {
int divisor = sa.getPayCosts().getTotalMana().getCMC(); int divisor = sa.getPayCosts().getTotalMana().getCMC();

View File

@@ -14,6 +14,7 @@ import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.effects.CharmEffect; import forge.game.ability.effects.CharmEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.keyword.Keyword;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.AbilitySub; import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -26,7 +27,6 @@ public class CharmAi extends SpellAbilityAi {
protected boolean checkApiLogic(Player ai, SpellAbility sa) { protected boolean checkApiLogic(Player ai, SpellAbility sa) {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa); List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
Collections.shuffle(choices);
final int num; final int num;
final int min; final int min;
@@ -37,6 +37,11 @@ public class CharmAi extends SpellAbilityAi {
min = sa.hasParam("MinCharmNum") ? AbilityUtils.calculateAmount(source, sa.getParam("MinCharmNum"), sa) : num; 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? 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 // Reset the chosen list otherwise it will be locked in forever by earlier calls

View File

@@ -45,6 +45,7 @@ import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode; import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerCollection; import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates; import forge.game.player.PlayerPredicates;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
@@ -456,25 +457,24 @@ public class CountersPutAi extends CountersAi {
} }
} }
if (!ai.getGame().getStack().isEmpty() && !SpellAbilityAi.isSorcerySpeed(sa, ai)) { if (sa.usesTargeting()) {
// only evaluates case where all tokens are placed on a single target if (!ai.getGame().getStack().isEmpty() && !SpellAbilityAi.isSorcerySpeed(sa, ai)) {
if (sa.usesTargeting() && sa.getMinTargets() < 2) { // only evaluates case where all tokens are placed on a single target
if (ComputerUtilCard.canPumpAgainstRemoval(ai, sa)) { if (sa.getMinTargets() < 2) {
Card c = sa.getTargetCard(); if (ComputerUtilCard.canPumpAgainstRemoval(ai, sa)) {
if (sa.getTargets().size() > 1) { Card c = sa.getTargetCard();
sa.resetTargets(); if (sa.getTargets().size() > 1) {
sa.getTargets().add(c); 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(); sa.resetTargets();
final boolean sacSelf = ComputerUtilCost.isSacrificeSelfCost(abCost); final boolean sacSelf = ComputerUtilCost.isSacrificeSelfCost(abCost);
@@ -482,7 +482,7 @@ public class CountersPutAi extends CountersAi {
if (sa.isCurse()) { if (sa.isCurse()) {
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield); list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
} else { } else {
list = new CardCollection(ai.getCardsIn(ZoneType.Battlefield)); list = ComputerUtil.getSafeTargets(ai, sa, ai.getCardsIn(ZoneType.Battlefield));
} }
list = CardLists.filter(list, new Predicate<Card>() { list = CardLists.filter(list, new Predicate<Card>() {
@@ -530,8 +530,7 @@ public class CountersPutAi extends CountersAi {
for (int i = 1; i < amount + 1; i++) { for (int i = 1; i < amount + 1; i++) {
int left = amount; int left = amount;
for (Card c : list) { for (Card c : list) {
if (ComputerUtilCard.shouldPumpCard(ai, sa, c, i, i, if (ComputerUtilCard.shouldPumpCard(ai, sa, c, i, i, Lists.newArrayList())) {
Lists.newArrayList())) {
sa.getTargets().add(c); sa.getTargets().add(c);
sa.addDividedAllocation(c, i); sa.addDividedAllocation(c, i);
left -= i; left -= i;
@@ -553,7 +552,7 @@ public class CountersPutAi extends CountersAi {
// target loop // target loop
while (sa.canAddMoreTarget()) { while (sa.canAddMoreTarget()) {
if (list.isEmpty()) { if (list.isEmpty()) {
if (!sa.isTargetNumberValid() || (sa.getTargets().size() == 0)) { if (!sa.isTargetNumberValid() || sa.getTargets().isEmpty()) {
sa.resetTargets(); sa.resetTargets();
return false; return false;
} else { } else {
@@ -567,34 +566,42 @@ public class CountersPutAi extends CountersAi {
} else { } else {
if (type.equals("P1P1") && !SpellAbilityAi.isSorcerySpeed(sa, ai)) { if (type.equals("P1P1") && !SpellAbilityAi.isSorcerySpeed(sa, ai)) {
for (Card c : list) { for (Card c : list) {
if (ComputerUtilCard.shouldPumpCard(ai, sa, c, amount, amount, if (ComputerUtilCard.shouldPumpCard(ai, sa, c, amount, amount, Lists.newArrayList())) {
Lists.newArrayList())) {
choice = c;
break;
} else if (!sa.getRestrictions().isSorcerySpeed() && !ComputerUtilCard.isUselessCreature(ai, c)) {
choice = c; choice = c;
break; break;
} }
} }
if (!source.isSpell()) { // does not cost a card
if (choice == null) { // find generic target if (choice == null) {
if (abCost == 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<AbilitySub> 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))) { || (ph.is(PhaseType.END_OF_TURN) && ph.getPlayerTurn().isOpponentOf(ai))) {
// only use at opponent EOT unless it is free // only use at opponent EOT unless it is free
choice = chooseBoonTarget(list, type); choice = chooseBoonTarget(list, type);
} }
} }
} }
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Dromoka's Command")) {
choice = chooseBoonTarget(list, type);
}
} else { } else {
choice = chooseBoonTarget(list, type); choice = chooseBoonTarget(list, type);
} }
} }
if (choice == null) { // can't find anything left if (choice == null) { // can't find anything left
if (!sa.isTargetNumberValid() || sa.getTargets().size() == 0) { if (!sa.isTargetNumberValid() || sa.getTargets().isEmpty()) {
sa.resetTargets(); sa.resetTargets();
return false; return false;
} else { } else {
@@ -910,7 +917,6 @@ public class CountersPutAi extends CountersAi {
// Didn't want to choose anything? // Didn't want to choose anything?
list.clear(); list.clear();
} }
} }
} }
return true; return true;

View File

@@ -20,7 +20,6 @@ package forge.game.spellability;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.player.Player;
/** /**
* <p> * <p>
@@ -51,11 +50,6 @@ public abstract class AbilityStatic extends Ability implements Cloneable {
} }
@Override @Override
public boolean canPlay() { public boolean canPlay() {
Player player = getActivatingPlayer();
if (player == null) {
player = this.getHostCard().getController();
}
final Card c = this.getHostCard(); final Card c = this.getHostCard();
return this.getRestrictions().canPlay(c, this); return this.getRestrictions().canPlay(c, this);

View File

@@ -3,5 +3,5 @@ ManaCost:1 B
Types:Instant Types:Instant
A:SP$ Charm | Cost$ 1 B | MinCharmNum$ 1 | CharmNum$ 2 | Choices$ DBPump,DBPutCounter 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: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. Oracle:Choose one or both —\n• Target creature gets -1/-1 until end of turn.\n• Put a +1/+1 counter on target creature.