Merge branch 'counters' into 'master'

CountersAi: skip for only one against Vorinclex

See merge request core-developers/forge!5536
This commit is contained in:
Michael Kamensky
2021-10-11 04:19:30 +00:00
9 changed files with 43 additions and 24 deletions

View File

@@ -2253,6 +2253,7 @@ public class AiController {
return true;
}
// AI logic for choosing which replacement effect to apply happens here.
public ReplacementEffect chooseSingleReplacementEffect(List<ReplacementEffect> list) {
// no need to choose anything
if (list.size() <= 1) {
@@ -2291,7 +2292,8 @@ public class AiController {
}
}
// AI logic for choosing which replacement effect to apply happens here.
// TODO always lower counters with Vorinclex first, might turn it from 1 to 0 as final
return Iterables.getFirst(list, null);
}

View File

@@ -1488,8 +1488,8 @@ public class ComputerUtilCard {
if (combat.isAttacking(c) && opp.getLife() > 0) {
int dmg = ComputerUtilCombat.damageIfUnblocked(c, opp, combat, true);
int pumpedDmg = ComputerUtilCombat.damageIfUnblocked(pumped, opp, pumpedCombat, true);
int poisonOrig = opp.canReceiveCounters(CounterEnumType.POISON) ? ComputerUtilCombat.poisonIfUnblocked(c, ai) : 0;
int poisonPumped = opp.canReceiveCounters(CounterEnumType.POISON) ? ComputerUtilCombat.poisonIfUnblocked(pumped, ai) : 0;
int poisonOrig = ComputerUtilCombat.poisonIfUnblocked(c, ai);
int poisonPumped = ComputerUtilCombat.poisonIfUnblocked(pumped, ai);
// predict Infect
if (pumpedDmg == 0 && c.hasKeyword(Keyword.INFECT)) {

View File

@@ -235,17 +235,25 @@ public class ComputerUtilCombat {
* @return a int.
*/
public static int poisonIfUnblocked(final Card attacker, final Player attacked) {
if (!attacked.canReceiveCounters(CounterEnumType.POISON)) {
return 0;
}
int damage = attacker.getNetCombatDamage();
int poison = 0;
damage += predictPowerBonusOfAttacker(attacker, null, null, false);
if (attacker.hasKeyword(Keyword.INFECT)) {
int pd = predictDamageTo(attacked, damage, attacker, true);
// opponent can always order it so that he gets 0
if (pd == 1 && Iterables.any(attacker.getController().getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Vorinclex, Monstrous Raider"))) {
pd = 0;
}
poison += pd;
if (attacker.hasKeyword(Keyword.DOUBLE_STRIKE)) {
poison += pd;
}
}
if (attacker.hasKeyword(Keyword.POISONOUS) && (damage > 0)) {
if (attacker.hasKeyword(Keyword.POISONOUS) && damage > 0) {
// TODO need to check for magnitude 1, each of their triggers could be replaced to 0
poison += attacker.getKeywordMagnitude(Keyword.POISONOUS);
}
return poison;

View File

@@ -20,15 +20,20 @@ package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CounterEnumType;
import forge.game.card.CounterType;
import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
@@ -40,7 +45,7 @@ import forge.util.Aggregates;
* @author Forge
* @version $Id$
*/
public abstract class CountersAi {
public abstract class CountersAi extends SpellAbilityAi {
// An AbilityFactory subclass for Putting or Removing Counters on Cards.
/**
@@ -54,10 +59,17 @@ public abstract class CountersAi {
* a {@link java.lang.String} object.
* @param amount
* a int.
* @param newParam TODO
* @return a {@link forge.game.card.Card} object.
*/
public static Card chooseCursedTarget(final CardCollectionView list, final String type, final int amount) {
public static Card chooseCursedTarget(final CardCollectionView list, final String type, final int amount, final Player ai) {
Card choice;
// opponent can always order it so that he gets 0
if (amount == 1 && Iterables.any(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Vorinclex, Monstrous Raider"))) {
return null;
}
if (type.equals("M1M1")) {
// try to kill the best killable creature, or reduce the best one
// but try not to target a Undying Creature
@@ -87,6 +99,7 @@ public abstract class CountersAi {
*/
public static Card chooseBoonTarget(final CardCollectionView list, final String type) {
Card choice = null;
if (type.equals("P1P1")) {
choice = ComputerUtilCard.getBestCreatureAI(list);

View File

@@ -49,7 +49,6 @@ public class CountersMultiplyAi extends SpellAbilityAi {
if (!c.canReceiveCounters(counterType)) {
return false;
}
} else {
for (Map.Entry<CounterType, Integer> e : c.getCounters().entrySet()) {
// has negative counter it would double
@@ -146,7 +145,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
CardCollection aiList = CardLists.filterControlledBy(list, ai);
if (!aiList.isEmpty()) {
// counter type list to check
// first loyalty, then P1P!, then Charge Counter
// first loyalty, then P1P1, then Charge Counter
List<CounterEnumType> typeList = Lists.newArrayList(CounterEnumType.LOYALTY, CounterEnumType.P1P1, CounterEnumType.CHARGE);
for (CounterEnumType type : typeList) {
// enough targets
@@ -182,7 +181,6 @@ public class CountersMultiplyAi extends SpellAbilityAi {
private void addTargetsByCounterType(final Player ai, final SpellAbility sa, final CardCollection list,
final CounterType type) {
CardCollection newList = CardLists.filter(list, CardPredicates.hasCounter(type));
if (newList.isEmpty()) {
return;

View File

@@ -63,7 +63,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
boolean opponentPoison = false;
for (final Player o : ai.getOpponents()) {
opponentPoison |= o.getPoisonCounters() >= 1;
opponentPoison |= o.getPoisonCounters() > 0 && o.canReceiveCounters(CounterEnumType.POISON);
hperms.addAll(CardLists.filter(o.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {

View File

@@ -53,7 +53,7 @@ import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.MyRandom;
public class CountersPutAi extends SpellAbilityAi {
public class CountersPutAi extends CountersAi {
/*
* (non-Javadoc)
@@ -179,7 +179,7 @@ public class CountersPutAi extends SpellAbilityAi {
if (abTgt.canTgtCreature()) {
// try to kill creature with -1/-1 counters if it can
// receive counters, execpt it has undying
// receive counters, except it has undying
CardCollection oppCreat = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa);
CardCollection oppCreatM1 = CardLists.filter(oppCreat, CardPredicates.hasCounter(CounterEnumType.M1M1));
oppCreatM1 = CardLists.getNotKeyword(oppCreatM1, Keyword.UNDYING);
@@ -549,7 +549,7 @@ public class CountersPutAi extends SpellAbilityAi {
}
if (sa.isCurse()) {
choice = CountersAi.chooseCursedTarget(list, type, amount);
choice = chooseCursedTarget(list, type, amount, ai);
} else {
if (type.equals("P1P1") && !SpellAbilityAi.isSorcerySpeed(sa)) {
for (Card c : list) {
@@ -564,15 +564,15 @@ public class CountersPutAi extends SpellAbilityAi {
if (abCost == null
|| (ph.is(PhaseType.END_OF_TURN) && ph.getPlayerTurn().isOpponentOf(ai))) {
// only use at opponent EOT unless it is free
choice = CountersAi.chooseBoonTarget(list, type);
choice = chooseBoonTarget(list, type);
}
}
}
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Dromoka's Command")) {
choice = CountersAi.chooseBoonTarget(list, type);
choice = chooseBoonTarget(list, type);
}
} else {
choice = CountersAi.chooseBoonTarget(list, type);
choice = chooseBoonTarget(list, type);
}
}
@@ -681,7 +681,6 @@ public class CountersPutAi extends SpellAbilityAi {
sa.resetTargets();
// target loop
while (sa.canAddMoreTarget()) {
if (list.isEmpty()) {
if (!sa.isTargetNumberValid()
|| sa.getTargets().size() == 0) {
@@ -693,7 +692,7 @@ public class CountersPutAi extends SpellAbilityAi {
}
if (sa.isCurse()) {
choice = CountersAi.chooseCursedTarget(list, type, amount);
choice = chooseCursedTarget(list, type, amount, ai);
} else {
CardCollection lands = CardLists.filter(list, CardPredicates.Presets.LANDS);
SpellAbility animate = sa.findSubAbilityByType(ApiType.Animate);
@@ -702,7 +701,7 @@ public class CountersPutAi extends SpellAbilityAi {
} else if ("BoonCounterOnOppCreature".equals(logic)) {
choice = ComputerUtilCard.getWorstCreatureAI(list);
} else {
choice = CountersAi.chooseBoonTarget(list, type);
choice = chooseBoonTarget(list, type);
}
}
@@ -838,7 +837,7 @@ public class CountersPutAi extends SpellAbilityAi {
// Choose targets here:
if (sa.isCurse()) {
if (preferred) {
choice = CountersAi.chooseCursedTarget(list, type, amount);
choice = chooseCursedTarget(list, type, amount, ai);
if (choice == null && mandatory) {
choice = Aggregates.random(list);
}
@@ -852,7 +851,7 @@ public class CountersPutAi extends SpellAbilityAi {
} else {
if (preferred) {
list = ComputerUtil.getSafeTargets(ai, sa, list);
choice = CountersAi.chooseBoonTarget(list, type);
choice = chooseBoonTarget(list, type);
if (choice == null && mandatory) {
choice = Aggregates.random(list);
}

View File

@@ -104,7 +104,7 @@ public class PoisonAi extends SpellAbilityAi {
if (!betterTgts.isEmpty()) {
tgts = betterTgts;
} else if (mandatory) {
// no better choice but better than hiting himself
// no better choice but better than hitting himself
sa.getTargets().add(tgts.getFirst());
return true;
}
@@ -121,7 +121,7 @@ public class PoisonAi extends SpellAbilityAi {
// need to target something, try to target allies
PlayerCollection allies = ai.getAllies().filter(PlayerPredicates.isTargetableBy(sa));
if (!allies.isEmpty()) {
// some ally would be uneffected
// some ally would be unaffected
PlayerCollection betterAllies = allies.filter(new Predicate<Player>() {
@Override
public boolean apply(Player input) {

View File

@@ -145,7 +145,6 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
}
public boolean requirementsCheck(Game game, Map<String,String> params) {
if (this.isSuppressed()) {
return false; // Effect removed by effect
}