mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 20:28:00 +00:00
AI: Various logic fixes and improvements (#3416)
* - Fix logic for Elderscale Wurm damage prediction * - AI logic hint for Tempting Wurm - Several related AI hint fixes (cards in hand don't have a controller set) * - Implement AI logic for Grothama, All-Devouring. * - AI shouldn't fight its own Grothama for card draw (leads to silly results). * - Attempt two at fixing Elderscale Wurm damage preditions. * - Improve banding AI so that the AI knows how to tank damage from Trample when the other possibility is multiple chump blockers. * - Clean up * - Attempt to defer checking for "...as if it were unblocked" static until after the Banding blocker(s) are assigned. - Clean up * - Clean up
This commit is contained in:
@@ -17,11 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package forge.ai;
|
package forge.ai;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
@@ -36,6 +32,7 @@ import forge.game.card.CardCollectionView;
|
|||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
|
import forge.game.combat.AttackingBand;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
@@ -770,21 +767,52 @@ public class AiBlockController {
|
|||||||
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(changesPTWhenBlocked(true)));
|
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(changesPTWhenBlocked(true)));
|
||||||
|
|
||||||
for (final Card attacker : tramplingAttackers) {
|
for (final Card attacker : tramplingAttackers) {
|
||||||
|
boolean staticAssignCombatDamageAsUnblocked = StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker);
|
||||||
|
|
||||||
if (CombatUtil.getMinNumBlockersForAttacker(attacker, combat.getDefenderPlayerByAttacker(attacker)) > combat.getBlockers(attacker).size()
|
if (CombatUtil.getMinNumBlockersForAttacker(attacker, combat.getDefenderPlayerByAttacker(attacker)) > combat.getBlockers(attacker).size()
|
||||||
|| StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)
|
|
||||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean needsMoreChumpBlockers = true;
|
||||||
|
|
||||||
|
// See if it's possible to tank up the damage with Banding
|
||||||
|
List<String> bandsWithString = Arrays.asList("Bands with Other Legendary Creatures",
|
||||||
|
"Bands with Other Creatures named Wolves of the Hunt",
|
||||||
|
"Bands with Other Dinosaurs");
|
||||||
|
if (AttackingBand.isValidBand(combat.getBlockers(attacker), true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
chumpBlockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
|
chumpBlockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
|
||||||
chumpBlockers.removeAll(combat.getBlockers(attacker));
|
chumpBlockers.removeAll(combat.getBlockers(attacker));
|
||||||
|
|
||||||
|
// See if there's a Banding blocker that can tank the damage
|
||||||
for (final Card blocker : chumpBlockers) {
|
for (final Card blocker : chumpBlockers) {
|
||||||
// Add an additional blocker if the current blockers are not
|
if (blocker.hasKeyword(Keyword.BANDING) || blocker.hasAnyKeyword(bandsWithString)) {
|
||||||
// enough and the new one would suck some of the damage
|
if (ComputerUtilCombat.getAttack(attacker) > ComputerUtilCombat.totalShieldDamage(attacker, combat.getBlockers(attacker))
|
||||||
if (ComputerUtilCombat.getAttack(attacker) > ComputerUtilCombat.totalShieldDamage(attacker, combat.getBlockers(attacker))
|
&& ComputerUtilCombat.shieldDamage(attacker, blocker) > 0
|
||||||
&& ComputerUtilCombat.shieldDamage(attacker, blocker) > 0
|
&& CombatUtil.canBlock(attacker, blocker, combat) && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
&& CombatUtil.canBlock(attacker, blocker, combat) && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
combat.addBlocker(attacker, blocker);
|
||||||
combat.addBlocker(attacker, blocker);
|
needsMoreChumpBlockers = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (staticAssignCombatDamageAsUnblocked) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsMoreChumpBlockers) {
|
||||||
|
for (final Card blocker : chumpBlockers) {
|
||||||
|
// Add an additional blocker if the current blockers are not
|
||||||
|
// enough and the new one would suck some of the damage
|
||||||
|
if (ComputerUtilCombat.getAttack(attacker) > ComputerUtilCombat.totalShieldDamage(attacker, combat.getBlockers(attacker))
|
||||||
|
&& ComputerUtilCombat.shieldDamage(attacker, blocker) > 0
|
||||||
|
&& CombatUtil.canBlock(attacker, blocker, combat) && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
|
combat.addBlocker(attacker, blocker);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -651,6 +651,21 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Grothama, All-Devouring
|
||||||
|
public static class GrothamaAllDevouring {
|
||||||
|
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
final Card devourer = AbilityUtils.getDefinedCards(source, sa.getParam("ExtraDefined"), sa).getFirst(); // maybe just getOriginalHost()?
|
||||||
|
if (ai.getTeamMates(true).contains(devourer.getController())) {
|
||||||
|
return false; // TODO: Currently, the AI doesn't ever fight its own (or allied) Grothama for card draw. This can be improved.
|
||||||
|
}
|
||||||
|
final Card fighter = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).getFirst();
|
||||||
|
boolean goodTradeOrNoTrade = devourer.canBeDestroyed() && (devourer.getNetPower() < fighter.getNetToughness() || !fighter.canBeDestroyed()
|
||||||
|
|| ComputerUtilCard.evaluateCreature(devourer) > ComputerUtilCard.evaluateCreature(fighter));
|
||||||
|
return goodTradeOrNoTrade && fighter.getNetPower() >= devourer.getNetToughness();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Guilty Conscience
|
// Guilty Conscience
|
||||||
public static class GuiltyConscience {
|
public static class GuiltyConscience {
|
||||||
public static Card getBestAttachTarget(final Player ai, final SpellAbility sa, final List<Card> list) {
|
public static Card getBestAttachTarget(final Player ai, final SpellAbility sa, final List<Card> list) {
|
||||||
|
|||||||
@@ -2,11 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtilAbility;
|
|
||||||
import forge.ai.ComputerUtilCard;
|
|
||||||
import forge.ai.ComputerUtilCombat;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -32,9 +28,13 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
// everything is defined or targeted above, can't do anything there?
|
// everything is defined or targeted above, can't do anything there unless a specific logic is set
|
||||||
if (sa.hasParam("Defined") && !sa.usesTargeting()) {
|
if (sa.hasParam("Defined") && !sa.usesTargeting()) {
|
||||||
// TODO extend Logic for cards like Arena or Grothama
|
// TODO extend Logic for cards like Arena
|
||||||
|
if ("Grothama".equals(sa.getParam("AILogic"))) { // Grothama, All-Devouring
|
||||||
|
return SpecialCardAi.GrothamaAllDevouring.consider(ai, sa);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
if (canPlayAI(ai, sa)) {
|
if (checkApiLogic(ai, sa)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
|
|||||||
@@ -799,7 +799,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
|||||||
restDamage = 2;
|
restDamage = 2;
|
||||||
}
|
}
|
||||||
} else if (c.getName().equals("Elderscale Wurm")) {
|
} else if (c.getName().equals("Elderscale Wurm")) {
|
||||||
if (c.getController().equals(this) && getLife() - restDamage < 7) {
|
if (c.getController().equals(this) && getLife() >= 7 && getLife() - restDamage < 7) {
|
||||||
restDamage = getLife() - 7;
|
restDamage = getLife() - 7;
|
||||||
if (restDamage < 0) {
|
if (restDamage < 0) {
|
||||||
restDamage = 0;
|
restDamage = 0;
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ K:Kicker:Sac<2/Land>
|
|||||||
A:SP$ Discard | Cost$ 2 B | ValidTgts$ Player | TgtPrompt$ Choose a player | NumCards$ WasKicked | Mode$ TgtChoose | SpellDescription$ Target player discards two cards. If CARDNAME was kicked, that player discards three cards instead.
|
A:SP$ Discard | Cost$ 2 B | ValidTgts$ Player | TgtPrompt$ Choose a player | NumCards$ WasKicked | Mode$ TgtChoose | SpellDescription$ Target player discards two cards. If CARDNAME was kicked, that player discards three cards instead.
|
||||||
SVar:WasKicked:Count$Kicked.3.2
|
SVar:WasKicked:Count$Kicked.3.2
|
||||||
SVar:NeedsToPlayKickedVar:Z GE3
|
SVar:NeedsToPlayKickedVar:Z GE3
|
||||||
SVar:Z:Count$ValidHand Card.OppCtrl
|
SVar:Z:Count$ValidHand Card.OppOwn
|
||||||
SVar:AIPreference:SacCost$Land.basic+YouCtrl
|
SVar:AIPreference:SacCost$Land.basic+YouCtrl
|
||||||
Oracle:Kicker—Sacrifice two lands. (You may sacrifice two lands in addition to any other costs as you cast this spell.)\nTarget player discards two cards. If this spell was kicked, that player discards three cards instead.
|
Oracle:Kicker—Sacrifice two lands. (You may sacrifice two lands in addition to any other costs as you cast this spell.)\nTarget player discards two cards. If this spell was kicked, that player discards three cards instead.
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ K:Kicker:3 B
|
|||||||
T:Mode$ ChangesZone | ValidCard$ Card.Self+kicked | Origin$ Any | Destination$ Battlefield | Execute$ TrigDiscard | TriggerDescription$ When CARDNAME enters the battlefield, if it was kicked, each opponent discards two cards.
|
T:Mode$ ChangesZone | ValidCard$ Card.Self+kicked | Origin$ Any | Destination$ Battlefield | Execute$ TrigDiscard | TriggerDescription$ When CARDNAME enters the battlefield, if it was kicked, each opponent discards two cards.
|
||||||
SVar:TrigDiscard:DB$ Discard | Defined$ Player.Opponent | NumCards$ 2 | Mode$ TgtChoose
|
SVar:TrigDiscard:DB$ Discard | Defined$ Player.Opponent | NumCards$ 2 | Mode$ TgtChoose
|
||||||
SVar:NeedsToPlayKickedVar:Z GE1
|
SVar:NeedsToPlayKickedVar:Z GE1
|
||||||
SVar:Z:Count$ValidHand Card.OppCtrl
|
SVar:Z:Count$ValidHand Card.OppOwn
|
||||||
Oracle:Kicker {3}{B} (You may pay an additional {3}{B} as you cast this spell.)\nWhen Caligo Skin-Witch enters the battlefield, if it was kicked, each opponent discards two cards.
|
Oracle:Kicker {3}{B} (You may pay an additional {3}{B} as you cast this spell.)\nWhen Caligo Skin-Witch enters the battlefield, if it was kicked, each opponent discards two cards.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Types:Legendary Creature Wurm
|
|||||||
PT:10/8
|
PT:10/8
|
||||||
S:Mode$ Continuous | Affected$ Creature.Other | AddTrigger$ GrothamaAttack | AddSVar$ HasAttackEffect | Description$ Other creatures have "Whenever this creature attacks, you may have it fight CARDNAME."
|
S:Mode$ Continuous | Affected$ Creature.Other | AddTrigger$ GrothamaAttack | AddSVar$ HasAttackEffect | Description$ Other creatures have "Whenever this creature attacks, you may have it fight CARDNAME."
|
||||||
SVar:GrothamaAttack:Mode$ Attacks | ValidCard$ Card.Self | Execute$ GrothamaFight | OptionalDecider$ You | TriggerDescription$ Whenever this creature attacks, ABILITY.
|
SVar:GrothamaAttack:Mode$ Attacks | ValidCard$ Card.Self | Execute$ GrothamaFight | OptionalDecider$ You | TriggerDescription$ Whenever this creature attacks, ABILITY.
|
||||||
SVar:GrothamaFight:DB$ Fight | Defined$ TriggeredAttackerLKICopy | ExtraDefined$ OriginalHost | SpellDescription$ You may have it fight ORIGINALHOST
|
SVar:GrothamaFight:DB$ Fight | Defined$ TriggeredAttackerLKICopy | ExtraDefined$ OriginalHost | AILogic$ Grothama | SpellDescription$ You may have it fight ORIGINALHOST
|
||||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigRepeat | TriggerDescription$ When NICKNAME leaves the battlefield, each player draws cards equal to the amount of damage dealt to NICKNAME this turn by sources they controlled.
|
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigRepeat | TriggerDescription$ When NICKNAME leaves the battlefield, each player draws cards equal to the amount of damage dealt to NICKNAME this turn by sources they controlled.
|
||||||
SVar:TrigRepeat:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ TrigDraw
|
SVar:TrigRepeat:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ TrigDraw
|
||||||
SVar:TrigDraw:DB$ Draw | Defined$ Remembered | NumCards$ X
|
SVar:TrigDraw:DB$ Draw | Defined$ Remembered | NumCards$ X
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ K:Kicker:4
|
|||||||
A:SP$ Discard | Cost$ 1 B | NumCards$ X | ValidTgts$ Player | TgtPrompt$ Select target player | Mode$ TgtChoose | SpellDescription$ Target player discards a card. If this spell was kicked, that player discards three cards instead.
|
A:SP$ Discard | Cost$ 1 B | NumCards$ X | ValidTgts$ Player | TgtPrompt$ Select target player | Mode$ TgtChoose | SpellDescription$ Target player discards a card. If this spell was kicked, that player discards three cards instead.
|
||||||
SVar:X:Count$Kicked.3.1
|
SVar:X:Count$Kicked.3.1
|
||||||
SVar:NeedsToPlayKickedVar:Z GE2
|
SVar:NeedsToPlayKickedVar:Z GE2
|
||||||
SVar:Z:Count$ValidHand Card.OppCtrl
|
SVar:Z:Count$ValidHand Card.OppOwn
|
||||||
Oracle:Kicker {4} (You may pay an additional {4} as you cast this spell.)\nTarget player discards a card. If this spell was kicked, that player discards three cards instead.
|
Oracle:Kicker {4} (You may pay an additional {4} as you cast this spell.)\nTarget player discards a card. If this spell was kicked, that player discards three cards instead.
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
|||||||
SVar:X:Remembered$Amount/Plus.1
|
SVar:X:Remembered$Amount/Plus.1
|
||||||
SVar:Y:Count$ValidHand Card.RememberedPlayerCtrl
|
SVar:Y:Count$ValidHand Card.RememberedPlayerCtrl
|
||||||
SVar:NeedsToPlayVar:Z GE4
|
SVar:NeedsToPlayVar:Z GE4
|
||||||
SVar:Z:Count$ValidHand Card.OppCtrl
|
SVar:Z:Count$ValidHand Card.OppOwn
|
||||||
Oracle:For each player, choose friend or foe. Each friend discards all cards from their hand, then draws that many cards plus one. Khorvath's Fury deals damage to each foe equal to the number of cards in their hand.
|
Oracle:For each player, choose friend or foe. Each friend discards all cards from their hand, then draws that many cards plus one. Khorvath's Fury deals damage to each foe equal to the number of cards in their hand.
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ SVar:DBDiscardYou:DB$ Discard | Defined$ You | NumCards$ 2 | SubAbility$ DBDisca
|
|||||||
SVar:DBDiscardTarget:DB$ Discard | Condition$ Kicked | ValidTgts$ Player | TgtPrompt$ Select target player | NumCards$ 2 | Mode$ TgtChoose | SpellDescription$ If CARDNAME was kicked, target player discards two cards.
|
SVar:DBDiscardTarget:DB$ Discard | Condition$ Kicked | ValidTgts$ Player | TgtPrompt$ Select target player | NumCards$ 2 | Mode$ TgtChoose | SpellDescription$ If CARDNAME was kicked, target player discards two cards.
|
||||||
DeckHints:Color$Black
|
DeckHints:Color$Black
|
||||||
SVar:NeedsToPlayKickedVar:Z GE1
|
SVar:NeedsToPlayKickedVar:Z GE1
|
||||||
SVar:Z:Count$ValidHand Card.OppCtrl
|
SVar:Z:Count$ValidHand Card.OppOwn
|
||||||
Oracle:Kicker {1}{B} (You may pay an additional {1}{B} as you cast this spell.)\nDraw three cards, then discard two cards. If this spell was kicked, target player discards two cards.
|
Oracle:Kicker {1}{B} (You may pay an additional {1}{B} as you cast this spell.)\nDraw three cards, then discard two cards. If this spell was kicked, target player discards two cards.
|
||||||
|
|||||||
@@ -6,4 +6,6 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S
|
|||||||
SVar:EachOpponent:DB$ RepeatEach | RepeatPlayers$ Player.Opponent | RepeatSubAbility$ TemptingChange
|
SVar:EachOpponent:DB$ RepeatEach | RepeatPlayers$ Player.Opponent | RepeatSubAbility$ TemptingChange
|
||||||
SVar:TemptingChange:DB$ ChangeZone | Origin$ Hand | Destination$ Battlefield | ChangeType$ Artifact,Creature,Enchantment,Land | DefinedPlayer$ Remembered | ChangeNum$ X
|
SVar:TemptingChange:DB$ ChangeZone | Origin$ Hand | Destination$ Battlefield | ChangeType$ Artifact,Creature,Enchantment,Land | DefinedPlayer$ Remembered | ChangeNum$ X
|
||||||
SVar:X:Count$ValidHand Artifact.RememberedPlayerCtrl,Creature.RememberedPlayerCtrl,Enchantment.RememberedPlayerCtrl,Land.RememberedPlayerCtrl
|
SVar:X:Count$ValidHand Artifact.RememberedPlayerCtrl,Creature.RememberedPlayerCtrl,Enchantment.RememberedPlayerCtrl,Land.RememberedPlayerCtrl
|
||||||
|
SVar:NeedsToPlayVar:Y LE2
|
||||||
|
SVar:Y:Count$ValidHand Card.OppOwn
|
||||||
Oracle:When Tempting Wurm enters the battlefield, each opponent may put any number of artifact, creature, enchantment, and/or land cards from their hand onto the battlefield.
|
Oracle:When Tempting Wurm enters the battlefield, each opponent may put any number of artifact, creature, enchantment, and/or land cards from their hand onto the battlefield.
|
||||||
|
|||||||
Reference in New Issue
Block a user