mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 04:38:00 +00:00
Unblockable fixes (#1662)
* Add shortcuts * Fix cards * Clean up * Fix CantBlockBy checks * Fix stack overflow Co-authored-by: tool4EvEr <tool4EvEr@192.168.0.59>
This commit is contained in:
@@ -359,7 +359,7 @@ public class AiAttackController {
|
||||
}
|
||||
});
|
||||
|
||||
final List<Card> notNeededAsBlockers = new CardCollection(attackers);
|
||||
final CardCollection notNeededAsBlockers = new CardCollection(attackers);
|
||||
|
||||
// don't hold back creatures that can't block any of the human creatures
|
||||
final List<Card> blockers = getPossibleBlockers(attackers, opponentsAttackers, true);
|
||||
@@ -378,7 +378,7 @@ public class AiAttackController {
|
||||
int thresholdMod = 0;
|
||||
int lastAcceptableBaselineLife = 0;
|
||||
if (pilotsNonAggroDeck) {
|
||||
lastAcceptableBaselineLife = ComputerUtil.predictNextCombatsRemainingLife(ai, playAggro, pilotsNonAggroDeck, 0, new CardCollection(notNeededAsBlockers));
|
||||
lastAcceptableBaselineLife = ComputerUtil.predictNextCombatsRemainingLife(ai, playAggro, pilotsNonAggroDeck, 0, notNeededAsBlockers);
|
||||
if (!ai.isCardInPlay("Laboratory Maniac")) {
|
||||
// AI is getting milled out
|
||||
thresholdMod += 3 - Math.min(ai.getCardsIn(ZoneType.Library).size(), 3);
|
||||
@@ -397,7 +397,7 @@ public class AiAttackController {
|
||||
continue;
|
||||
}
|
||||
notNeededAsBlockers.add(c);
|
||||
int currentBaselineLife = ComputerUtil.predictNextCombatsRemainingLife(ai, playAggro, pilotsNonAggroDeck, 0, new CardCollection(notNeededAsBlockers));
|
||||
int currentBaselineLife = ComputerUtil.predictNextCombatsRemainingLife(ai, playAggro, pilotsNonAggroDeck, 0, notNeededAsBlockers);
|
||||
// AI doesn't know from what it will lose, so it might still keep an unnecessary blocker back sometimes
|
||||
if (currentBaselineLife == Integer.MIN_VALUE) {
|
||||
notNeededAsBlockers.remove(c);
|
||||
@@ -839,6 +839,7 @@ public class AiAttackController {
|
||||
if (attackMax != -1 && combat.getAttackers().size() >= attackMax)
|
||||
return aiAggression;
|
||||
|
||||
// TODO if lifeInDanger use chance to hold back some
|
||||
if (canAttackWrapper(attacker, defender) && isEffectiveAttacker(ai, attacker, combat, defender)) {
|
||||
combat.addAttacker(attacker, defender);
|
||||
}
|
||||
@@ -848,7 +849,7 @@ public class AiAttackController {
|
||||
}
|
||||
|
||||
// Cards that are remembered to attack anyway (e.g. temporarily stolen creatures)
|
||||
if (ai.getController() instanceof PlayerControllerAi) {
|
||||
if (ai.getController().isAI()) {
|
||||
// Only do this if |ai| is actually an AI - as we could be trying to predict how the human will attack.
|
||||
for (Card attacker : this.attackers) {
|
||||
if (AiCardMemory.isRememberedCard(ai, attacker, AiCardMemory.MemorySet.MANDATORY_ATTACKERS)) {
|
||||
@@ -894,7 +895,7 @@ public class AiAttackController {
|
||||
aiAggression = 6;
|
||||
for (Card attacker : this.attackers) {
|
||||
// reached max, breakup
|
||||
if (attackMax != -1 && combat.getAttackers().size() >= attackMax)
|
||||
if (combat.getAttackers().size() >= attackMax)
|
||||
break;
|
||||
if (canAttackWrapper(attacker, defender) && shouldAttack(attacker, this.blockers, combat, defender)) {
|
||||
combat.addAttacker(attacker, defender);
|
||||
|
||||
@@ -1061,7 +1061,7 @@ public class AiBlockController {
|
||||
|
||||
// remove all attackers that can't be blocked anyway
|
||||
for (final Card a : attackers) {
|
||||
if (!CombatUtil.canBeBlocked(a, ai)) {
|
||||
if (!CombatUtil.canBeBlocked(a, null, ai)) { // pass null to skip redundant checks for performance
|
||||
attackersLeft.remove(a);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1055,11 +1055,9 @@ public class AiController {
|
||||
// Cheaper Spectacle costs should be preferred
|
||||
// FIXME: Any better way to identify that these are the same ability, one with Spectacle and one not?
|
||||
// (looks like it's not a full-fledged alternative cost as such, and is not processed with other alt costs)
|
||||
if (a.isSpectacle() && !b.isSpectacle()
|
||||
&& a.getPayCosts().getTotalMana().getCMC() < b.getPayCosts().getTotalMana().getCMC()) {
|
||||
if (a.isSpectacle() && !b.isSpectacle() && a1 < b1) {
|
||||
return 1;
|
||||
} else if (b.isSpectacle() && !a.isSpectacle()
|
||||
&& b.getPayCosts().getTotalMana().getCMC() < a.getPayCosts().getTotalMana().getCMC()) {
|
||||
} else if (b.isSpectacle() && !a.isSpectacle() && b1 < a1) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -1088,6 +1086,9 @@ public class AiController {
|
||||
if (source.hasSVar("AIPriorityModifier")) {
|
||||
p += Integer.parseInt(source.getSVar("AIPriorityModifier"));
|
||||
}
|
||||
if (ComputerUtilCard.isCardRemAIDeck(source)) {
|
||||
p -= 10;
|
||||
}
|
||||
// don't play equipments before having any creatures
|
||||
if (source.isEquipment() && noCreatures) {
|
||||
p -= 9;
|
||||
@@ -1691,6 +1692,7 @@ public class AiController {
|
||||
Iterables.removeIf(saList, new Predicate<SpellAbility>() {
|
||||
@Override
|
||||
public boolean apply(final SpellAbility spellAbility) { //don't include removedAI cards if somehow the AI can play the ability or gain control of unsupported card
|
||||
// TODO allow when experimental profile?
|
||||
return spellAbility instanceof LandAbility || (spellAbility.getHostCard() != null && ComputerUtilCard.isCardRemAIDeck(spellAbility.getHostCard()));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -385,6 +385,9 @@ public class ComputerUtilCard {
|
||||
* @return the card
|
||||
*/
|
||||
public static Card getBestCreatureAI(final Iterable<Card> list) {
|
||||
if (Iterables.size(list) == 1) {
|
||||
return Iterables.get(list, 0);
|
||||
}
|
||||
return Aggregates.itemWithMax(Iterables.filter(list, CardPredicates.Presets.CREATURES), ComputerUtilCard.creatureEvaluator);
|
||||
}
|
||||
|
||||
@@ -397,6 +400,9 @@ public class ComputerUtilCard {
|
||||
* @return a {@link forge.game.card.Card} object.
|
||||
*/
|
||||
public static Card getWorstCreatureAI(final Iterable<Card> list) {
|
||||
if (Iterables.size(list) == 1) {
|
||||
return Iterables.get(list, 0);
|
||||
}
|
||||
return Aggregates.itemWithMin(Iterables.filter(list, CardPredicates.Presets.CREATURES), ComputerUtilCard.creatureEvaluator);
|
||||
}
|
||||
|
||||
@@ -410,6 +416,9 @@ public class ComputerUtilCard {
|
||||
* @return a {@link forge.game.card.Card} object.
|
||||
*/
|
||||
public static Card getBestCreatureToBounceAI(final CardCollectionView list) {
|
||||
if (Iterables.size(list) == 1) {
|
||||
return Iterables.get(list, 0);
|
||||
}
|
||||
final int tokenBonus = 60;
|
||||
Card biggest = null;
|
||||
int biggestvalue = -1;
|
||||
|
||||
@@ -1565,8 +1565,9 @@ public class AttachAi extends SpellAbilityAi {
|
||||
|
||||
boolean canBeBlocked = false;
|
||||
for (Player opp : ai.getOpponents()) {
|
||||
if (CombatUtil.canBeBlocked(card, opp)) {
|
||||
if (CombatUtil.canBeBlocked(card, null, opp)) {
|
||||
canBeBlocked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,10 +69,10 @@ public class EffectAi extends SpellAbilityAi {
|
||||
randomReturn = true;
|
||||
}
|
||||
} else if (logic.equals("Fog")) {
|
||||
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||
if (phase.isPlayerTurn(sa.getActivatingPlayer())) {
|
||||
return false;
|
||||
}
|
||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
if (!phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return false;
|
||||
}
|
||||
if (!game.getStack().isEmpty()) {
|
||||
@@ -216,9 +216,12 @@ public class EffectAi extends SpellAbilityAi {
|
||||
} else if (logic.equals("Fight")) {
|
||||
return FightAi.canFightAi(ai, sa, 0, 0);
|
||||
} else if (logic.equals("Pump")) {
|
||||
if (SpellApiToAi.Converter.get(sa.getApi()).canPlayAIWithSubs(ai, sa)) {
|
||||
List<Card> options = ai.getCreaturesInPlay();
|
||||
if (phase.isPlayerTurn(ai) && phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) && !options.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(options));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else if (logic.equals("Burn")) {
|
||||
// for DamageDeal sub-abilities (eg. Wild Slash, Skullcrack)
|
||||
SpellAbility burn = sa.getSubAbility();
|
||||
@@ -245,7 +248,7 @@ public class EffectAi extends SpellAbilityAi {
|
||||
Card host = sa.getHostCard();
|
||||
Combat combat = game.getCombat();
|
||||
if (combat != null && combat.isAttacking(host, ai) && !combat.isBlocked(host)
|
||||
&& game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
&& phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
&& !AiCardMemory.isRememberedCard(ai, host, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN)) {
|
||||
AiCardMemory.rememberCard(ai, host, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN); // ideally needs once per combat or something
|
||||
return true;
|
||||
|
||||
@@ -102,7 +102,7 @@ public final class EncodeAi extends SpellAbilityAi {
|
||||
public boolean apply(final Card c) {
|
||||
boolean canAttackOpponent = false;
|
||||
for (Player opp : ai.getOpponents()) {
|
||||
if (CombatUtil.canAttack(c, opp) && !CombatUtil.canBeBlocked(c, opp)) {
|
||||
if (CombatUtil.canAttack(c, opp) && !CombatUtil.canBeBlocked(c, null, opp)) {
|
||||
canAttackOpponent = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -336,7 +336,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
Keyword.FLANKING).isEmpty();
|
||||
} else if (keyword.startsWith("Trample")) {
|
||||
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|
||||
&& CombatUtil.canBeBlocked(card, opp)
|
||||
&& CombatUtil.canBeBlocked(card, null, opp)
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& newPower > 1
|
||||
&& Iterables.any(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card));
|
||||
|
||||
Reference in New Issue
Block a user