Merge branch 'master' into historicformats

This commit is contained in:
maustin
2018-04-15 09:10:12 +01:00
13 changed files with 143 additions and 36 deletions

View File

@@ -421,7 +421,7 @@ public class AiAttackController {
CardCollectionView oppBattlefield = c.getController().getCardsIn(ZoneType.Battlefield);
if (c.getName().equals("Heart of Kiran")) {
if (!CardLists.filter(oppBattlefield, CardPredicates.Presets.PLANEWALKERS).isEmpty()) {
if (!CardLists.filter(oppBattlefield, CardPredicates.Presets.PLANESWALKERS).isEmpty()) {
// can be activated by removing a loyalty counter instead of tapping a creature
continue;
}

View File

@@ -96,14 +96,28 @@ public class ComputerUtilCard {
* @return best Planeswalker
*/
public static Card getBestPlaneswalkerAI(final List<Card> list) {
List<Card> all = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS);
if (all.size() == 0) {
List<Card> all = CardLists.filter(list, CardPredicates.Presets.PLANESWALKERS);
if (all.isEmpty()) {
return null;
}
// no AI logic, just return most expensive
return Aggregates.itemWithMax(all, CardPredicates.Accessors.fnGetCmc);
}
/**
* Returns the worst Planeswalker from a given list
* @param list list of cards to evaluate
* @return best Planeswalker
*/
public static Card getWorstPlaneswalkerAI(final List<Card> list) {
List<Card> all = CardLists.filter(list, CardPredicates.Presets.PLANESWALKERS);
if (all.isEmpty()) {
return null;
}
// no AI logic, just return least expensive
return Aggregates.itemWithMin(all, CardPredicates.Accessors.fnGetCmc);
}
// The AI doesn't really pick the best enchantment, just the most expensive.
/**
* <p>

View File

@@ -112,7 +112,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
// with one touch
CardCollection planeswalkerList = CardLists.filter(
CardLists.filterControlledBy(countersList, ai.getOpponents()),
CardPredicates.Presets.PLANEWALKERS,
CardPredicates.Presets.PLANESWALKERS,
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
if (!planeswalkerList.isEmpty()) {

View File

@@ -121,7 +121,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS,
CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANESWALKERS,
CardPredicates.hasCounter(CounterType.LOYALTY, 5));
if (!planeswalkerList.isEmpty()) {
@@ -169,7 +169,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
CardCollection planeswalkerList = CardLists.filter(list,
Predicates.and(CardPredicates.Presets.PLANEWALKERS, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
Predicates.and(CardPredicates.Presets.PLANESWALKERS, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
if (!planeswalkerList.isEmpty()) {

View File

@@ -44,6 +44,11 @@ public abstract class DamageAiBase extends SpellAbilityAi {
}
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) {
// TODO: once the "planeswalker redirection" rule is removed completely, just remove this code and
// remove the "burn Planeswalkers" code in the called method.
return shouldTgtP(comp, sa, d, noPrevention, false);
}
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention, final boolean noPlaneswalkerRedirection) {
int restDamage = d;
final Game game = comp.getGame();
Player enemy = ComputerUtil.getOpponentFor(comp);
@@ -78,7 +83,9 @@ public abstract class DamageAiBase extends SpellAbilityAi {
}
// burn Planeswalkers
if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS)) {
// TODO: Must be removed completely when the "planeswalker redirection" rule is removed.
if (!noPlaneswalkerRedirection
&& Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS)) {
return true;
}

View File

@@ -271,25 +271,7 @@ public class DamageDealAi extends DamageAiBase {
final Player activator = sa.getActivatingPlayer();
final Card source = sa.getHostCard();
final Game game = source.getGame();
List<Card> hPlay = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), activator, source, sa);
if (activator.equals(ai)) {
hPlay = CardLists.filterControlledBy(hPlay, pl);
}
final List<GameObject> objects = Lists.newArrayList(sa.getTargets().getTargets());
if (sa.hasParam("TargetUnique")) {
objects.addAll(sa.getUniqueTargets());
}
for (final Object o : objects) {
if (o instanceof Card) {
final Card c = (Card) o;
if (hPlay.contains(c)) {
hPlay.remove(c);
}
}
}
hPlay = CardLists.getTargetableCards(hPlay, sa);
List<Card> hPlay = CardLists.filter(getTargetableCards(ai, sa, pl, tgt, activator, source, game), CardPredicates.Presets.PLANESWALKERS);
List<Card> killables = CardLists.filter(hPlay, new Predicate<Card>() {
@Override
@@ -338,6 +320,77 @@ public class DamageDealAi extends DamageAiBase {
return null;
}
/**
* <p>
* dealDamageChooseTgtPW.
* </p>
*
* @param d
* a int.
* @param noPrevention
* a boolean.
* @param pl
* a {@link forge.game.player.Player} object.
* @param mandatory
* a boolean.
* @return a {@link forge.game.card.Card} object.
*/
private Card dealDamageChooseTgtPW(final Player ai, final SpellAbility sa, final int d, final boolean noPrevention,
final Player pl, final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Player activator = sa.getActivatingPlayer();
final Card source = sa.getHostCard();
final Game game = source.getGame();
List<Card> hPlay = CardLists.filter(getTargetableCards(ai, sa, pl, tgt, activator, source, game), CardPredicates.Presets.PLANESWALKERS);
List<Card> killables = CardLists.filter(hPlay, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getSVar("Targeting").equals("Dies")
|| (ComputerUtilCombat.getEnoughDamageToKill(c, d, source, false, noPrevention) <= d)
&& !ComputerUtil.canRegenerate(ai, c)
&& !(c.getSVar("SacMe").length() > 0);
}
});
// Filter AI-specific targets if provided
killables = ComputerUtil.filterAITgts(sa, ai, new CardCollection(killables), true);
if (pl.isOpponentOf(ai) && activator.equals(ai) && !killables.isEmpty()) {
return ComputerUtilCard.getBestPlaneswalkerAI(killables);
}
if (!hPlay.isEmpty() && pl.isOpponentOf(ai) && activator.equals(ai)) {
return ComputerUtilCard.getBestPlaneswalkerAI(hPlay);
}
return null;
}
private List<Card> getTargetableCards(Player ai, SpellAbility sa, Player pl, TargetRestrictions tgt, Player activator, Card source, Game game) {
List<Card> hPlay = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), activator, source, sa);
if (activator.equals(ai)) {
hPlay = CardLists.filterControlledBy(hPlay, pl);
}
final List<GameObject> objects = Lists.newArrayList(sa.getTargets().getTargets());
if (sa.hasParam("TargetUnique")) {
objects.addAll(sa.getUniqueTargets());
}
for (final Object o : objects) {
if (o instanceof Card) {
final Card c = (Card) o;
if (hPlay.contains(c)) {
hPlay.remove(c);
}
}
}
hPlay = CardLists.getTargetableCards(hPlay, sa);
return hPlay;
}
/**
* <p>
* damageTargetAI.
@@ -475,7 +528,27 @@ public class DamageDealAi extends DamageAiBase {
return targetingPlayer.getController().chooseTargetsFor(sa);
}
if (tgt.canTgtPlaneswalker()) {
// We can damage planeswalkers with this, consider targeting.
Card c = this.dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, enemy, false);
if (c != null && !this.shouldTgtP(ai, sa, dmg, noPrevention, true)) {
tcs.add(c);
if (divided) {
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
if (assignedDamage <= dmg) {
tgt.addDividedAllocation(c, assignedDamage);
}
dmg = dmg - assignedDamage;
if (dmg <= 0) {
break;
}
}
continue;
}
}
if (tgt.canTgtCreatureAndPlayer()) {
Card c = null;
if (this.shouldTgtP(ai, sa, dmg, noPrevention)) {
tcs.add(enemy);
@@ -489,7 +562,8 @@ public class DamageDealAi extends DamageAiBase {
dmg = dmg * sa.getTargets().getNumTargeted() / (sa.getTargets().getNumTargeted() +1);
}
final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, enemy, false);
// look for creature targets; currently also catches planeswalkers that can be killed immediately
c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, enemy, false);
if (c != null) {
//option to hold removal instead only applies for single targeted removal
if (sa.isSpell() && !divided && !immediately && tgt.getMaxTargets(sa.getHostCard(), sa) == 1) {
@@ -687,7 +761,19 @@ public class DamageDealAi extends DamageAiBase {
final Player opp = ComputerUtil.getOpponentFor(ai);
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
// TODO: Consider targeting the planeswalker
if (tgt.canTgtPlaneswalker()) {
final Card c = this.dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, ai, mandatory);
if (c != null) {
sa.getTargets().add(c);
if (divided) {
tgt.addDividedAllocation(c, dmg);
break;
}
continue;
}
}
// TODO: This currently also catches planeswalkers that can be killed (still necessary? Or can be removed?)
if (tgt.canTgtCreature()) {
final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, ai, mandatory);
if (c != null) {

View File

@@ -180,7 +180,7 @@ public class DrawAi extends SpellAbilityAi {
int numHand = ai.getCardsIn(ZoneType.Hand).size();
if ("Jace, Vryn's Prodigy".equals(sourceName) && ai.getCardsIn(ZoneType.Graveyard).size() > 3) {
return CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS,
return CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS,
CardPredicates.isType("Jace")).size() <= 0;
}
if (source.isSpell() && ai.getCardsIn(ZoneType.Hand).contains(source)) {

View File

@@ -69,7 +69,7 @@ public class PermanentAi extends SpellAbilityAi {
/* -- not used anymore after Ixalan (Planeswalkers are now legendary, not unique by subtype) --
if (card.isPlaneswalker()) {
CardCollection list = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield),
CardPredicates.Presets.PLANEWALKERS);
CardPredicates.Presets.PLANESWALKERS);
for (String type : card.getType().getSubtypes()) { // determine
// planewalker
// subtype

View File

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

View File

@@ -587,7 +587,7 @@ public final class CardPredicates {
return c.isLand() && c.isSnow();
}
};
public static final Predicate<Card> PLANEWALKERS = new Predicate<Card>() {
public static final Predicate<Card> PLANESWALKERS = new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.isPlaneswalker();

View File

@@ -77,7 +77,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.PLANEWALKERS)) {
for (Card pw : CardLists.filter(c.getController().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS)) {
// 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.<GameEntity>newArrayList());

View File

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

View File

@@ -506,8 +506,8 @@ public class CardThemedDeckBuilder extends DeckGeneratorBase {
&&!card.getRules().getManaCost().isPureGeneric()
&& colors.containsAllColorsFrom(card.getRules().getColorIdentity().getColor())
&& !deckList.contains(card)
&& (keyCard!=null && !keyCard.equals(card))
&& (secondKeyCard!=null && !secondKeyCard.equals(card))
&& (keyCard == null || !keyCard.equals(card))
&& (secondKeyCard==null || !secondKeyCard.equals(card))
&&!card.getRules().getAiHints().getRemAIDecks()
&&!card.getRules().getAiHints().getRemRandomDecks()
&&!card.getRules().getMainPart().getType().isLand();