mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 04:08:01 +00:00
Merge branch 'master' into historicformats
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user