Merge branch 'pwcombat' into 'master'

Smarter check for AI if specific attacker should go for PW

See merge request core-developers/forge!5585
This commit is contained in:
Michael Kamensky
2021-10-19 04:05:05 +00:00
17 changed files with 104 additions and 129 deletions

View File

@@ -18,7 +18,6 @@
package forge.ai;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.google.common.base.Predicate;
@@ -44,7 +43,6 @@ import forge.game.combat.CombatUtil;
import forge.game.combat.GlobalAttackRestrictions;
import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
@@ -53,6 +51,7 @@ import forge.util.Aggregates;
import forge.util.Expressions;
import forge.util.MyRandom;
import forge.util.TextUtil;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView;
@@ -207,7 +206,7 @@ public class AiAttackController {
* a {@link forge.game.combat.Combat} object.
* @return a boolean.
*/
public final boolean isEffectiveAttacker(final Player ai, final Card attacker, final Combat combat) {
public final boolean isEffectiveAttacker(final Player ai, final Card attacker, final Combat combat, final GameEntity defender) {
// if the attacker will die when attacking don't attack
if ((attacker.getNetToughness() + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, null, combat, true)) <= 0) {
return false;
@@ -238,10 +237,8 @@ public class AiAttackController {
return true;
}
final Player opp = this.defendingOpponent;
// Damage opponent if unblocked
final int dmgIfUnblocked = ComputerUtilCombat.damageIfUnblocked(attacker, opp, combat, true);
final int dmgIfUnblocked = ComputerUtilCombat.damageIfUnblocked(attacker, defender, combat, true);
if (dmgIfUnblocked > 0) {
boolean onlyIfExalted = false;
if (combat.getAttackers().isEmpty() && ai.countExaltedBonus() > 0
@@ -255,14 +252,14 @@ public class AiAttackController {
}
}
// Poison opponent if unblocked
if (ComputerUtilCombat.poisonIfUnblocked(attacker, opp) > 0) {
if (defender instanceof Player && ComputerUtilCombat.poisonIfUnblocked(attacker, (Player) defender) > 0) {
return true;
}
// TODO check if that makes sense
int exalted = ai.countExaltedBonus();
if (this.attackers.size() == 1 && exalted > 0
&& ComputerUtilCombat.predictDamageTo(opp, exalted, attacker, true) > 0) {
&& ComputerUtilCombat.predictDamageTo(defender, exalted, attacker, true) > 0) {
return true;
}
@@ -315,7 +312,6 @@ public class AiAttackController {
}
// this checks to make sure that the computer player doesn't lose when the human player attacks
// this method is used by getAttackers()
public final List<Card> notNeededAsBlockers(final Player ai, final List<Card> attackers) {
final List<Card> notNeededAsBlockers = new ArrayList<>(attackers);
int fixedBlockers = 0;
@@ -423,7 +419,7 @@ public class AiAttackController {
if ((blockersNeeded == 0 || finestHour) && !this.oppList.isEmpty()) {
// total attack = biggest creature + exalted, *2 if Rafiq is in play
int humanBasePower = getAttack(this.oppList.get(0)) + humanExaltedBonus;
int humanBasePower = ComputerUtilCombat.getAttack(this.oppList.get(0)) + humanExaltedBonus;
if (finestHour) {
// For Finest Hour, one creature could attack and get the bonus TWICE
humanBasePower = humanBasePower + humanExaltedBonus;
@@ -649,9 +645,8 @@ public class AiAttackController {
if (-1 == n) {
System.out.println("getMustAttackEntity() or getMustAttackEntityThisTurn() returned something not in defenders.");
return prefDefender;
} else {
return entity;
}
return entity;
} else {
// 1. assault the opponent if you can kill him
if (bAssault) {
@@ -688,6 +683,7 @@ public class AiAttackController {
boolean tradeIfTappedOut = false;
int extraChanceIfOppHasMana = 0;
boolean tradeIfLowerLifePressure = false;
boolean predictEvasion = false;
if (ai.getController().isAI()) {
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
playAggro = aic.getBooleanProperty(AiProps.PLAY_AGGRO);
@@ -695,6 +691,7 @@ public class AiAttackController {
tradeIfTappedOut = aic.getBooleanProperty(AiProps.ATTACK_INTO_TRADE_WHEN_TAPPED_OUT);
extraChanceIfOppHasMana = aic.getIntProperty(AiProps.CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA);
tradeIfLowerLifePressure = aic.getBooleanProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE);
predictEvasion = aic.getBooleanProperty(AiProps.COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION);
}
final boolean bAssault = doAssault(ai);
@@ -736,7 +733,7 @@ public class AiAttackController {
} else if (attacker.getSVar("MustAttack").equals("True")) {
mustAttack = true;
} else if (attacker.hasSVar("EndOfTurnLeavePlay")
&& isEffectiveAttacker(ai, attacker, combat)) {
&& isEffectiveAttacker(ai, attacker, combat, defender)) {
mustAttack = true;
} else if (seasonOfTheWitch) {
// TODO: if there are other ways to tap this creature (like mana creature), then don't need to attack
@@ -767,7 +764,7 @@ public class AiAttackController {
return;
}
if (bAssault) {
if (bAssault && defender == this.defendingOpponent) { // in case we are forced to attack someone else
if (LOG_AI_ATTACKS)
System.out.println("Assault");
CardLists.sortByPowerDesc(attackersLeft);
@@ -776,7 +773,7 @@ public class AiAttackController {
if (attackMax != -1 && combat.getAttackers().size() >= attackMax)
return;
if (canAttackWrapper(attacker, defender) && isEffectiveAttacker(ai, attacker, combat)) {
if (canAttackWrapper(attacker, defender) && isEffectiveAttacker(ai, attacker, combat, defender)) {
combat.addAttacker(attacker, defender);
}
}
@@ -817,7 +814,7 @@ public class AiAttackController {
System.out.println("Exalted");
this.aiAggression = 6;
for (Card attacker : this.attackers) {
if (canAttackWrapper(attacker, defender) && this.shouldAttack(ai, attacker, this.blockers, combat)) {
if (canAttackWrapper(attacker, defender) && shouldAttack(ai, attacker, this.blockers, combat, defender)) {
combat.addAttacker(attacker, defender);
return;
}
@@ -833,7 +830,7 @@ public class AiAttackController {
// reached max, breakup
if (attackMax != -1 && combat.getAttackers().size() >= attackMax)
break;
if (canAttackWrapper(attacker, defender) && this.shouldAttack(ai, attacker, this.blockers, combat)) {
if (canAttackWrapper(attacker, defender) && shouldAttack(ai, attacker, this.blockers, combat, defender)) {
combat.addAttacker(attacker, defender);
}
}
@@ -853,7 +850,8 @@ public class AiAttackController {
final List<Card> nextTurnAttackers = new ArrayList<>();
int candidateCounterAttackDamage = 0;
final Player opp = this.defendingOpponent;
final Player opp = defender instanceof Player ? (Player) defender : ((Card)defender).getController();
this.oppList = getOpponentCreatures(opp);
// get the potential damage and strength of the AI forces
final List<Card> candidateAttackers = new ArrayList<>();
int candidateUnblockedDamage = 0;
@@ -867,9 +865,6 @@ public class AiAttackController {
}
}
boolean predictEvasion = (ai.getController().isAI()
&& ((PlayerControllerAi)ai.getController()).getAi().getBooleanProperty(AiProps.COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION));
CardCollection categorizedOppList = new CardCollection();
if (predictEvasion) {
// If predicting evasion, make sure that attackers with evasion are considered first
@@ -1059,70 +1054,60 @@ public class AiAttackController {
if ( LOG_AI_ATTACKS )
System.out.println("attackersLeft = " + attackersLeft);
for (int i = 0; i < attackersLeft.size(); i++) {
final Card attacker = attackersLeft.get(i);
if (this.aiAggression < 5 && !attacker.hasFirstStrike() && !attacker.hasDoubleStrike()
&& ComputerUtilCombat.getTotalFirstStrikeBlockPower(attacker, this.defendingOpponent)
>= ComputerUtilCombat.getDamageToKill(attacker)) {
continue;
}
FCollection<GameEntity> possibleDefenders = new FCollection<>(opp);
possibleDefenders.addAll(opp.getPlaneswalkersInPlay());
if (this.shouldAttack(ai, attacker, this.blockers, combat) && canAttackWrapper(attacker, defender)) {
combat.addAttacker(attacker, defender);
// check if attackers are enough to finish the attacked planeswalker
if (defender instanceof Card) {
Card pw = (Card) defender;
final int blockNum = this.blockers.size();
int attackNum = 0;
int damage = 0;
List<Card> attacking = combat.getAttackersOf(defender);
CardLists.sortByPowerAsc(attacking);
for (Card atta : attacking) {
if (attackNum >= blockNum || !CombatUtil.canBeBlocked(attacker, this.blockers, combat)) {
damage += ComputerUtilCombat.damageIfUnblocked(atta, opp, null, false);
} else if (CombatUtil.canBeBlocked(attacker, this.blockers, combat)) {
attackNum++;
}
}
// if enough damage: switch to next planeswalker or player
if (damage >= pw.getCounters(CounterEnumType.LOYALTY)) {
List<Card> pwDefending = combat.getDefendingPlaneswalkers();
// look for next planeswalker
for (Card walker : Lists.newArrayList(pwDefending)) {
if (!combat.getAttackersOf(walker).isEmpty()) {
pwDefending.remove(walker);
while (!attackersLeft.isEmpty()) {
CardCollection attackersAssigned = new CardCollection();
for (int i = 0; i < attackersLeft.size(); i++) {
final Card attacker = attackersLeft.get(i);
if (this.aiAggression < 5 && !attacker.hasFirstStrike() && !attacker.hasDoubleStrike()
&& ComputerUtilCombat.getTotalFirstStrikeBlockPower(attacker, this.defendingOpponent)
>= ComputerUtilCombat.getDamageToKill(attacker)) {
continue;
}
if (shouldAttack(ai, attacker, this.blockers, combat, defender) && canAttackWrapper(attacker, defender)) {
combat.addAttacker(attacker, defender);
attackersAssigned.add(attacker);
// check if attackers are enough to finish the attacked planeswalker
if (i < attackersLeft.size() - 1 && defender instanceof Card) {
final int blockNum = this.blockers.size();
int attackNum = 0;
int damage = 0;
List<Card> attacking = combat.getAttackersOf(defender);
CardLists.sortByPowerDesc(attacking);
for (Card atta : attacking) {
if (attackNum >= blockNum || !CombatUtil.canBeBlocked(atta, this.blockers, combat)) {
damage += ComputerUtilCombat.damageIfUnblocked(atta, defender, null, false);
} else {
attackNum++;
}
}
if (pwDefending.isEmpty()) {
defender = Collections.min(Lists.newArrayList(combat.getDefendingPlayers()), PlayerPredicates.compareByLife());
}
else {
final Card pwNearUlti = ComputerUtilCard.getBestPlaneswalkerToDamage(pwDefending);
defender = pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending);
// if enough damage: switch to next planeswalker
if (damage >= ComputerUtilCombat.getDamageToKill((Card) defender)) {
break;
}
}
}
}
attackersLeft.removeAll(attackersAssigned);
possibleDefenders.remove(defender);
if (attackersLeft.isEmpty() || possibleDefenders.isEmpty()) {
break;
}
CardCollection pwDefending = new CardCollection(Iterables.filter(possibleDefenders, Card.class));
if (pwDefending.isEmpty()) {
// TODO for now only looks at same player as we'd have to check the others from start too
//defender = Collections.min(new PlayerCollection(Iterables.filter(possibleDefenders, Player.class)), PlayerPredicates.compareByLife());
defender = opp;
} else {
final Card pwNearUlti = ComputerUtilCard.getBestPlaneswalkerToDamage(pwDefending);
defender = pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending);
}
}
} // getAttackers()
/**
* <p>
* getAttack.
* </p>
*
* @param c
* a {@link forge.game.card.Card} object.
* @return a int.
*/
public final static int getAttack(final Card c) {
int n = c.getNetCombatDamage();
if (c.hasKeyword(Keyword.DOUBLE_STRIKE)) {
n *= 2;
}
return n;
}
/**
@@ -1138,7 +1123,7 @@ public class AiAttackController {
* a {@link forge.game.combat.Combat} object.
* @return a boolean.
*/
public final boolean shouldAttack(final Player ai, final Card attacker, final List<Card> defenders, final Combat combat) {
public final boolean shouldAttack(final Player ai, final Card attacker, final List<Card> defenders, final Combat combat, final GameEntity defender) {
boolean canBeKilled = false; // indicates if the attacker can be killed
boolean canBeKilledByOne = false; // indicates if the attacker can be killed by a single blocker
boolean canKillAll = true; // indicates if the attacker can kill all single blockers
@@ -1169,7 +1154,7 @@ public class AiAttackController {
}
}
if (!isEffectiveAttacker(ai, attacker, combat)) {
if (!isEffectiveAttacker(ai, attacker, combat, defender)) {
return false;
}
boolean hasAttackEffect = attacker.getSVar("HasAttackEffect").equals("TRUE") || attacker.hasStartOfKeyword("Annihilator");
@@ -1205,29 +1190,29 @@ public class AiAttackController {
// look at the attacker in relation to the blockers to establish a
// number of factors about the attacking context that will be relevant
// to the attackers decision according to the selected strategy
for (final Card defender : validBlockers) {
for (final Card blocker : validBlockers) {
// if both isWorthLessThanAllKillers and canKillAllDangerous are false there's nothing more to check
if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) {
numberOfPossibleBlockers += 1;
if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, false)
if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, blocker, combat, false)
&& !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterEnumType.P1P1) == 0)) {
canBeKilledByOne = true; // there is a single creature on the battlefield that can kill the creature
// see if the defending creature is of higher or lower
// value. We don't want to attack only to lose value
if (isWorthLessThanAllKillers && !attacker.hasSVar("SacMe")
&& ComputerUtilCard.evaluateCreature(defender) <= ComputerUtilCard.evaluateCreature(attacker)) {
&& ComputerUtilCard.evaluateCreature(blocker) <= ComputerUtilCard.evaluateCreature(attacker)) {
isWorthLessThanAllKillers = false;
}
}
// see if this attacking creature can destroy this defender, if
// not record that it can't kill everything
if (canKillAllDangerous && !ComputerUtilCombat.canDestroyBlocker(ai, defender, attacker, combat, false)) {
if (canKillAllDangerous && !ComputerUtilCombat.canDestroyBlocker(ai, blocker, attacker, combat, false)) {
canKillAll = false;
if (defender.getSVar("HasCombatEffect").equals("TRUE") || defender.getSVar("HasBlockEffect").equals("TRUE")) {
if (blocker.getSVar("HasCombatEffect").equals("TRUE") || blocker.getSVar("HasBlockEffect").equals("TRUE")) {
canKillAllDangerous = false;
} else {
if (defender.hasKeyword(Keyword.WITHER) || defender.hasKeyword(Keyword.INFECT)
|| defender.hasKeyword(Keyword.LIFELINK)) {
if (blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT)
|| blocker.hasKeyword(Keyword.LIFELINK)) {
canKillAllDangerous = false;
// there is a creature that can survive an attack from this creature
// and combat will have negative effects
@@ -1371,7 +1356,6 @@ public class AiAttackController {
break;
}
}
}
if (missTarget) {

View File

@@ -167,7 +167,6 @@ public class ComputerUtilCombat {
return totalDamageOfBlockers(attacker, list);
}
// This function takes Doran and Double Strike into account
/**
* <p>
@@ -202,10 +201,10 @@ public class ComputerUtilCombat {
* a {@link forge.game.combat.Combat} object.
* @return a int.
*/
public static int damageIfUnblocked(final Card attacker, final Player attacked, final Combat combat, boolean withoutAbilities) {
public static int damageIfUnblocked(final Card attacker, final GameEntity attacked, final Combat combat, boolean withoutAbilities) {
int damage = attacker.getNetCombatDamage();
int sum = 0;
if (!attacked.canLoseLife()) {
if (attacked instanceof Player && !((Player) attacked).canLoseLife()) {
return 0;
}
@@ -2139,7 +2138,6 @@ public class ComputerUtilCombat {
return damageMap;
} // setAssignedDamage()
// how much damage is enough to kill the creature (for AI)
/**
* <p>
@@ -2175,13 +2173,13 @@ public class ComputerUtilCombat {
*/
public static final int getEnoughDamageToKill(final Card c, final int maxDamage, final Card source, final boolean isCombat,
final boolean noPrevention) {
final int killDamage = c.isPlaneswalker() ? c.getCurrentLoyalty() : getDamageToKill(c);
final int killDamage = getDamageToKill(c);
if (c.hasKeyword(Keyword.INDESTRUCTIBLE) || c.getShieldCount() > 0) {
if (!(source.hasKeyword(Keyword.WITHER) || source.hasKeyword(Keyword.INFECT))) {
return maxDamage + 1;
}
} else if (source.hasKeyword(Keyword.DEATHTOUCH)) {
} else if (source.hasKeyword(Keyword.DEATHTOUCH) && !c.isPlaneswalker()) {
for (int i = 1; i <= maxDamage; i++) {
if (noPrevention) {
if (c.staticReplaceDamage(i, source, isCombat) > 0) {
@@ -2218,9 +2216,9 @@ public class ComputerUtilCombat {
*/
public final static int getDamageToKill(final Card c) {
int damageShield = c.getPreventNextDamageTotalShields();
int killDamage = c.getLethalDamage() + damageShield;
int killDamage = (c.isPlaneswalker() ? c.getCurrentLoyalty() : c.getLethalDamage()) + damageShield;
if ((killDamage > damageShield)
if (killDamage > damageShield
&& c.hasSVar("DestroyWhenDamaged")) {
killDamage = 1 + damageShield;
}

View File

@@ -152,8 +152,8 @@ public class PumpAi extends PumpAiBase {
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & "))
: Lists.newArrayList();
final String numDefense = sa.hasParam("NumDef") ? sa.getParam("NumDef") : "";
final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : "";
final String numDefense = sa.getParamOrDefault("NumDef", "");
final String numAttack = sa.getParamOrDefault("NumAtt", "");
final String aiLogic = sa.getParamOrDefault("AILogic", "");
@@ -699,8 +699,8 @@ public class PumpAi extends PumpAiBase {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final SpellAbility root = sa.getRootAbility();
final String numDefense = sa.hasParam("NumDef") ? sa.getParam("NumDef") : "";
final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : "";
final String numDefense = sa.getParamOrDefault("NumDef", "");
final String numAttack = sa.getParamOrDefault("NumAtt", "");
if (sa.getSVar("X").equals("Count$xPaid")) {
sa.setXManaCostPaid(null);
@@ -750,8 +750,8 @@ public class PumpAi extends PumpAiBase {
final SpellAbility root = sa.getRootAbility();
final Card source = sa.getHostCard();
final String numDefense = sa.hasParam("NumDef") ? sa.getParam("NumDef") : "";
final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : "";
final String numDefense = sa.getParamOrDefault("NumDef", "");
final String numAttack = sa.getParamOrDefault("NumAtt", "");
if (numDefense.equals("-X") && sa.getSVar("X").equals("Count$ChosenNumber")) {
int energy = ai.getCounters(CounterEnumType.ENERGY);

View File

@@ -36,17 +36,17 @@ public class RepeatEachAi extends SpellAbilityAi {
return false;
} else if ("CloneAllTokens".equals(logic)) {
List<Card> humTokenCreats = CardLists.filter(aiPlayer.getOpponents().getCreaturesInPlay(), Presets.TOKEN);
List<Card> compTokenCreats = CardLists.filter(aiPlayer.getCreaturesInPlay(), Presets.TOKEN);
List<Card> compTokenCreats = aiPlayer.getTokensInPlay();
return compTokenCreats.size() > humTokenCreats.size();
} else if ("BalanceLands".equals(logic)) {
if (CardLists.filter(aiPlayer.getCardsIn(ZoneType.Battlefield), Presets.LANDS).size() >= 5) {
if (aiPlayer.getLandsInPlay().size() >= 5) {
return false;
}
List<Player> opponents = aiPlayer.getOpponents();
for(Player opp : opponents) {
if (CardLists.filter(opp.getCardsIn(ZoneType.Battlefield), Presets.LANDS).size() < 4) {
if (opp.getLandsInPlay().size() < 4) {
return false;
}
}

View File

@@ -1644,7 +1644,7 @@ public class GameAction {
private boolean handlePlaneswalkerRule(Player p, CardZoneTable table) {
// get all Planeswalkers
final List<Card> list = CardLists.filter(p.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS);
final List<Card> list = p.getPlaneswalkersInPlay();
boolean recheck = false;
//final Multimap<String, Card> uniqueWalkers = ArrayListMultimap.create(); // Not used as of Ixalan

View File

@@ -45,7 +45,6 @@ import forge.game.card.CardPredicates;
import forge.game.card.CardState;
import forge.game.card.CardUtil;
import forge.game.card.CounterType;
import forge.game.card.CardPredicates.Presets;
import forge.game.cost.Cost;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
@@ -2403,7 +2402,7 @@ public class AbilityUtils {
if (sq[0].startsWith("Domain")) {
int n = 0;
Player neededPlayer = sq[0].equals("DomainActivePlayer") ? game.getPhaseHandler().getPlayerTurn() : player;
CardCollection someCards = CardLists.filter(neededPlayer.getCardsIn(ZoneType.Battlefield), Presets.LANDS);
CardCollection someCards = neededPlayer.getLandsInPlay();
for (String basic : MagicColor.Constant.BASIC_LANDS) {
if (!CardLists.getType(someCards, basic).isEmpty()) {
n++;

View File

@@ -24,7 +24,6 @@ import forge.game.card.CardCollection;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardUtil;
import forge.game.card.CounterEnumType;
import forge.game.card.CounterType;
@@ -132,7 +131,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
List<GameObject> tgtObjects = Lists.newArrayList();
int divrem = 0;
if (sa.hasParam("Bolster")) {
CardCollection creatsYouCtrl = CardLists.filter(activator.getCardsIn(ZoneType.Battlefield), Presets.CREATURES);
CardCollection creatsYouCtrl = activator.getCreaturesInPlay();
CardCollection leastToughness = new CardCollection(Aggregates.listWithMin(creatsYouCtrl, CardPredicates.Accessors.fnGetDefense));
Map<String, Object> params = Maps.newHashMap();

View File

@@ -57,10 +57,10 @@ public class DamagePreventEffect extends DamagePreventEffectBase {
return sb.toString();
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellEffect#resolve(java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellEffect#resolve(java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
public void resolve(SpellAbility sa) {
Card host = sa.getHostCard();
int numDam = AbilityUtils.calculateAmount(host, sa.getParam("Amount"), sa);

View File

@@ -169,7 +169,6 @@ public class CardLists {
Collections.sort(list, Collections.reverseOrder(PowerComparator));
}
/**
*
* Given a CardCollection c, return a CardCollection that contains a random amount of cards from c.

View File

@@ -1479,7 +1479,7 @@ public class CardProperty {
if (StringUtils.isEmpty(what)) return combat.isBlocking(card);
if (what.startsWith("Source")) return combat.isBlocking(card, source);
if (what.startsWith("CreatureYouCtrl")) {
for (final Card c : CardLists.filter(sourceController.getCardsIn(ZoneType.Battlefield), Presets.CREATURES))
for (final Card c : sourceController.getCreaturesInPlay())
if (combat.isBlocking(card, c))
return true;
return false;

View File

@@ -13,8 +13,6 @@ import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
@@ -88,7 +86,7 @@ public class AttackRequirement {
if (c.hasKeyword("Each opponent must attack you or a planeswalker you control with at least one creature each combat if able.")) {
if (attacker.getController().isOpponentOf(c.getController()) && !defenderOrPWSpecific.containsKey(c.getController())) {
defenderOrPWSpecific.put(c.getController(), 1);
for (Card pw : CardLists.filter(c.getController().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS)) {
for (Card pw : c.getController().getPlaneswalkersInPlay()) {
// Add the attack alternatives that suffice (planeswalkers that can be attacked instead of the player)
if (!defenderSpecificAlternatives.containsKey(c.getController())) {
defenderSpecificAlternatives.put(c.getController(), Lists.newArrayList());

View File

@@ -243,7 +243,6 @@ public class Combat {
public final void addAttacker(final Card c, GameEntity defender) {
addAttacker(c, defender, null);
}
public final void addAttacker(final Card c, GameEntity defender, AttackingBand band) {
Collection<AttackingBand> attackersOfDefender = attackedByBands.get(defender);
if (attackersOfDefender == null) {
@@ -260,8 +259,7 @@ public class Combat {
if (band == null || !attackersOfDefender.contains(band)) {
band = new AttackingBand(c);
attackersOfDefender.add(band);
}
else {
} else {
band.addAttacker(c);
}
c.updateAttackingForView();

View File

@@ -69,7 +69,7 @@ public class CombatUtil {
final FCollection<GameEntity> defenders = new FCollection<>();
for (final Player defender : playerWhoAttacks.getOpponents()) {
defenders.add(defender);
final CardCollection planeswalkers = CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS);
final CardCollection planeswalkers = defender.getPlaneswalkersInPlay();
defenders.addAll(planeswalkers);
}
return defenders;

View File

@@ -2449,6 +2449,10 @@ public class Player extends GameEntity implements Comparable<Player> {
return CardLists.filter(getCardsIn(ZoneType.Battlefield), Presets.CREATURES);
}
public CardCollection getPlaneswalkersInPlay() {
return CardLists.filter(getCardsIn(ZoneType.Battlefield), Presets.PLANESWALKERS);
}
/**
* use to get a list of tokens in play for a given player.
*/

View File

@@ -6,7 +6,7 @@ K:Flying
R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | DayTime$ Neither | ReplaceWith$ DoDay | Description$ If it's neither day nor night, it becomes day as CARDNAME enters the battlefield.
SVar:DoDay:DB$ DayTime | Value$ Day | SubAbility$ ETB
SVar:ETB:DB$ InternalEtbReplacement
T:Mode$ DayTimeChanges | Execute$ TrigReturn | TriggerZones$ Battlefield | TriggerDescription$ Whenever day becomes night or night becomes day, you may pay {1}{R}. If you do, return CARDNAME from your graveyard to the battlefield tapped.
T:Mode$ DayTimeChanges | Execute$ TrigReturn | TriggerZones$ Graveyard | TriggerDescription$ Whenever day becomes night or night becomes day, you may pay {1}{R}. If you do, return CARDNAME from your graveyard to the battlefield tapped.
SVar:TrigReturn:AB$ ChangeZone | Cost$ 1 R | Origin$ Graveyard | Destination$ Battlefield | Tapped$ True
DeckHas:Ability$Graveyard
Oracle:Flying\nIf it's neither day nor night, it becomes day as Sunstreak Phoenix enters the battlefield.\nWhenever day becomes night or night becomes day, you may pay {1}{R}. If you do, return Sunstreak Phoenix from your graveyard to the battlefield tapped.

View File

@@ -27,8 +27,6 @@ import forge.game.GameEntity;
import forge.game.GameEntityView;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardView;
import forge.game.combat.AttackingBand;
import forge.game.combat.Combat;
@@ -128,7 +126,7 @@ public class InputAttack extends InputSyncronizedBase {
final List<Player> defenders = playerAttacks.getOpponents();
final Set<CardView> refreshCards = Sets.newHashSet();
for (final Card c : CardLists.filter(playerAttacks.getCardsIn(ZoneType.Battlefield), Presets.CREATURES)) {
for (final Card c : playerAttacks.getCreaturesInPlay()) {
if (combat.isAttacking(c)) {
continue;
}

View File

@@ -20,8 +20,6 @@ package forge.gamemodes.match.input;
import java.util.List;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardView;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
@@ -60,7 +58,7 @@ public class InputBlock extends InputSyncronizedBase {
//auto-select first attacker to declare blockers for
for (final Card attacker : combat.getAttackers()) {
for (final Card c : CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), Presets.CREATURES)) {
for (final Card c : defender.getCreaturesInPlay()) {
if (CombatUtil.canBlock(attacker, c, combat)) {
FThreads.invokeInEdtNowOrLater(new Runnable() { //must set current attacker on EDT
@Override