mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 20:28:00 +00:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -91,7 +91,7 @@ public class AiAttackController {
|
|||||||
|
|
||||||
public AiAttackController(final Player ai, boolean nextTurn) {
|
public AiAttackController(final Player ai, boolean nextTurn) {
|
||||||
this.ai = ai;
|
this.ai = ai;
|
||||||
this.defendingOpponent = choosePreferredDefenderPlayer();
|
this.defendingOpponent = choosePreferredDefenderPlayer(ai);
|
||||||
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
||||||
this.myList = ai.getCreaturesInPlay();
|
this.myList = ai.getCreaturesInPlay();
|
||||||
this.attackers = new ArrayList<>();
|
this.attackers = new ArrayList<>();
|
||||||
@@ -107,7 +107,7 @@ public class AiAttackController {
|
|||||||
|
|
||||||
public AiAttackController(final Player ai, Card attacker) {
|
public AiAttackController(final Player ai, Card attacker) {
|
||||||
this.ai = ai;
|
this.ai = ai;
|
||||||
this.defendingOpponent = choosePreferredDefenderPlayer();
|
this.defendingOpponent = choosePreferredDefenderPlayer(ai);
|
||||||
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
||||||
this.myList = ai.getCreaturesInPlay();
|
this.myList = ai.getCreaturesInPlay();
|
||||||
this.attackers = new ArrayList<>();
|
this.attackers = new ArrayList<>();
|
||||||
@@ -156,13 +156,12 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Choose opponent for AI to attack here. Expand as necessary. */
|
/** Choose opponent for AI to attack here. Expand as necessary. */
|
||||||
private Player choosePreferredDefenderPlayer() {
|
public static Player choosePreferredDefenderPlayer(Player ai) {
|
||||||
Player defender = ai.getWeakestOpponent(); //Gets opponent with the least life
|
Player defender = ai.getWeakestOpponent(); //Concentrate on opponent within easy kill range
|
||||||
|
|
||||||
if (defender.getLife() < 8) { //Concentrate on opponent within easy kill range
|
if (defender.getLife() > 8) { //Otherwise choose a random opponent to ensure no ganging up on players
|
||||||
return defender;
|
// TODO should we cache the random for each turn? some functions like shouldPumpCard base their decisions on the assumption who will be attacked
|
||||||
} else { //Otherwise choose a random opponent to ensure no ganging up on players
|
return ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
|
||||||
defender = ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
|
|
||||||
}
|
}
|
||||||
return defender;
|
return defender;
|
||||||
}
|
}
|
||||||
@@ -624,7 +623,7 @@ public class AiAttackController {
|
|||||||
int totalCombatDamage = ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, opp) + trampleDamage;
|
int totalCombatDamage = ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, opp) + trampleDamage;
|
||||||
int totalPoisonDamage = ComputerUtilCombat.sumPoisonIfUnblocked(unblockedAttackers, opp);
|
int totalPoisonDamage = ComputerUtilCombat.sumPoisonIfUnblocked(unblockedAttackers, opp);
|
||||||
|
|
||||||
if (totalCombatDamage + ComputerUtil.possibleNonCombatDamage(ai) >= opp.getLife()
|
if (totalCombatDamage + ComputerUtil.possibleNonCombatDamage(ai, opp) >= opp.getLife()
|
||||||
&& !((opp.cantLoseForZeroOrLessLife() || ai.cantWin()) && opp.getLife() < 1)) {
|
&& !((opp.cantLoseForZeroOrLessLife() || ai.cantWin()) && opp.getLife() < 1)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -919,7 +918,7 @@ public class AiAttackController {
|
|||||||
// find the potential damage ratio the AI can cause
|
// find the potential damage ratio the AI can cause
|
||||||
double humanLifeToDamageRatio = 1000000;
|
double humanLifeToDamageRatio = 1000000;
|
||||||
if (candidateUnblockedDamage > 0) {
|
if (candidateUnblockedDamage > 0) {
|
||||||
humanLifeToDamageRatio = (double) (opp.getLife() - ComputerUtil.possibleNonCombatDamage(ai)) / candidateUnblockedDamage;
|
humanLifeToDamageRatio = (double) (opp.getLife() - ComputerUtil.possibleNonCombatDamage(ai, opp)) / candidateUnblockedDamage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine if the ai outnumbers the player
|
// determine if the ai outnumbers the player
|
||||||
|
|||||||
@@ -1102,6 +1102,8 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CardCollection getCardsToDiscard(int min, final int max, final CardCollection validCards, final SpellAbility sa) {
|
public CardCollection getCardsToDiscard(int min, final int max, final CardCollection validCards, final SpellAbility sa) {
|
||||||
|
String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
if (validCards.size() < min) {
|
if (validCards.size() < min) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1111,11 +1113,16 @@ public class AiController {
|
|||||||
int count = 0;
|
int count = 0;
|
||||||
if (sa != null) {
|
if (sa != null) {
|
||||||
sourceCard = sa.getHostCard();
|
sourceCard = sa.getHostCard();
|
||||||
if ("Always".equals(sa.getParam("AILogic")) && !validCards.isEmpty()) {
|
if ("Always".equals(logic) && !validCards.isEmpty()) {
|
||||||
min = 1;
|
min = 1;
|
||||||
} else if ("VolrathsShapeshifter".equals(sa.getParam("AILogic"))) {
|
} else if (logic.startsWith("UnlessAtLife.")) {
|
||||||
|
int threshold = AbilityUtils.calculateAmount(sourceCard, logic.substring(logic.indexOf(".") + 1), sa);
|
||||||
|
if (player.getLife() <= threshold) {
|
||||||
|
min = 1;
|
||||||
|
}
|
||||||
|
} else if ("VolrathsShapeshifter".equals(logic)) {
|
||||||
return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa);
|
return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa);
|
||||||
} else if ("DiscardCMCX".equals(sa.getParam("AILogic"))) {
|
} else if ("DiscardCMCX".equals(logic)) {
|
||||||
final int cmc = sa.getXManaCostPaid();
|
final int cmc = sa.getXManaCostPaid();
|
||||||
CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc));
|
CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc));
|
||||||
if (discards.isEmpty()) {
|
if (discards.isEmpty()) {
|
||||||
|
|||||||
@@ -188,7 +188,6 @@ public class ComputerUtil {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
|
||||||
|
|
||||||
// Play higher costing spells first?
|
// Play higher costing spells first?
|
||||||
final Cost cost = sa.getPayCosts();
|
final Cost cost = sa.getPayCosts();
|
||||||
|
|
||||||
@@ -213,7 +212,8 @@ public class ComputerUtil {
|
|||||||
if (unless != null && !unless.endsWith(">")) {
|
if (unless != null && !unless.endsWith(">")) {
|
||||||
final int amount = AbilityUtils.calculateAmount(source, unless, sa);
|
final int amount = AbilityUtils.calculateAmount(source, unless, sa);
|
||||||
|
|
||||||
final int usableManaSources = ComputerUtilMana.getAvailableManaSources(ComputerUtil.getOpponentFor(ai), true).size();
|
// this is enough as long as the AI is only smart enough to target top of stack
|
||||||
|
final int usableManaSources = ComputerUtilMana.getAvailableManaSources(ComputerUtilAbility.getTopSpellAbilityOnStack(ai.getGame(), sa).getActivatingPlayer(), true).size();
|
||||||
|
|
||||||
// If the Unless isn't enough, this should be less likely to be used
|
// If the Unless isn't enough, this should be less likely to be used
|
||||||
if (amount > usableManaSources) {
|
if (amount > usableManaSources) {
|
||||||
@@ -1068,9 +1068,6 @@ public class ComputerUtil {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (card.isEquipment() && buffedcard.isCreature() && CombatUtil.canAttack(buffedcard, ComputerUtil.getOpponentFor(ai))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (card.isCreature()) {
|
if (card.isCreature()) {
|
||||||
if (buffedcard.hasKeyword(Keyword.SOULBOND) && !buffedcard.isPaired()) {
|
if (buffedcard.hasKeyword(Keyword.SOULBOND) && !buffedcard.isPaired()) {
|
||||||
return true;
|
return true;
|
||||||
@@ -1093,8 +1090,8 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
} // BuffedBy
|
} // BuffedBy
|
||||||
|
|
||||||
// get all cards the human controls with AntiBuffedBy
|
// there's a good chance AI will attack weak target
|
||||||
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
final CardCollectionView antibuffed = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
|
||||||
for (Card buffedcard : antibuffed) {
|
for (Card buffedcard : antibuffed) {
|
||||||
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
||||||
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
||||||
@@ -1142,27 +1139,16 @@ public class ComputerUtil {
|
|||||||
* @return true if it's OK to cast this Card for less than the max targets
|
* @return true if it's OK to cast this Card for less than the max targets
|
||||||
*/
|
*/
|
||||||
public static boolean shouldCastLessThanMax(final Player ai, final Card source) {
|
public static boolean shouldCastLessThanMax(final Player ai, final Card source) {
|
||||||
boolean ret = true;
|
if (source.getXManaCostPaid() > 0) {
|
||||||
if (source.getManaCost().countX() > 0) {
|
// If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by payment resources available.
|
||||||
// If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by mana available.
|
return true;
|
||||||
return ret;
|
}
|
||||||
} else {
|
if (aiLifeInDanger(ai, false, 0)) {
|
||||||
// Otherwise, if life is possibly in danger, then this is fine.
|
// Otherwise, if life is possibly in danger, then this is fine.
|
||||||
Combat combat = new Combat(ComputerUtil.getOpponentFor(ai));
|
return true;
|
||||||
CardCollectionView attackers = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
|
|
||||||
for (Card att : attackers) {
|
|
||||||
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
|
|
||||||
combat.addAttacker(att, ComputerUtil.getOpponentFor(att.getController()));
|
|
||||||
}
|
}
|
||||||
}
|
// do not play now.
|
||||||
AiBlockController aiBlock = new AiBlockController(ai);
|
return false;
|
||||||
aiBlock.assignBlockersForCombat(combat);
|
|
||||||
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
|
||||||
// Otherwise, return false. Do not play now.
|
|
||||||
ret = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1266,8 +1252,8 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all cards the human controls with AntiBuffedBy
|
// there's a good chance AI will attack weak target
|
||||||
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
final CardCollectionView antibuffed = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
|
||||||
for (Card buffedcard : antibuffed) {
|
for (Card buffedcard : antibuffed) {
|
||||||
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
||||||
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
||||||
@@ -1463,7 +1449,7 @@ public class ComputerUtil {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int possibleNonCombatDamage(Player ai) {
|
public static int possibleNonCombatDamage(Player ai, Player enemy) {
|
||||||
int damage = 0;
|
int damage = 0;
|
||||||
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
||||||
all.addAll(ai.getCardsActivableInExternalZones(true));
|
all.addAll(ai.getCardsActivableInExternalZones(true));
|
||||||
@@ -1483,7 +1469,6 @@ public class ComputerUtil {
|
|||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final Player enemy = ComputerUtil.getOpponentFor(ai);
|
|
||||||
if (!sa.canTarget(enemy)) {
|
if (!sa.canTarget(enemy)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -2346,7 +2331,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (logic.equals("ChosenLandwalk")) {
|
else if (logic.equals("ChosenLandwalk")) {
|
||||||
for (Card c : ComputerUtil.getOpponentFor(ai).getLandsInPlay()) {
|
for (Card c : AiAttackController.choosePreferredDefenderPlayer(ai).getLandsInPlay()) {
|
||||||
for (String t : c.getType()) {
|
for (String t : c.getType()) {
|
||||||
if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) {
|
if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) {
|
||||||
chosen = t;
|
chosen = t;
|
||||||
@@ -2364,7 +2349,7 @@ public class ComputerUtil {
|
|||||||
else if (kindOfType.equals("Land")) {
|
else if (kindOfType.equals("Land")) {
|
||||||
if (logic != null) {
|
if (logic != null) {
|
||||||
if (logic.equals("ChosenLandwalk")) {
|
if (logic.equals("ChosenLandwalk")) {
|
||||||
for (Card c : ComputerUtil.getOpponentFor(ai).getLandsInPlay()) {
|
for (Card c : AiAttackController.choosePreferredDefenderPlayer(ai).getLandsInPlay()) {
|
||||||
for (String t : c.getType().getLandTypes()) {
|
for (String t : c.getType().getLandTypes()) {
|
||||||
if (!invalidTypes.contains(t)) {
|
if (!invalidTypes.contains(t)) {
|
||||||
chosen = t;
|
chosen = t;
|
||||||
@@ -2399,15 +2384,18 @@ public class ComputerUtil {
|
|||||||
case "Torture":
|
case "Torture":
|
||||||
return "Torture";
|
return "Torture";
|
||||||
case "GraceOrCondemnation":
|
case "GraceOrCondemnation":
|
||||||
return ai.getCreaturesInPlay().size() > ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size() ? "Grace"
|
List<ZoneType> graceZones = new ArrayList<ZoneType>();
|
||||||
: "Condemnation";
|
graceZones.add(ZoneType.Battlefield);
|
||||||
|
graceZones.add(ZoneType.Graveyard);
|
||||||
|
CardCollection graceCreatures = CardLists.getType(sa.getHostCard().getGame().getCardsIn(graceZones), "Creature");
|
||||||
|
int humanGrace = CardLists.filterControlledBy(graceCreatures, ai.getOpponents()).size();
|
||||||
|
int aiGrace = CardLists.filterControlledBy(graceCreatures, ai).size();
|
||||||
|
return aiGrace > humanGrace ? "Grace" : "Condemnation";
|
||||||
case "CarnageOrHomage":
|
case "CarnageOrHomage":
|
||||||
CardCollection cardsInPlay = CardLists
|
CardCollection cardsInPlay = CardLists.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
|
||||||
.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
|
|
||||||
CardCollection humanlist = CardLists.filterControlledBy(cardsInPlay, ai.getOpponents());
|
CardCollection humanlist = CardLists.filterControlledBy(cardsInPlay, ai.getOpponents());
|
||||||
CardCollection computerlist = CardLists.filterControlledBy(cardsInPlay, ai);
|
CardCollection computerlist = ai.getCreaturesInPlay();
|
||||||
return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard
|
return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
|
||||||
.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
|
|
||||||
case "Judgment":
|
case "Judgment":
|
||||||
if (votes.isEmpty()) {
|
if (votes.isEmpty()) {
|
||||||
CardCollection list = new CardCollection();
|
CardCollection list = new CardCollection();
|
||||||
@@ -2934,23 +2922,6 @@ public class ComputerUtil {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public static final Player getOpponentFor(final Player player) {
|
|
||||||
// This method is deprecated and currently functions as a synonym for player.getWeakestOpponent
|
|
||||||
// until it can be replaced everywhere in the code.
|
|
||||||
|
|
||||||
// Consider replacing calls to this method either with a multiplayer-friendly determination of
|
|
||||||
// opponent that contextually makes the most sense, or with a direct call to player.getWeakestOpponent
|
|
||||||
// where that is applicable and makes sense from the point of view of multiplayer AI logic.
|
|
||||||
Player opponent = player.getWeakestOpponent();
|
|
||||||
if (opponent != null) {
|
|
||||||
return opponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalStateException("No opponents left ingame for " + player);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int countUsefulCreatures(Player p) {
|
public static int countUsefulCreatures(Player p) {
|
||||||
CardCollection creats = p.getCreaturesInPlay();
|
CardCollection creats = p.getCreaturesInPlay();
|
||||||
int count = 0;
|
int count = 0;
|
||||||
@@ -3033,7 +3004,7 @@ public class ComputerUtil {
|
|||||||
// call this to determine if it's safe to use a life payment spell
|
// call this to determine if it's safe to use a life payment spell
|
||||||
// or trigger "emergency" strategies such as holding mana for Spike Weaver of Counterspell.
|
// or trigger "emergency" strategies such as holding mana for Spike Weaver of Counterspell.
|
||||||
public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) {
|
public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) {
|
||||||
Player opponent = ComputerUtil.getOpponentFor(ai);
|
for (Player opponent: ai.getOpponents()) {
|
||||||
// test whether the human can kill the ai next turn
|
// test whether the human can kill the ai next turn
|
||||||
Combat combat = new Combat(opponent);
|
Combat combat = new Combat(opponent);
|
||||||
boolean containsAttacker = false;
|
boolean containsAttacker = false;
|
||||||
@@ -3059,6 +3030,7 @@ public class ComputerUtil {
|
|||||||
if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) {
|
if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -550,7 +550,7 @@ public class ComputerUtilCard {
|
|||||||
*/
|
*/
|
||||||
public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) {
|
public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) {
|
||||||
AiBlockController aiBlk = new AiBlockController(ai);
|
AiBlockController aiBlk = new AiBlockController(ai);
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
Combat combat = new Combat(opp);
|
Combat combat = new Combat(opp);
|
||||||
//Use actual attackers if available, else consider all possible attackers
|
//Use actual attackers if available, else consider all possible attackers
|
||||||
Combat currentCombat = ai.getGame().getCombat();
|
Combat currentCombat = ai.getGame().getCombat();
|
||||||
|
|||||||
@@ -97,34 +97,39 @@ public class ComputerUtilCombat {
|
|||||||
* canAttackNextTurn.
|
* canAttackNextTurn.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param atacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defender
|
* @param defender
|
||||||
* the defending {@link GameEntity}.
|
* the defending {@link GameEntity}.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
public static boolean canAttackNextTurn(final Card atacker, final GameEntity defender) {
|
public static boolean canAttackNextTurn(final Card attacker, final GameEntity defender) {
|
||||||
if (!atacker.isCreature()) {
|
if (!attacker.isCreature()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!CombatUtil.canAttackNextTurn(atacker, defender)) {
|
if (!CombatUtil.canAttackNextTurn(attacker, defender)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final KeywordInterface inst : atacker.getKeywords()) {
|
for (final KeywordInterface inst : attacker.getKeywords()) {
|
||||||
final String keyword = inst.getOriginal();
|
final String keyword = inst.getOriginal();
|
||||||
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
|
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
|
||||||
final String defined = keyword.split(":")[1];
|
final String defined = keyword.split(":")[1];
|
||||||
final Player player = AbilityUtils.getDefinedPlayers(atacker, defined, null).get(0);
|
final Player player = AbilityUtils.getDefinedPlayers(attacker, defined, null).get(0);
|
||||||
if (!defender.equals(player)) {
|
if (!defender.equals(player)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO this should be a factor but needs some alignment with AttachAi
|
||||||
|
//boolean leavesPlay = !ComputerUtilCard.hasActiveUndyingOrPersist(attacker)
|
||||||
|
// && ((attacker.hasKeyword(Keyword.VANISHING) && attacker.getCounters(CounterEnumType.TIME) == 1)
|
||||||
|
// || (attacker.hasKeyword(Keyword.FADING) && attacker.getCounters(CounterEnumType.FADE) == 0)
|
||||||
|
// || attacker.hasSVar("EndOfTurnLeavePlay"));
|
||||||
// The creature won't untap next turn
|
// The creature won't untap next turn
|
||||||
return !atacker.isTapped() || Untap.canUntap(atacker);
|
return !attacker.isTapped() || Untap.canUntap(attacker);
|
||||||
} // canAttackNextTurn(Card, GameEntity)
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.BidLife, BidLifeAi.class)
|
.put(ApiType.BidLife, BidLifeAi.class)
|
||||||
.put(ApiType.Bond, BondAi.class)
|
.put(ApiType.Bond, BondAi.class)
|
||||||
.put(ApiType.Branch, AlwaysPlayAi.class)
|
.put(ApiType.Branch, AlwaysPlayAi.class)
|
||||||
|
.put(ApiType.Camouflage, ChooseCardAi.class)
|
||||||
.put(ApiType.ChangeCombatants, ChangeCombatantsAi.class)
|
.put(ApiType.ChangeCombatants, ChangeCombatantsAi.class)
|
||||||
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
||||||
.put(ApiType.ChangeX, AlwaysPlayAi.class)
|
.put(ApiType.ChangeX, AlwaysPlayAi.class)
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
|||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = ai.getWeakestOpponent();
|
||||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
|
||||||
|
|
||||||
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
|
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
@@ -40,6 +39,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
return randomReturn;
|
return randomReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +56,8 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
|||||||
} else {
|
} else {
|
||||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
return defined.contains(opp);
|
// if at least two players are returned we can affect another opponent
|
||||||
|
return defined.size() > 1 || defined.contains(opp);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -13,18 +13,21 @@ public class BalanceAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
String logic = sa.getParam("AILogic");
|
String logic = sa.getParam("AILogic");
|
||||||
|
|
||||||
int diff = 0;
|
int diff = 0;
|
||||||
// TODO Add support for multiplayer logic
|
Player opp = aiPlayer.getWeakestOpponent();
|
||||||
final Player opp = aiPlayer.getWeakestOpponent();
|
|
||||||
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
|
|
||||||
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
||||||
|
for (Player min : aiPlayer.getOpponents()) {
|
||||||
|
if (min.getCardsIn(ZoneType.Battlefield).size() < opp.getCardsIn(ZoneType.Battlefield).size()) {
|
||||||
|
opp = min;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
|
||||||
|
|
||||||
if ("BalanceCreaturesAndLands".equals(logic)) {
|
if ("BalanceCreaturesAndLands".equals(logic)) {
|
||||||
// Copied over from hardcoded Balance. We should be checking value of the lands/creatures not just counting
|
// TODO Copied over from hardcoded Balance. We should be checking value of the lands/creatures for each opponent, not just counting
|
||||||
diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
|
diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
|
||||||
CardLists.filter(compPerms, CardPredicates.Presets.LANDS).size();
|
CardLists.filter(compPerms, CardPredicates.Presets.LANDS).size();
|
||||||
diff += 1.5 * ( CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() -
|
diff += 1.5 * (CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() -
|
||||||
CardLists.filter(compPerms, CardPredicates.Presets.CREATURES).size());
|
CardLists.filter(compPerms, CardPredicates.Presets.CREATURES).size());
|
||||||
}
|
}
|
||||||
else if ("BalancePermanents".equals(logic)) {
|
else if ("BalancePermanents".equals(logic)) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -24,7 +25,7 @@ public class BidLifeAi extends SpellAbilityAi {
|
|||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (tgt.canTgtCreature()) {
|
if (tgt.canTgtCreature()) {
|
||||||
List<Card> list = CardLists.getTargetableCards(aiPlayer.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), sa);
|
List<Card> list = CardLists.getTargetableCards(AiAttackController.choosePreferredDefenderPlayer(aiPlayer).getCardsIn(ZoneType.Battlefield), sa);
|
||||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import com.google.common.collect.Iterables;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.AiBlockController;
|
import forge.ai.AiBlockController;
|
||||||
import forge.ai.AiCardMemory;
|
import forge.ai.AiCardMemory;
|
||||||
import forge.ai.AiController;
|
import forge.ai.AiController;
|
||||||
@@ -263,7 +264,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
ZoneType origin = null;
|
ZoneType origin = null;
|
||||||
final Player opponent = ai.getWeakestOpponent();
|
final Player opponent = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||||
|
|
||||||
if (sa.hasParam("Origin")) {
|
if (sa.hasParam("Origin")) {
|
||||||
@@ -471,7 +472,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
// if putting cards from hand to library and parent is drawing cards
|
// if putting cards from hand to library and parent is drawing cards
|
||||||
// make sure this will actually do something:
|
// make sure this will actually do something:
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Player opp = aiPlayer.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||||
if (tgt != null && tgt.canTgtPlayer()) {
|
if (tgt != null && tgt.canTgtPlayer()) {
|
||||||
boolean isCurse = sa.isCurse();
|
boolean isCurse = sa.isCurse();
|
||||||
if (isCurse && sa.canTarget(opp)) {
|
if (isCurse && sa.canTarget(opp)) {
|
||||||
@@ -530,7 +531,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
Iterable<Player> pDefined;
|
Iterable<Player> pDefined;
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if ((tgt != null) && tgt.canTgtPlayer()) {
|
if ((tgt != null) && tgt.canTgtPlayer()) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
@@ -892,7 +893,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
|
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
|
||||||
sa.setXManaCostPaid(xPay);
|
sa.setXManaCostPaid(xPay);
|
||||||
// TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
|
|
||||||
}
|
}
|
||||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
|
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
|
||||||
|
|
||||||
@@ -913,9 +913,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
if (sa.isSpell()) {
|
if (sa.isSpell()) {
|
||||||
list.remove(source); // spells can't target their own source, because it's actually in the stack zone
|
list.remove(source); // spells can't target their own source, because it's actually in the stack zone
|
||||||
}
|
}
|
||||||
//System.out.println("isPreferredTarget " + list);
|
|
||||||
if (sa.hasParam("AttachedTo")) {
|
if (sa.hasParam("AttachedTo")) {
|
||||||
//System.out.println("isPreferredTarget att " + list);
|
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
@@ -927,7 +925,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
//System.out.println("isPreferredTarget ok " + list);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.size() < sa.getMinTargets()) {
|
if (list.size() < sa.getMinTargets()) {
|
||||||
@@ -1482,9 +1479,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if ("DeathgorgeScavenger".equals(logic)) {
|
if ("DeathgorgeScavenger".equals(logic)) {
|
||||||
return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa);
|
return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa);
|
||||||
} else if ("ExtraplanarLens".equals(logic)) {
|
}
|
||||||
|
if ("ExtraplanarLens".equals(logic)) {
|
||||||
return SpecialCardAi.ExtraplanarLens.consider(ai, sa);
|
return SpecialCardAi.ExtraplanarLens.consider(ai, sa);
|
||||||
} else if ("ExileCombatThreat".equals(logic)) {
|
}
|
||||||
|
if ("ExileCombatThreat".equals(logic)) {
|
||||||
return doExileCombatThreatLogic(ai, sa);
|
return doExileCombatThreatLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1984,11 +1983,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toPay == 0) {
|
if (toPay == 0 || toPay <= usableManaSources) {
|
||||||
canBeSaved.add(potentialTgt);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toPay <= usableManaSources) {
|
|
||||||
canBeSaved.add(potentialTgt);
|
canBeSaved.add(potentialTgt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.google.common.base.Predicates;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCombat;
|
import forge.ai.ComputerUtilCombat;
|
||||||
@@ -114,7 +115,7 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
return !CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host, sa).isEmpty();
|
return !CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host, sa).isEmpty();
|
||||||
} else if (aiLogic.equals("Duneblast")) {
|
} else if (aiLogic.equals("Duneblast")) {
|
||||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||||
CardCollection oppCreatures = ai.getWeakestOpponent().getCreaturesInPlay();
|
CardCollection oppCreatures = AiAttackController.choosePreferredDefenderPlayer(ai).getCreaturesInPlay();
|
||||||
aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
|
aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
|
||||||
oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);
|
oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.google.common.collect.Iterables;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpecialCardAi;
|
import forge.ai.SpecialCardAi;
|
||||||
@@ -44,7 +45,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
|||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (tgt.canOnlyTgtOpponent()) {
|
if (tgt.canOnlyTgtOpponent()) {
|
||||||
sa.getTargets().add(ai.getWeakestOpponent());
|
sa.getTargets().add(AiAttackController.choosePreferredDefenderPlayer(ai));
|
||||||
} else {
|
} else {
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -16,7 +17,7 @@ public class ChooseEvenOddAi extends SpellAbilityAi {
|
|||||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
Player opp = aiPlayer.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -16,7 +17,7 @@ public class ChooseNumberAi extends SpellAbilityAi {
|
|||||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
Player opp = aiPlayer.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.google.common.base.Predicates;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCombat;
|
import forge.ai.ComputerUtilCombat;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
@@ -54,7 +55,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
|||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package forge.ai.ability;
|
|||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
@@ -29,7 +30,7 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|
||||||
CardCollection list =
|
CardCollection list =
|
||||||
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
CardLists.getValidCards(AiAttackController.choosePreferredDefenderPlayer(ai).getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||||
// AI won't try to grab cards that are filtered out of AI decks on
|
// AI won't try to grab cards that are filtered out of AI decks on
|
||||||
// purpose
|
// purpose
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.google.common.base.Predicate;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
@@ -26,9 +27,6 @@ import forge.game.spellability.TargetRestrictions;
|
|||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
public class DebuffAi extends SpellAbilityAi {
|
public class DebuffAi extends SpellAbilityAi {
|
||||||
// *************************************************************************
|
|
||||||
// ***************************** Debuff ************************************
|
|
||||||
// *************************************************************************
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
||||||
@@ -140,7 +138,6 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||||
Card t = null;
|
Card t = null;
|
||||||
// boolean goodt = false;
|
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if ((sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().size() == 0)) {
|
if ((sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().size() == 0)) {
|
||||||
@@ -176,19 +173,18 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
* @return a CardCollection.
|
* @return a CardCollection.
|
||||||
*/
|
*/
|
||||||
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
|
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
|
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
|
||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
return c.hasAnyKeyword(kws); // don't add duplicate negative
|
return c.hasAnyKeyword(kws); // don't add duplicate negative keywords
|
||||||
// keywords
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
} // getCurseCreatures()
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -216,7 +212,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
list.remove(c);
|
list.remove(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
final CardCollection pref = CardLists.filterControlledBy(list, ai.getWeakestOpponent());
|
final CardCollection pref = CardLists.filterControlledBy(list, ai.getOpponents());
|
||||||
final CardCollection forced = CardLists.filterControlledBy(list, ai);
|
final CardCollection forced = CardLists.filterControlledBy(list, ai);
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
@@ -242,8 +238,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - if forced targeting, just pick something without the given
|
// TODO - if forced targeting, just pick something without the given keyword
|
||||||
// keyword
|
|
||||||
Card c;
|
Card c;
|
||||||
if (CardLists.getNotType(forced, "Creature").size() == 0) {
|
if (CardLists.getNotType(forced, "Creature").size() == 0) {
|
||||||
c = ComputerUtilCard.getWorstCreatureAI(forced);
|
c = ComputerUtilCard.getWorstCreatureAI(forced);
|
||||||
|
|||||||
@@ -70,12 +70,12 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
return doMassRemovalLogic(ai, sa);
|
return doMassRemovalLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
|
public static boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
Player opponent = ai.getWeakestOpponent(); // TODO: how should this AI logic work for multiplayer and getOpponents()?
|
|
||||||
|
|
||||||
final int CREATURE_EVAL_THRESHOLD = 200;
|
// if we hit the whole board, the other opponents who are not the reason to cast this probably still suffer a bit too
|
||||||
|
final int CREATURE_EVAL_THRESHOLD = 200 / (!sa.usesTargeting() ? ai.getOpponents().size() : 1);
|
||||||
|
|
||||||
if (logic.equals("Always")) {
|
if (logic.equals("Always")) {
|
||||||
return true; // e.g. Tetzimoc, Primal Death, where we want to cast the permanent even if the removal trigger does nothing
|
return true; // e.g. Tetzimoc, Primal Death, where we want to cast the permanent even if the removal trigger does nothing
|
||||||
@@ -93,10 +93,10 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
valid = valid.replace("X", Integer.toString(xPay));
|
valid = valid.replace("X", Integer.toString(xPay));
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield),
|
// TODO should probably sort results when targeted to use on biggest threat instead of first match
|
||||||
valid.split(","), source.getController(), source, sa);
|
for (Player opponent: ai.getOpponents()) {
|
||||||
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
|
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
||||||
source.getController(), source, sa);
|
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
||||||
|
|
||||||
opplist = CardLists.filter(opplist, predicate);
|
opplist = CardLists.filter(opplist, predicate);
|
||||||
ailist = CardLists.filter(ailist, predicate);
|
ailist = CardLists.filter(ailist, predicate);
|
||||||
@@ -135,8 +135,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if only creatures are affected evaluate both lists and pass only if
|
// if only creatures are affected evaluate both lists and pass only if human creatures are more valuable
|
||||||
// human creatures are more valuable
|
|
||||||
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
|
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
|
||||||
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
|
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -167,7 +166,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
} // only lands involved
|
} // only lands involved
|
||||||
else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
|
else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
|
||||||
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds") && !opplist.isEmpty()) {
|
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
|
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
|
||||||
@@ -188,4 +187,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ import java.util.Map;
|
|||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
@@ -32,7 +33,7 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
Player libraryOwner = ai;
|
Player libraryOwner = ai;
|
||||||
|
|
||||||
@@ -120,7 +121,7 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final SpellAbility root = sa.getRootAbility();
|
final SpellAbility root = sa.getRootAbility();
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (mandatory && sa.canTarget(opp)) {
|
if (mandatory && sa.canTarget(opp)) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -19,7 +20,7 @@ public class DigMultipleAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
Player libraryOwner = ai;
|
Player libraryOwner = ai;
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ public class DigMultipleAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (mandatory && sa.canTarget(opp)) {
|
if (mandatory && sa.canTarget(opp)) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -31,10 +32,8 @@ public class DigUntilAi extends SpellAbilityAi {
|
|||||||
chance = 1;
|
chance = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
|
||||||
|
|
||||||
Player libraryOwner = ai;
|
Player libraryOwner = ai;
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
|
|
||||||
if ("DontMillSelf".equals(logic)) {
|
if ("DontMillSelf".equals(logic)) {
|
||||||
// A card that digs for specific things and puts everything revealed before it into graveyard
|
// A card that digs for specific things and puts everything revealed before it into graveyard
|
||||||
@@ -92,12 +91,12 @@ public class DigUntilAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||||
return randomReturn;
|
return randomReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
@@ -60,11 +61,9 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
if (players.get(0) == ai) {
|
if (players.get(0) == ai) {
|
||||||
// the ai should only be using something like this if he has
|
// the ai should only be using something like this if he has
|
||||||
// few cards in hand,
|
// few cards in hand,
|
||||||
// cards like this better have a good drawback to be in the
|
// cards like this better have a good drawback to be in the AIs deck
|
||||||
// AIs deck
|
|
||||||
} else {
|
} else {
|
||||||
// defined to the human, so that's fine as long the human
|
// defined to the human, so that's fine as long the human has cards
|
||||||
// has cards
|
|
||||||
if (!humanHasHand) {
|
if (!humanHasHand) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -170,7 +169,7 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (!discardTargetAI(ai, sa)) {
|
if (!discardTargetAI(ai, sa)) {
|
||||||
if (mandatory && sa.canTarget(opp)) {
|
if (mandatory && sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ public class DrainManaAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
// assume we are looking to tap human's stuff
|
// assume we are looking to tap human's stuff
|
||||||
// TODO - check for things with untap abilities, and don't tap
|
// TODO - check for things with untap abilities, and don't tap those.
|
||||||
// those.
|
|
||||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
if (!defined.contains(opp)) {
|
if (!defined.contains(opp)) {
|
||||||
|
|||||||
@@ -29,11 +29,10 @@ public class GameLossAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
// Phage the Untouchable
|
// Phage the Untouchable
|
||||||
// (Final Fortune would need to attach it's delayed trigger to a
|
// (Final Fortune would need to attach it's delayed trigger to a
|
||||||
// specific turn, which can't be done yet)
|
// specific turn, which can't be done yet)
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = ai.getGame().getCombat().getDefenderPlayerByAttacker(sa.getHostCard());
|
||||||
|
|
||||||
if (!mandatory && opp.cantLose()) {
|
if (!mandatory && opp.cantLose()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -19,7 +20,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
final int myLife = aiPlayer.getLife();
|
final int myLife = aiPlayer.getLife();
|
||||||
Player opponent = aiPlayer.getWeakestOpponent();
|
Player opponent = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||||
final int hLife = opponent.getLife();
|
final int hLife = opponent.getLife();
|
||||||
|
|
||||||
if (!aiPlayer.canGainLife()) {
|
if (!aiPlayer.canGainLife()) {
|
||||||
@@ -75,7 +76,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
|
|||||||
final boolean mandatory) {
|
final boolean mandatory) {
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.AiProps;
|
import forge.ai.AiProps;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
@@ -154,7 +155,7 @@ public class LifeExchangeVariantAi extends SpellAbilityAi {
|
|||||||
final boolean mandatory) {
|
final boolean mandatory) {
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final int myLife = ai.getLife();
|
final int myLife = ai.getLife();
|
||||||
final Player opponent = ai.getWeakestOpponent();
|
final Player opponent = ai.getStrongestOpponent();
|
||||||
final int hlife = opponent.getLife();
|
final int hlife = opponent.getLife();
|
||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
|
|
||||||
@@ -36,8 +36,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO handle proper calculation of X values based on Cost and what
|
// TODO handle proper calculation of X values based on Cost and what would be paid
|
||||||
// would be paid
|
|
||||||
int amount;
|
int amount;
|
||||||
// we shouldn't have to worry too much about PayX for SetLife
|
// we shouldn't have to worry too much about PayX for SetLife
|
||||||
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
@@ -58,11 +57,9 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
if (tgt.canOnlyTgtOpponent()) {
|
if (tgt.canOnlyTgtOpponent()) {
|
||||||
sa.getTargets().add(opponent);
|
sa.getTargets().add(opponent);
|
||||||
// if we can only target the human, and the Human's life
|
// if we can only target the human, and the Human's life
|
||||||
// would
|
// would go up, don't play it.
|
||||||
// go up, don't play it.
|
|
||||||
// possibly add a combo here for Magister Sphinx and
|
// possibly add a combo here for Magister Sphinx and
|
||||||
// Higedetsu's
|
// Higedetsu's (sp?) Second Rite
|
||||||
// (sp?) Second Rite
|
|
||||||
if ((amount > hlife) || !opponent.canLoseLife()) {
|
if ((amount > hlife) || !opponent.canLoseLife()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -81,8 +78,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
if (sa.getParam("Defined").equals("Player")) {
|
if (sa.getParam("Defined").equals("Player")) {
|
||||||
if (amount == 0) {
|
if (amount == 0) {
|
||||||
return false;
|
return false;
|
||||||
} else if (myLife > amount) { // will decrease computer's
|
} else if (myLife > amount) { // will decrease computer's life
|
||||||
// life
|
|
||||||
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
|
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -104,7 +100,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final int myLife = ai.getLife();
|
final int myLife = ai.getLife();
|
||||||
final Player opponent = ai.getWeakestOpponent();
|
final Player opponent = ai.getStrongestOpponent();
|
||||||
final int hlife = opponent.getLife();
|
final int hlife = opponent.getLife();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
@@ -133,8 +129,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the Target is gaining life, target self.
|
// If the Target is gaining life, target self.
|
||||||
// if the Target is modifying how much life is gained, this needs to
|
// if the Target is modifying how much life is gained, this needs to be handled better
|
||||||
// be handled better
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|||||||
@@ -30,13 +30,12 @@ public class PowerExchangeAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|
||||||
List<Card> list =
|
List<Card> list =
|
||||||
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
CardLists.getValidCards(ai.getGame().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||||
// AI won't try to grab cards that are filtered out of AI decks on
|
// AI won't try to grab cards that are filtered out of AI decks on purpose
|
||||||
// purpose
|
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
return !ComputerUtilCard.isCardRemAIDeck(c) && c.canBeTargetedBy(sa);
|
return !ComputerUtilCard.isCardRemAIDeck(c) && c.canBeTargetedBy(sa) && c.getController() != ai;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
CardLists.sortByPowerAsc(list);
|
CardLists.sortByPowerAsc(list);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.google.common.base.Predicate;
|
|||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCombat;
|
import forge.ai.ComputerUtilCombat;
|
||||||
@@ -201,7 +202,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final Combat combat = game.getCombat();
|
final Combat combat = game.getCombat();
|
||||||
final PhaseHandler ph = game.getPhaseHandler();
|
final PhaseHandler ph = game.getPhaseHandler();
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
final int newPower = card.getNetCombatDamage() + attack;
|
final int newPower = card.getNetCombatDamage() + attack;
|
||||||
//int defense = getNumDefense(sa);
|
//int defense = getNumDefense(sa);
|
||||||
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
|
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
|
||||||
|
|||||||
@@ -48,12 +48,6 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final int power = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumAtt"), sa);
|
|
||||||
final int defense = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumDef"), sa);
|
|
||||||
final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<>();
|
|
||||||
|
|
||||||
final PhaseType phase = game.getPhaseHandler().getPhase();
|
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -64,15 +58,9 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("ValidCards")) {
|
|
||||||
valid = sa.getParam("ValidCards");
|
|
||||||
}
|
|
||||||
|
|
||||||
final Player opp = ai.getWeakestOpponent();
|
|
||||||
CardCollection comp = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
|
|
||||||
CardCollection human = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
|
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
final Player opp = ai.getStrongestOpponent();
|
||||||
|
|
||||||
if (tgt != null && sa.canTarget(opp) && sa.hasParam("IsCurse")) {
|
if (tgt != null && sa.canTarget(opp) && sa.hasParam("IsCurse")) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
@@ -85,6 +73,18 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final int power = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumAtt"), sa);
|
||||||
|
final int defense = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumDef"), sa);
|
||||||
|
final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<>();
|
||||||
|
final PhaseType phase = game.getPhaseHandler().getPhase();
|
||||||
|
|
||||||
|
if (sa.hasParam("ValidCards")) {
|
||||||
|
valid = sa.getParam("ValidCards");
|
||||||
|
}
|
||||||
|
|
||||||
|
CardCollection comp = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
|
||||||
|
CardCollection human = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
|
||||||
|
|
||||||
if (!game.getStack().isEmpty() && !sa.isCurse()) {
|
if (!game.getStack().isEmpty() && !sa.isCurse()) {
|
||||||
return pumpAgainstRemoval(ai, sa, comp);
|
return pumpAgainstRemoval(ai, sa, comp);
|
||||||
}
|
}
|
||||||
@@ -139,8 +139,7 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluate both lists and pass only if human creatures are more
|
// evaluate both lists and pass only if human creatures are more valuable
|
||||||
// valuable
|
|
||||||
return (ComputerUtilCard.evaluateCreatureList(comp) + 200) < ComputerUtilCard.evaluateCreatureList(human);
|
return (ComputerUtilCard.evaluateCreatureList(comp) + 200) < ComputerUtilCard.evaluateCreatureList(human);
|
||||||
} // end Curse
|
} // end Curse
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.AiController;
|
import forge.ai.AiController;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.PlayerControllerAi;
|
import forge.ai.PlayerControllerAi;
|
||||||
@@ -14,7 +15,7 @@ public class RepeatAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
if (!opp.canBeTargetedBy(sa)) {
|
if (!opp.canBeTargetedBy(sa)) {
|
||||||
@@ -44,9 +45,8 @@ public class RepeatAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
|
|||||||
@@ -20,9 +20,6 @@ import forge.game.spellability.TargetRestrictions;
|
|||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
public class SacrificeAi extends SpellAbilityAi {
|
public class SacrificeAi extends SpellAbilityAi {
|
||||||
// **************************************************************
|
|
||||||
// *************************** Sacrifice ***********************
|
|
||||||
// **************************************************************
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
@@ -48,8 +45,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Improve AI for triggers. If source is a creature with:
|
// Improve AI for triggers. If source is a creature with:
|
||||||
// When ETB, sacrifice a creature. Check to see if the AI has something
|
// When ETB, sacrifice a creature. Check to see if the AI has something to sacrifice
|
||||||
// to sacrifice
|
|
||||||
|
|
||||||
// Eventually, we can call the trigger of ETB abilities with not
|
// Eventually, we can call the trigger of ETB abilities with not
|
||||||
// mandatory as part of the checks to cast something
|
// mandatory as part of the checks to cast something
|
||||||
@@ -58,12 +54,11 @@ public class SacrificeAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean sacrificeTgtAI(final Player ai, final SpellAbility sa) {
|
private boolean sacrificeTgtAI(final Player ai, final SpellAbility sa) {
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final boolean destroy = sa.hasParam("Destroy");
|
final boolean destroy = sa.hasParam("Destroy");
|
||||||
|
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = ai.getStrongestOpponent();
|
||||||
|
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
@@ -109,8 +104,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
|||||||
sa.setXManaCostPaid(Math.min(ComputerUtilCost.getMaxXValue(sa, ai), amount));
|
sa.setXManaCostPaid(Math.min(ComputerUtilCost.getMaxXValue(sa, ai), amount));
|
||||||
}
|
}
|
||||||
|
|
||||||
final int half = (amount / 2) + (amount % 2); // Half of amount
|
final int half = (amount / 2) + (amount % 2); // Half of amount rounded up
|
||||||
// rounded up
|
|
||||||
|
|
||||||
// If the Human has at least half rounded up of the amount to be
|
// If the Human has at least half rounded up of the amount to be
|
||||||
// sacrificed, cast the spell
|
// sacrificed, cast the spell
|
||||||
@@ -130,8 +124,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
|||||||
// If Sacrifice hits both players:
|
// If Sacrifice hits both players:
|
||||||
// Only cast it if Human has the full amount of valid
|
// Only cast it if Human has the full amount of valid
|
||||||
// Only cast it if AI doesn't have the full amount of Valid
|
// Only cast it if AI doesn't have the full amount of Valid
|
||||||
// TODO: Cast if the type is favorable: my "worst" valid is
|
// TODO: Cast if the type is favorable: my "worst" valid is worse than his "worst" valid
|
||||||
// worse than his "worst" valid
|
|
||||||
final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1";
|
final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1";
|
||||||
int amount = AbilityUtils.calculateAmount(source, num, sa);
|
int amount = AbilityUtils.calculateAmount(source, num, sa);
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,12 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.ComputerUtilMana;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
import forge.util.TextUtil;
|
|
||||||
|
|
||||||
public class SacrificeAllAi extends SpellAbilityAi {
|
public class SacrificeAllAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -22,22 +17,7 @@ public class SacrificeAllAi extends SpellAbilityAi {
|
|||||||
// based on what the expected targets could be
|
// based on what the expected targets could be
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
String valid = "";
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
if (sa.hasParam("ValidCards")) {
|
|
||||||
valid = sa.getParam("ValidCards");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valid.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
|
||||||
// Set PayX here to maximum value.
|
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
|
||||||
valid = TextUtil.fastReplace(valid, "X", Integer.toString(xPay));
|
|
||||||
}
|
|
||||||
|
|
||||||
CardCollection humanlist =
|
|
||||||
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
|
||||||
CardCollection computerlist =
|
|
||||||
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
|
||||||
|
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
// AI currently disabled for some costs
|
// AI currently disabled for some costs
|
||||||
@@ -46,29 +26,19 @@ public class SacrificeAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (logic.equals("HellionEruption")) {
|
||||||
|
if (ai.getCreaturesInPlay().size() < 5 || ai.getCreaturesInPlay().size() * 150 < ComputerUtilCard.evaluateCreatureList(ai.getCreaturesInPlay())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DestroyAllAi.doMassRemovalLogic(ai, sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// prevent run-away activations - first time will always return true
|
// prevent run-away activations - first time will always return true
|
||||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
|
|
||||||
// if only creatures are affected evaluate both lists and pass only if
|
|
||||||
// human creatures are more valuable
|
|
||||||
if ((CardLists.getNotType(humanlist, "Creature").size() == 0) && (CardLists.getNotType(computerlist, "Creature").size() == 0)) {
|
|
||||||
if ((ComputerUtilCard.evaluateCreatureList(computerlist) + 200) >= ComputerUtilCard
|
|
||||||
.evaluateCreatureList(humanlist)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} // only lands involved
|
|
||||||
else if ((CardLists.getNotType(humanlist, "Land").size() == 0) && (CardLists.getNotType(computerlist, "Land").size() == 0)) {
|
|
||||||
if ((ComputerUtilCard.evaluatePermanentList(computerlist) + 1) >= ComputerUtilCard
|
|
||||||
.evaluatePermanentList(humanlist)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} // otherwise evaluate both lists by CMC and pass only if human
|
|
||||||
// permanents are more valuable
|
|
||||||
else if ((ComputerUtilCard.evaluatePermanentList(computerlist) + 3) >= ComputerUtilCard
|
|
||||||
.evaluatePermanentList(humanlist)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ((MyRandom.getRandom().nextFloat() < .9667) && chance);
|
return ((MyRandom.getRandom().nextFloat() < .9667) && chance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ public class TapAi extends TapAiBase {
|
|||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
|
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
|
||||||
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
|
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
|
||||||
// TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import java.util.List;
|
|||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
@@ -109,7 +110,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
|||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
protected boolean tapPrefTargeting(final Player ai, final Card source, final SpellAbility sa, final boolean mandatory) {
|
protected boolean tapPrefTargeting(final Player ai, final Card source, final SpellAbility sa, final boolean mandatory) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||||
tapList = CardLists.getTargetableCards(tapList, sa);
|
tapList = CardLists.getTargetableCards(tapList, sa);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -28,7 +29,7 @@ public class TwoPilesAi extends SpellAbilityAi {
|
|||||||
valid = sa.getParam("ValidCards");
|
valid = sa.getParam("ValidCards");
|
||||||
}
|
}
|
||||||
|
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ public class UnattachAllAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Card card = sa.getHostCard();
|
final Card card = sa.getHostCard();
|
||||||
final Player opp = ai.getWeakestOpponent();
|
|
||||||
// Check if there are any valid targets
|
// Check if there are any valid targets
|
||||||
List<GameObject> targets = new ArrayList<>();
|
List<GameObject> targets = new ArrayList<>();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
@@ -66,8 +65,8 @@ public class UnattachAllAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!mandatory && card.isEquipment() && !targets.isEmpty()) {
|
if (!mandatory && card.isEquipment() && !targets.isEmpty()) {
|
||||||
Card newTarget = (Card) targets.get(0);
|
Card newTarget = (Card) targets.get(0);
|
||||||
//don't equip human creatures
|
//don't equip opponent creatures
|
||||||
if (newTarget.getController().equals(opp)) {
|
if (!newTarget.getController().equals(ai)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.util.Map;
|
|||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
@@ -130,7 +131,8 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
Player targetController = ai;
|
Player targetController = ai;
|
||||||
|
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
targetController = ai.getWeakestOpponent();
|
// TODO search through all opponents, may need to check if different controllers allowed
|
||||||
|
targetController = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection list = CardLists.getTargetableCards(targetController.getCardsIn(ZoneType.Battlefield), sa);
|
CardCollection list = CardLists.getTargetableCards(targetController.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
@@ -149,8 +151,7 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CardCollection untapList = targetUntapped ? list : CardLists.filter(list, Presets.TAPPED);
|
CardCollection untapList = targetUntapped ? list : CardLists.filter(list, Presets.TAPPED);
|
||||||
// filter out enchantments and planeswalkers, their tapped state doesn't
|
// filter out enchantments and planeswalkers, their tapped state doesn't matter.
|
||||||
// matter.
|
|
||||||
final String[] tappablePermanents = {"Creature", "Land", "Artifact"};
|
final String[] tappablePermanents = {"Creature", "Land", "Artifact"};
|
||||||
untapList = CardLists.getValidCards(untapList, tappablePermanents, source.getController(), source, sa);
|
untapList = CardLists.getValidCards(untapList, tappablePermanents, source.getController(), source, sa);
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public enum ApiType {
|
|||||||
Block (BlockEffect.class),
|
Block (BlockEffect.class),
|
||||||
Bond (BondEffect.class),
|
Bond (BondEffect.class),
|
||||||
Branch (BranchEffect.class),
|
Branch (BranchEffect.class),
|
||||||
|
Camouflage (CamouflageEffect.class),
|
||||||
ChangeCombatants (ChangeCombatantsEffect.class),
|
ChangeCombatants (ChangeCombatantsEffect.class),
|
||||||
ChangeTargets (ChangeTargetsEffect.class),
|
ChangeTargets (ChangeTargetsEffect.class),
|
||||||
ChangeText (ChangeTextEffect.class),
|
ChangeText (ChangeTextEffect.class),
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package forge.game.ability.effects;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.ability.SpellAbilityEffect;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardCollection;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.combat.Combat;
|
||||||
|
import forge.game.combat.CombatUtil;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.util.Localizer;
|
||||||
|
|
||||||
|
public class CamouflageEffect extends SpellAbilityEffect {
|
||||||
|
|
||||||
|
private void randomizeBlockers(SpellAbility sa, Combat combat, Player declarer, Player defender, List<Card> attackers, List<CardCollection> blockerPiles) {
|
||||||
|
CardLists.shuffle(attackers);
|
||||||
|
for (int i = 0; i < attackers.size(); i++) {
|
||||||
|
final Card attacker = attackers.get(i);
|
||||||
|
CardCollection blockers = blockerPiles.get(i);
|
||||||
|
|
||||||
|
// Remove all illegal blockers first
|
||||||
|
for (int j = blockers.size() - 1; j >= 0; j--) {
|
||||||
|
final Card blocker = blockers.get(j);
|
||||||
|
if (!CombatUtil.canBlock(attacker, blocker, combat)) {
|
||||||
|
blockers.remove(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.") &&
|
||||||
|
blockers.size() < defender.getCreaturesInPlay().size() ||
|
||||||
|
blockers.size() < CombatUtil.needsBlockers(attacker)) {
|
||||||
|
// If not enough remaining creatures to block, don't add them as blocker
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attacker.hasKeyword("CantBeBlockedByAmount GT1") && blockers.size() > 1) {
|
||||||
|
// If no more than one creature can block, order the player to choose one to block
|
||||||
|
Card chosen = declarer.getController().chooseCardsForEffect(blockers, sa,
|
||||||
|
Localizer.getInstance().getMessage("lblChooseBlockerForAttacker", attacker.toString()), 1, 1, false, null).get(0);
|
||||||
|
combat.addBlocker(attacker, chosen);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all remaning blockers normally
|
||||||
|
for (final Card blocker : blockers) {
|
||||||
|
combat.addBlocker(attacker, blocker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resolve(SpellAbility sa) {
|
||||||
|
Card hostCard = sa.getHostCard();
|
||||||
|
Player declarer = getDefinedPlayersOrTargeted(sa).get(0);
|
||||||
|
Player defender = AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("Defender"), sa).get(0);
|
||||||
|
Combat combat = hostCard.getGame().getCombat();
|
||||||
|
List<Card> attackers = combat.getAttackers();
|
||||||
|
List<CardCollection> blockerPiles = new ArrayList<>();
|
||||||
|
|
||||||
|
if (declarer.isAI()) {
|
||||||
|
// For AI player, just let it declare blockers normally, then randomize it later.
|
||||||
|
declarer.getController().declareBlockers(defender, combat);
|
||||||
|
// Remove all blockers first
|
||||||
|
for (final Card attacker : attackers) {
|
||||||
|
CardCollection blockers = combat.getBlockers(attacker);
|
||||||
|
blockerPiles.add(blockers);
|
||||||
|
for (final Card blocker : blockers) {
|
||||||
|
combat.removeFromCombat(blocker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // Human player
|
||||||
|
CardCollection pool = new CardCollection(defender.getCreaturesInPlay());
|
||||||
|
// remove all blockers that can't block
|
||||||
|
for (final Card blocker : pool) {
|
||||||
|
if (!CombatUtil.canBlock(blocker)) {
|
||||||
|
pool.remove(blocker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<Integer> blockedSoFar = new ArrayList<>(Collections.nCopies(pool.size(), 0));
|
||||||
|
|
||||||
|
for (int i = 0; i < attackers.size(); i++) {
|
||||||
|
int size = pool.size();
|
||||||
|
CardCollection blockers = new CardCollection(declarer.getController().chooseCardsForEffect(
|
||||||
|
pool, sa, Localizer.getInstance().getMessage("lblChooseBlockersForPile", String.valueOf(i + 1)), 0, size, false, null));
|
||||||
|
blockerPiles.add(blockers);
|
||||||
|
// Remove chosen creatures, unless it can block additional attackers
|
||||||
|
for (final Card blocker : blockers) {
|
||||||
|
int index = pool.indexOf(blocker);
|
||||||
|
Integer blockedCount = blockedSoFar.get(index) + 1;
|
||||||
|
if (!blocker.canBlockAny() && blocker.canBlockAdditional() < blockedCount) {
|
||||||
|
pool.remove(index);
|
||||||
|
blockedSoFar.remove(index);
|
||||||
|
} else {
|
||||||
|
blockedSoFar.set(index, blockedCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
randomizeBlockers(sa, combat, declarer, defender, attackers, blockerPiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -349,6 +349,9 @@ public class DiscardEffect extends SpellAbilityEffect {
|
|||||||
runParams.put(AbilityKey.Cause, sa);
|
runParams.put(AbilityKey.Cause, sa);
|
||||||
runParams.put(AbilityKey.FirstTime, firstDiscard);
|
runParams.put(AbilityKey.FirstTime, firstDiscard);
|
||||||
game.getTriggerHandler().runTrigger(TriggerType.DiscardedAll, runParams, false);
|
game.getTriggerHandler().runTrigger(TriggerType.DiscardedAll, runParams, false);
|
||||||
|
if (sa.hasParam("RememberDiscardingPlayers")) {
|
||||||
|
source.addRemembered(p);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1247,17 +1247,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
|||||||
currentState.addTrigger(t);
|
currentState.addTrigger(t);
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
@Deprecated
|
|
||||||
public final void removeTrigger(final Trigger t) {
|
|
||||||
currentState.removeTrigger(t);
|
|
||||||
}
|
|
||||||
@Deprecated
|
|
||||||
public final void removeTrigger(final Trigger t, final CardStateName state) {
|
|
||||||
getState(state).removeTrigger(t);
|
|
||||||
}
|
|
||||||
public final void clearTriggersNew() {
|
|
||||||
currentState.clearTriggers();
|
|
||||||
}
|
|
||||||
|
|
||||||
public final boolean hasTrigger(final Trigger t) {
|
public final boolean hasTrigger(final Trigger t) {
|
||||||
return currentState.hasTrigger(t);
|
return currentState.hasTrigger(t);
|
||||||
|
|||||||
@@ -653,9 +653,16 @@ public class PhaseHandler implements java.io.Serializable {
|
|||||||
}
|
}
|
||||||
if (combat.isPlayerAttacked(p)) {
|
if (combat.isPlayerAttacked(p)) {
|
||||||
if (CombatUtil.canBlock(p, combat)) {
|
if (CombatUtil.canBlock(p, combat)) {
|
||||||
|
// Replacement effects (for Camouflage)
|
||||||
|
final Map<AbilityKey, Object> repRunParams = AbilityKey.mapFromAffected(p);
|
||||||
|
repRunParams.put(AbilityKey.Player, whoDeclaresBlockers);
|
||||||
|
ReplacementResult repres = game.getReplacementHandler().run(ReplacementType.DeclareBlocker, repRunParams);
|
||||||
|
if (repres == ReplacementResult.NotReplaced) {
|
||||||
|
// If not replaced, run normal declare blockers
|
||||||
whoDeclaresBlockers.getController().declareBlockers(p, combat);
|
whoDeclaresBlockers.getController().declareBlockers(p, combat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else { continue; }
|
else { continue; }
|
||||||
|
|
||||||
if (game.isGameOver()) { // they just like to close window at any moment
|
if (game.isGameOver()) { // they just like to close window at any moment
|
||||||
|
|||||||
@@ -421,6 +421,9 @@ public class Player extends GameEntity implements Comparable<Player> {
|
|||||||
public final Player getWeakestOpponent() {
|
public final Player getWeakestOpponent() {
|
||||||
return getOpponents().min(PlayerPredicates.compareByLife());
|
return getOpponents().min(PlayerPredicates.compareByLife());
|
||||||
}
|
}
|
||||||
|
public final Player getStrongestOpponent() {
|
||||||
|
return getOpponents().max(PlayerPredicates.compareByLife());
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isOpponentOf(Player other) {
|
public boolean isOpponentOf(Player other) {
|
||||||
return other != this && other != null && (other.teamNumber < 0 || other.teamNumber != teamNumber);
|
return other != this && other != null && (other.teamNumber < 0 || other.teamNumber != teamNumber);
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package forge.game.replacement;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import forge.game.ability.AbilityKey;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
public class ReplaceDeclareBlocker extends ReplacementEffect {
|
||||||
|
|
||||||
|
public ReplaceDeclareBlocker(final Map<String, String> mapParams, final Card host, final boolean intrinsic) {
|
||||||
|
super(mapParams, host, intrinsic);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canReplace(Map<AbilityKey, Object> runParams) {
|
||||||
|
if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Affected))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) {
|
||||||
|
sa.setReplacingObject(AbilityKey.DefendingPlayer, runParams.get(AbilityKey.Affected));
|
||||||
|
// Here the Player is the one who would declare blockers (may be changed by some Card's effect)
|
||||||
|
sa.setReplacingObject(AbilityKey.Player, runParams.get(AbilityKey.Player));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ public enum ReplacementType {
|
|||||||
CreateToken(ReplaceToken.class),
|
CreateToken(ReplaceToken.class),
|
||||||
DamageDone(ReplaceDamage.class),
|
DamageDone(ReplaceDamage.class),
|
||||||
DealtDamage(ReplaceDealtDamage.class),
|
DealtDamage(ReplaceDealtDamage.class),
|
||||||
|
DeclareBlocker(ReplaceDeclareBlocker.class),
|
||||||
Destroy(ReplaceDestroy.class),
|
Destroy(ReplaceDestroy.class),
|
||||||
Discard(ReplaceDiscard.class),
|
Discard(ReplaceDiscard.class),
|
||||||
Draw(ReplaceDraw.class),
|
Draw(ReplaceDraw.class),
|
||||||
|
|||||||
7
forge-gui/res/cardsfolder/c/camouflage.txt
Normal file
7
forge-gui/res/cardsfolder/c/camouflage.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Name:Camouflage
|
||||||
|
ManaCost:G
|
||||||
|
Types:Instant
|
||||||
|
A:SP$ Effect | Cost$ G | ReplacementEffects$ RDeclareBlocker | ActivationPhases$ Declare Attackers | PlayerTurn$ True | AILogic$ Evasion | SpellDescription$ Cast this spell only during your declare attackers step. This turn, instead of declaring blockers, each defending player chooses any number of creatures they control and divides them into a number of piles equal to the number of attacking creatures for whom that player is the defending player. Creatures those players control that can block additional creatures may likewise be put into additional piles. Assign each pile to a different one of those attacking creatures at random. Each creature in a pile that can block the creature that pile is assigned to does so. (Piles can be empty.)
|
||||||
|
SVar:RDeclareBlocker:Event$ DeclareBlocker | ValidPlayer$ Opponent | ReplaceWith$ DBCamouflage | Description$ This turn, instead of declaring blockers, each defending player chooses any number of creatures they control and divides them into a number of piles equal to the number of attacking creatures for whom that player is the defending player. Creatures those players control that can block additional creatures may likewise be put into additional piles. Assign each pile to a different one of those attacking creatures at random. Each creature in a pile that can block the creature that pile is assigned to does so. (Piles can be empty.)
|
||||||
|
SVar:DBCamouflage:DB$ Camouflage | Defined$ ReplacedPlayer | Defender$ ReplacedDefendingPlayer | AILogic$ BestBlocker
|
||||||
|
Oracle:Cast this spell only during your declare attackers step.\nThis turn, instead of declaring blockers, each defending player chooses any number of creatures they control and divides them into a number of piles equal to the number of attacking creatures for whom that player is the defending player. Creatures those players control that can block additional creatures may likewise be put into additional piles. Assign each pile to a different one of those attacking creatures at random. Each creature in a pile that can block the creature that pile is assigned to does so. (Piles can be empty.)
|
||||||
@@ -10,4 +10,5 @@ SVar:DBCleanup:DB$ Cleanup | ForgetDefined$ TriggeredCard
|
|||||||
A:AB$ FlipACoin | Cost$ T | TgtZone$ Stack | TargetType$ Spell | ValidTgts$ Artifact.YouCtrl+IsRemembered | WinSubAbility$ DBDraw | LoseSubAbility$ DBCounter | TgtPrompt$ Select target Artifact spell | SpellDescription$ Flip a coin. If you win the flip, draw a card. If you lose the flip, counter target artifact spell you control that isn't the target of an ability from another creature named Goblin Artisans.
|
A:AB$ FlipACoin | Cost$ T | TgtZone$ Stack | TargetType$ Spell | ValidTgts$ Artifact.YouCtrl+IsRemembered | WinSubAbility$ DBDraw | LoseSubAbility$ DBCounter | TgtPrompt$ Select target Artifact spell | SpellDescription$ Flip a coin. If you win the flip, draw a card. If you lose the flip, counter target artifact spell you control that isn't the target of an ability from another creature named Goblin Artisans.
|
||||||
SVar:DBDraw:DB$ Draw | NumCards$ 1 | Defined$ You
|
SVar:DBDraw:DB$ Draw | NumCards$ 1 | Defined$ You
|
||||||
SVar:DBCounter:DB$ Counter | Defined$ Targeted
|
SVar:DBCounter:DB$ Counter | Defined$ Targeted
|
||||||
|
AI:RemoveDeck:All
|
||||||
Oracle:{T}: Flip a coin. If you win the flip, draw a card. If you lose the flip, counter target artifact spell you control that isn't the target of an ability from another creature named Goblin Artisans.
|
Oracle:{T}: Flip a coin. If you win the flip, draw a card. If you lose the flip, counter target artifact spell you control that isn't the target of an ability from another creature named Goblin Artisans.
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
Name:Hellion Eruption
|
Name:Hellion Eruption
|
||||||
ManaCost:5 R
|
ManaCost:5 R
|
||||||
Types:Sorcery
|
Types:Sorcery
|
||||||
A:SP$ SacrificeAll | Cost$ 5 R | ValidCards$ Creature.YouCtrl | RememberSacrificed$ True | SubAbility$ DBToken | SpellDescription$ Sacrifice all creatures you control, then create that many 4/4 red Hellion creature tokens.
|
A:SP$ SacrificeAll | Cost$ 5 R | ValidCards$ Creature.YouCtrl | RememberSacrificed$ True | SubAbility$ DBToken | AILogic$ HellionEruption | SpellDescription$ Sacrifice all creatures you control, then create that many 4/4 red Hellion creature tokens.
|
||||||
SVar:DBToken:DB$Token | TokenAmount$ X | TokenScript$ r_4_4_hellion | TokenOwner$ You | LegacyImage$ r 4 4 hellion roe
|
SVar:DBToken:DB$Token | TokenAmount$ X | TokenScript$ r_4_4_hellion | TokenOwner$ You | LegacyImage$ r 4 4 hellion roe
|
||||||
SVar:X:Remembered$Amount
|
SVar:X:Remembered$Amount
|
||||||
AI:RemoveDeck:All
|
|
||||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/hellion_eruption.jpg
|
SVar:Picture:http://www.wizards.com/global/images/magic/general/hellion_eruption.jpg
|
||||||
Oracle:Sacrifice all creatures you control, then create that many 4/4 red Hellion creature tokens.
|
Oracle:Sacrifice all creatures you control, then create that many 4/4 red Hellion creature tokens.
|
||||||
|
|||||||
8
forge-gui/res/cardsfolder/upcoming/frostboil_snarl.txt
Normal file
8
forge-gui/res/cardsfolder/upcoming/frostboil_snarl.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Name:Frostboil Snarl
|
||||||
|
ManaCost:no cost
|
||||||
|
Types:Land
|
||||||
|
K:ETBReplacement:Other:DBTap
|
||||||
|
SVar:DBTap:DB$ Tap | ETB$ True | Defined$ Self | UnlessCost$ Reveal<1/Island,Mountain/Island or Mountain> | UnlessPayer$ You | StackDescription$ enters the battlefield tapped. | SpellDescription$ As CARDNAME enters the battlefield, you may reveal a Island or Mountain card from your hand. If you don't, CARDNAME enters the battlefield tapped.
|
||||||
|
A:AB$ Mana | Cost$ T | Produced$ U | SpellDescription$ Add {U}.
|
||||||
|
A:AB$ Mana | Cost$ T | Produced$ R | SpellDescription$ Add {R}.
|
||||||
|
Oracle:As Frostboil Snarl enters the battlefield, you may reveal a Island or Mountain card from your hand. If you don't, Frostboil Snarl enters the battlefield tapped.\n{T}: Add {U} or {R}.
|
||||||
8
forge-gui/res/cardsfolder/upcoming/furycalm_snarl.txt
Normal file
8
forge-gui/res/cardsfolder/upcoming/furycalm_snarl.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Name:Furycalm Snarl
|
||||||
|
ManaCost:no cost
|
||||||
|
Types:Land
|
||||||
|
K:ETBReplacement:Other:DBTap
|
||||||
|
SVar:DBTap:DB$ Tap | ETB$ True | Defined$ Self | UnlessCost$ Reveal<1/Mountain,Plains/Mountain or Plains> | UnlessPayer$ You | StackDescription$ enters the battlefield tapped. | SpellDescription$ As CARDNAME enters the battlefield, you may reveal a Mountain or Plains card from your hand. If you don't, CARDNAME enters the battlefield tapped.
|
||||||
|
A:AB$ Mana | Cost$ T | Produced$ R | SpellDescription$ Add {R}.
|
||||||
|
A:AB$ Mana | Cost$ T | Produced$ W | SpellDescription$ Add {W}.
|
||||||
|
Oracle:As Furycalm Snarl enters the battlefield, you may reveal a Mountain or Plains card from your hand. If you don't, Furycalm Snarl enters the battlefield tapped.\n{T}: Add {R} or {W}.
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
Name:Necroblossom Snarl
|
||||||
|
ManaCost:no cost
|
||||||
|
Types:Land
|
||||||
|
K:ETBReplacement:Other:DBTap
|
||||||
|
SVar:DBTap:DB$ Tap | ETB$ True | Defined$ Self | UnlessCost$ Reveal<1/Swamp,Forest/Swamp or Forest> | UnlessPayer$ You | StackDescription$ enters the battlefield tapped. | SpellDescription$ As CARDNAME enters the battlefield, you may reveal a Swamp or Forest card from your hand. If you don't, CARDNAME enters the battlefield tapped.
|
||||||
|
A:AB$ Mana | Cost$ T | Produced$ B | SpellDescription$ Add {B}.
|
||||||
|
A:AB$ Mana | Cost$ T | Produced$ G | SpellDescription$ Add {G}.
|
||||||
|
Oracle:As Necroblossom Snarl enters the battlefield, you may reveal a Swamp or Forest card from your hand. If you don't, Necroblossom Snarl enters the battlefield tapped.\n{T}: Add {B} or {G}.
|
||||||
20
forge-gui/res/cardsfolder/upcoming/professor_onyx.txt
Normal file
20
forge-gui/res/cardsfolder/upcoming/professor_onyx.txt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Name:Professor Onyx
|
||||||
|
ManaCost:4 B B
|
||||||
|
Types:Legendary Planeswalker Liliana
|
||||||
|
Loyalty:5
|
||||||
|
T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | NoResolvingCheck$ True | Execute$ TrigDrain | TriggerZones$ Battlefield | TriggerDescription$ Magecraft — Whenever you cast or copy an instant or sorcery spell, each opponent loses 2 life and you gain 2 life.
|
||||||
|
T:Mode$ SpellCopy | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDrain | Secondary$ True | TriggerDescription$ Magecraft — Whenever you cast or copy an instant or sorcery spell, each opponent loses 2 life and you gain 2 life.
|
||||||
|
SVar:TrigDrain:DB$ LoseLife | Defined$ Player.Opponent | LifeAmount$ 2 | SubAbility$ DBGainLife
|
||||||
|
SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 2
|
||||||
|
A:AB$ LoseLife | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | Defined$ You | LifeAmount$ 1 | SubAbility$ DBDig | SpellDescription$ You lose 1 life. Look at the top three cards of your library. Put one of them into your hand and the rest into your graveyard.
|
||||||
|
SVar:DBDig:DB$ Dig | DigNum$ 3 | ChangeNum$ 1 | DestinationZone2$ Graveyard | StackDescription$ {p:You} looks at the top three cards of their library. {p:You} puts one of them into their hand and the rest into their graveyard.
|
||||||
|
A:AB$ RepeatEach | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | RepeatPlayers$ Opponent | RepeatSubAbility$ DBChooseCard | SubAbility$ DBSac | SpellDescription$ Each opponent sacrifices a creature with the greatest power among creatures that player controls.
|
||||||
|
SVar:DBChooseCard:DB$ ChooseCard | Defined$ Player.IsRemembered | Choices$ Creature.greatestPowerControlledByRemembered | ChoiceTitle$ Choose a creature you control with the greatest power | Mandatory$ True | RememberChosen$ True
|
||||||
|
SVar:DBSac:DB$ SacrificeAll | ValidCards$ Card.IsRemembered | SubAbility$ DBCleanup | StackDescription$ Each opponent sacrifices a creature with the greatest power among creatures they control.
|
||||||
|
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearChosen$ True
|
||||||
|
A:AB$ Repeat | Cost$ SubCounter<8/LOYALTY> | Planeswalker$ True | Ultimate$ True | RepeatSubAbility$ TrigDiscard | MaxRepeat$ 7 | StackDescription$ SpellDescription | SpellDescription$ Each opponent may discard a card. If they don't, they lose 3 life. Repeat this process six more times.
|
||||||
|
SVar:TrigDiscard:DB$ Discard | Defined$ Player.Opponent | Mode$ TgtChoose | Optional$ True | RememberDiscardingPlayers$ True | AILogic$ UnlessAtLife.6 | SubAbility$ DBLoseLife
|
||||||
|
SVar:DBLoseLife:DB$ LoseLife | Defined$ Opponent.IsNotRemembered | LifeAmount$ 3 | SubAbility$ DBCleanup
|
||||||
|
DeckHints:Type$Instant|Sorcery
|
||||||
|
DeckHas:Ability$Graveyard & Ability$LifeGain
|
||||||
|
Oracle:Magecraft — Whenever you cast or copy an instant or sorcery spell, each opponent loses 2 life and you gain 2 life.\n[+1]: You lose 1 life. Look at the top three cards of your library. Put one of them into your hand and the rest into your graveyard.\n[−3]: Each opponent sacrifices a creature with the greatest power among creatures that player controls.\n[−8]: Each opponent may discard a card. If they don't, they lose 3 life. Repeat this process six more times.
|
||||||
8
forge-gui/res/cardsfolder/upcoming/shineshadow_snarl.txt
Normal file
8
forge-gui/res/cardsfolder/upcoming/shineshadow_snarl.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Name:Shineshadow Snarl
|
||||||
|
ManaCost:no cost
|
||||||
|
Types:Land
|
||||||
|
K:ETBReplacement:Other:DBTap
|
||||||
|
SVar:DBTap:DB$ Tap | ETB$ True | Defined$ Self | UnlessCost$ Reveal<1/Plains,Swamp/Plains or Swamp> | UnlessPayer$ You | StackDescription$ enters the battlefield tapped. | SpellDescription$ As CARDNAME enters the battlefield, you may reveal a Plains or Swamp card from your hand. If you don't, CARDNAME enters the battlefield tapped.
|
||||||
|
A:AB$ Mana | Cost$ T | Produced$ W | SpellDescription$ Add {W}.
|
||||||
|
A:AB$ Mana | Cost$ T | Produced$ B | SpellDescription$ Add {B}.
|
||||||
|
Oracle:As Shineshadow Snarl enters the battlefield, you may reveal a Plains or Swamp card from your hand. If you don't, Shineshadow Snarl enters the battlefield tapped.\n{T}: Add {W} or {B}.
|
||||||
8
forge-gui/res/cardsfolder/upcoming/vineglimmer_snarl.txt
Normal file
8
forge-gui/res/cardsfolder/upcoming/vineglimmer_snarl.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Name:Vineglimmer Snarl
|
||||||
|
ManaCost:no cost
|
||||||
|
Types:Land
|
||||||
|
K:ETBReplacement:Other:DBTap
|
||||||
|
SVar:DBTap:DB$ Tap | ETB$ True | Defined$ Self | UnlessCost$ Reveal<1/Forest,Island/Forest or Island> | UnlessPayer$ You | StackDescription$ enters the battlefield tapped. | SpellDescription$ As CARDNAME enters the battlefield, you may reveal a Forest or Island card from your hand. If you don't, CARDNAME enters the battlefield tapped.
|
||||||
|
A:AB$ Mana | Cost$ T | Produced$ G | SpellDescription$ Add {G}.
|
||||||
|
A:AB$ Mana | Cost$ T | Produced$ U | SpellDescription$ Add {U}.
|
||||||
|
Oracle:As Vineglimmer Snarl enters the battlefield, you may reveal a Forest or Island card from your hand. If you don't, Vineglimmer Snarl enters the battlefield tapped.\n{T}: Add {G} or {U}.
|
||||||
@@ -1750,6 +1750,9 @@ lblDoYouWantTopBid=Möchtest du überbieten? Aktuelles Gebot:
|
|||||||
lblTopBidWithValueLife=hat mit {0} Leben überboten
|
lblTopBidWithValueLife=hat mit {0} Leben überboten
|
||||||
#BondEffect.java
|
#BondEffect.java
|
||||||
lblSelectACardPair=Wähle Karte zum Verbinden
|
lblSelectACardPair=Wähle Karte zum Verbinden
|
||||||
|
#CamouflageEffect.java
|
||||||
|
lblChooseBlockerForAttacker=Choose a creature to block {0}
|
||||||
|
lblChooseBlockersForPile=Choose creatures to put in pile {0} (can be empty)
|
||||||
#ChangeCombatantsEffect.java
|
#ChangeCombatantsEffect.java
|
||||||
lblChooseDefenderToAttackWithCard=Welchen Verteidiger mit {0} angreifen?
|
lblChooseDefenderToAttackWithCard=Welchen Verteidiger mit {0} angreifen?
|
||||||
#ChangeTargetsEffect.java
|
#ChangeTargetsEffect.java
|
||||||
|
|||||||
@@ -1750,6 +1750,9 @@ lblDoYouWantTopBid=Do you want to top bid? Current Bid \=
|
|||||||
lblTopBidWithValueLife=topped bid with {0} life
|
lblTopBidWithValueLife=topped bid with {0} life
|
||||||
#BondEffect.java
|
#BondEffect.java
|
||||||
lblSelectACardPair=Select a card to pair with
|
lblSelectACardPair=Select a card to pair with
|
||||||
|
#CamouflageEffect.java
|
||||||
|
lblChooseBlockerForAttacker=Choose a creature to block {0}
|
||||||
|
lblChooseBlockersForPile=Choose creatures to put in pile {0} (can be empty)
|
||||||
#ChangeCombatantsEffect.java
|
#ChangeCombatantsEffect.java
|
||||||
lblChooseDefenderToAttackWithCard=Choose which defender to attack with {0}
|
lblChooseDefenderToAttackWithCard=Choose which defender to attack with {0}
|
||||||
#ChangeTargetsEffect.java
|
#ChangeTargetsEffect.java
|
||||||
|
|||||||
@@ -1750,6 +1750,9 @@ lblDoYouWantTopBid=¿Quieres hacer una puja máxima? Puja actual \=
|
|||||||
lblTopBidWithValueLife=puja más alta con {0} de vida
|
lblTopBidWithValueLife=puja más alta con {0} de vida
|
||||||
#BondEffect.java
|
#BondEffect.java
|
||||||
lblSelectACardPair=Selecciona una carta para emparejarla con
|
lblSelectACardPair=Selecciona una carta para emparejarla con
|
||||||
|
#CamouflageEffect.java
|
||||||
|
lblChooseBlockerForAttacker=Choose a creature to block {0}
|
||||||
|
lblChooseBlockersForPile=Choose creatures to put in pile {0} (can be empty)
|
||||||
#ChangeCombatantsEffect.java
|
#ChangeCombatantsEffect.java
|
||||||
lblChooseDefenderToAttackWithCard=Elige con qué defensor atacar con {0}
|
lblChooseDefenderToAttackWithCard=Elige con qué defensor atacar con {0}
|
||||||
#ChangeTargetsEffect.java
|
#ChangeTargetsEffect.java
|
||||||
|
|||||||
@@ -1750,6 +1750,9 @@ lblDoYouWantTopBid=Do you want to top bid? Current Bid \=
|
|||||||
lblTopBidWithValueLife=topped bid with {0} life
|
lblTopBidWithValueLife=topped bid with {0} life
|
||||||
#BondEffect.java
|
#BondEffect.java
|
||||||
lblSelectACardPair=Select a card to pair with
|
lblSelectACardPair=Select a card to pair with
|
||||||
|
#CamouflageEffect.java
|
||||||
|
lblChooseBlockerForAttacker=Choose a creature to block {0}
|
||||||
|
lblChooseBlockersForPile=Choose creatures to put in pile {0} (can be empty)
|
||||||
#ChangeCombatantsEffect.java
|
#ChangeCombatantsEffect.java
|
||||||
lblChooseDefenderToAttackWithCard=Choose which defender to attack with {0}
|
lblChooseDefenderToAttackWithCard=Choose which defender to attack with {0}
|
||||||
#ChangeTargetsEffect.java
|
#ChangeTargetsEffect.java
|
||||||
|
|||||||
@@ -1750,6 +1750,9 @@ lblDoYouWantTopBid=競りの点数を上げますか? 現在の点数 \=
|
|||||||
lblTopBidWithValueLife={0}点のライフで競りの点数をつけた
|
lblTopBidWithValueLife={0}点のライフで競りの点数をつけた
|
||||||
#BondEffect.java
|
#BondEffect.java
|
||||||
lblSelectACardPair=組にしたいカードを選ぶ
|
lblSelectACardPair=組にしたいカードを選ぶ
|
||||||
|
#CamouflageEffect.java
|
||||||
|
lblChooseBlockerForAttacker={0}をブロックするクリーチャーを選ぶ
|
||||||
|
lblChooseBlockersForPile={0}番の束に入れるクリーチャーを選ぶ(空にできる)
|
||||||
#ChangeCombatantsEffect.java
|
#ChangeCombatantsEffect.java
|
||||||
lblChooseDefenderToAttackWithCard={0}が攻撃する対象を選ぶ
|
lblChooseDefenderToAttackWithCard={0}が攻撃する対象を選ぶ
|
||||||
#ChangeTargetsEffect.java
|
#ChangeTargetsEffect.java
|
||||||
|
|||||||
@@ -117,8 +117,8 @@ cbpCounterDisplayLocation=计数器显示区域
|
|||||||
cbpGraveyardOrdering=允许指衍生物进入墓地
|
cbpGraveyardOrdering=允许指衍生物进入墓地
|
||||||
lblAltLifeDisplay=备用牌手布局(横向模式)
|
lblAltLifeDisplay=备用牌手布局(横向模式)
|
||||||
nlAltLifeDisplay=启用备用牌手布局以显示玩家的生命以及中毒,能量和经验指示物。
|
nlAltLifeDisplay=启用备用牌手布局以显示玩家的生命以及中毒,能量和经验指示物。
|
||||||
lblPreferredArt=Preferred Art
|
lblPreferredArt=首选卡图
|
||||||
nlPreferredArt=Sets the preferred art for cards.
|
nlPreferredArt=设置牌张的首选卡图。
|
||||||
Troubleshooting=故障排除
|
Troubleshooting=故障排除
|
||||||
GeneralConfiguration=常规配置
|
GeneralConfiguration=常规配置
|
||||||
lblPlayerName=玩家名称
|
lblPlayerName=玩家名称
|
||||||
@@ -1750,6 +1750,9 @@ lblDoYouWantTopBid=你想要喊更高的价? 现在价钱 \=
|
|||||||
lblTopBidWithValueLife=最高喊价为{0}生命
|
lblTopBidWithValueLife=最高喊价为{0}生命
|
||||||
#BondEffect.java
|
#BondEffect.java
|
||||||
lblSelectACardPair=选择要组成搭档的牌
|
lblSelectACardPair=选择要组成搭档的牌
|
||||||
|
#CamouflageEffect.java
|
||||||
|
lblChooseBlockerForAttacker=Choose a creature to block {0}
|
||||||
|
lblChooseBlockersForPile=Choose creatures to put in pile {0} (can be empty)
|
||||||
#ChangeCombatantsEffect.java
|
#ChangeCombatantsEffect.java
|
||||||
lblChooseDefenderToAttackWith=选择守军进行进攻
|
lblChooseDefenderToAttackWith=选择守军进行进攻
|
||||||
#ChangeTargetsEffect.java
|
#ChangeTargetsEffect.java
|
||||||
@@ -1933,9 +1936,9 @@ lblTapOrUntapTarget=横置还是重置{0}?
|
|||||||
#TwoPilesEffect.java
|
#TwoPilesEffect.java
|
||||||
lblSelectCardForFaceDownPile=选择一个面朝下的堆
|
lblSelectCardForFaceDownPile=选择一个面朝下的堆
|
||||||
lblDivideCardIntoTwoPiles=将牌分为两堆
|
lblDivideCardIntoTwoPiles=将牌分为两堆
|
||||||
lblSelectCardForLeftPile=Select cards for the left pile
|
lblSelectCardForLeftPile=选择左堆中的牌张
|
||||||
lblLeftPile=Left pile
|
lblLeftPile=左堆
|
||||||
lblRightPile=Right pile
|
lblRightPile=右堆
|
||||||
lblChoosesPile=选择堆
|
lblChoosesPile=选择堆
|
||||||
lblEmptyPile=空堆
|
lblEmptyPile=空堆
|
||||||
#UntapEffect.java
|
#UntapEffect.java
|
||||||
@@ -1953,7 +1956,7 @@ lblViewAll=查看所有牌
|
|||||||
lblSetupGame=设定游戏状态
|
lblSetupGame=设定游戏状态
|
||||||
lblDumpGame=转储游戏状态
|
lblDumpGame=转储游戏状态
|
||||||
lblTutor=导师牌
|
lblTutor=导师牌
|
||||||
lblRollbackPhase=Rollback Phase
|
lblRollbackPhase=回滚阶段
|
||||||
lblAddCounterPermanent=向牌添加指示物
|
lblAddCounterPermanent=向牌添加指示物
|
||||||
lblSubCounterPermanent=从牌减少指示物
|
lblSubCounterPermanent=从牌减少指示物
|
||||||
lblTapPermanent=横置永久物
|
lblTapPermanent=横置永久物
|
||||||
|
|||||||
Reference in New Issue
Block a user