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) {
|
||||
this.ai = ai;
|
||||
this.defendingOpponent = choosePreferredDefenderPlayer();
|
||||
this.defendingOpponent = choosePreferredDefenderPlayer(ai);
|
||||
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
||||
this.myList = ai.getCreaturesInPlay();
|
||||
this.attackers = new ArrayList<>();
|
||||
@@ -107,7 +107,7 @@ public class AiAttackController {
|
||||
|
||||
public AiAttackController(final Player ai, Card attacker) {
|
||||
this.ai = ai;
|
||||
this.defendingOpponent = choosePreferredDefenderPlayer();
|
||||
this.defendingOpponent = choosePreferredDefenderPlayer(ai);
|
||||
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
||||
this.myList = ai.getCreaturesInPlay();
|
||||
this.attackers = new ArrayList<>();
|
||||
@@ -156,13 +156,12 @@ public class AiAttackController {
|
||||
}
|
||||
|
||||
/** Choose opponent for AI to attack here. Expand as necessary. */
|
||||
private Player choosePreferredDefenderPlayer() {
|
||||
Player defender = ai.getWeakestOpponent(); //Gets opponent with the least life
|
||||
public static Player choosePreferredDefenderPlayer(Player ai) {
|
||||
Player defender = ai.getWeakestOpponent(); //Concentrate on opponent within easy kill range
|
||||
|
||||
if (defender.getLife() < 8) { //Concentrate on opponent within easy kill range
|
||||
return defender;
|
||||
} else { //Otherwise choose a random opponent to ensure no ganging up on players
|
||||
defender = ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
|
||||
if (defender.getLife() > 8) { //Otherwise choose a random opponent to ensure no ganging up on players
|
||||
// TODO should we cache the random for each turn? some functions like shouldPumpCard base their decisions on the assumption who will be attacked
|
||||
return ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
|
||||
}
|
||||
return defender;
|
||||
}
|
||||
@@ -624,7 +623,7 @@ public class AiAttackController {
|
||||
int totalCombatDamage = ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, opp) + trampleDamage;
|
||||
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)) {
|
||||
return true;
|
||||
}
|
||||
@@ -919,7 +918,7 @@ public class AiAttackController {
|
||||
// find the potential damage ratio the AI can cause
|
||||
double humanLifeToDamageRatio = 1000000;
|
||||
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
|
||||
|
||||
@@ -1102,6 +1102,8 @@ public class AiController {
|
||||
}
|
||||
|
||||
public CardCollection getCardsToDiscard(int min, final int max, final CardCollection validCards, final SpellAbility sa) {
|
||||
String logic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
if (validCards.size() < min) {
|
||||
return null;
|
||||
}
|
||||
@@ -1111,11 +1113,16 @@ public class AiController {
|
||||
int count = 0;
|
||||
if (sa != null) {
|
||||
sourceCard = sa.getHostCard();
|
||||
if ("Always".equals(sa.getParam("AILogic")) && !validCards.isEmpty()) {
|
||||
if ("Always".equals(logic) && !validCards.isEmpty()) {
|
||||
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);
|
||||
} else if ("DiscardCMCX".equals(sa.getParam("AILogic"))) {
|
||||
} else if ("DiscardCMCX".equals(logic)) {
|
||||
final int cmc = sa.getXManaCostPaid();
|
||||
CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc));
|
||||
if (discards.isEmpty()) {
|
||||
|
||||
@@ -188,7 +188,6 @@ public class ComputerUtil {
|
||||
final Card source = sa.getHostCard();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
|
||||
// Play higher costing spells first?
|
||||
final Cost cost = sa.getPayCosts();
|
||||
|
||||
@@ -213,7 +212,8 @@ public class ComputerUtil {
|
||||
if (unless != null && !unless.endsWith(">")) {
|
||||
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 (amount > usableManaSources) {
|
||||
@@ -1068,9 +1068,6 @@ public class ComputerUtil {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (card.isEquipment() && buffedcard.isCreature() && CombatUtil.canAttack(buffedcard, ComputerUtil.getOpponentFor(ai))) {
|
||||
return true;
|
||||
}
|
||||
if (card.isCreature()) {
|
||||
if (buffedcard.hasKeyword(Keyword.SOULBOND) && !buffedcard.isPaired()) {
|
||||
return true;
|
||||
@@ -1093,8 +1090,8 @@ public class ComputerUtil {
|
||||
|
||||
} // BuffedBy
|
||||
|
||||
// get all cards the human controls with AntiBuffedBy
|
||||
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
||||
// there's a good chance AI will attack weak target
|
||||
final CardCollectionView antibuffed = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
|
||||
for (Card buffedcard : antibuffed) {
|
||||
if (buffedcard.hasSVar("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
|
||||
*/
|
||||
public static boolean shouldCastLessThanMax(final Player ai, final Card source) {
|
||||
boolean ret = true;
|
||||
if (source.getManaCost().countX() > 0) {
|
||||
// If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by mana available.
|
||||
return ret;
|
||||
} else {
|
||||
if (source.getXManaCostPaid() > 0) {
|
||||
// If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by payment resources available.
|
||||
return true;
|
||||
}
|
||||
if (aiLifeInDanger(ai, false, 0)) {
|
||||
// Otherwise, if life is possibly in danger, then this is fine.
|
||||
Combat combat = new Combat(ComputerUtil.getOpponentFor(ai));
|
||||
CardCollectionView attackers = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
|
||||
for (Card att : attackers) {
|
||||
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
|
||||
combat.addAttacker(att, ComputerUtil.getOpponentFor(att.getController()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
AiBlockController aiBlock = new AiBlockController(ai);
|
||||
aiBlock.assignBlockersForCombat(combat);
|
||||
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
// Otherwise, return false. Do not play now.
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
// do not play now.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1266,8 +1252,8 @@ public class ComputerUtil {
|
||||
}
|
||||
}
|
||||
|
||||
// get all cards the human controls with AntiBuffedBy
|
||||
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
||||
// there's a good chance AI will attack weak target
|
||||
final CardCollectionView antibuffed = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
|
||||
for (Card buffedcard : antibuffed) {
|
||||
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
||||
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
||||
@@ -1463,7 +1449,7 @@ public class ComputerUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int possibleNonCombatDamage(Player ai) {
|
||||
public static int possibleNonCombatDamage(Player ai, Player enemy) {
|
||||
int damage = 0;
|
||||
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
||||
all.addAll(ai.getCardsActivableInExternalZones(true));
|
||||
@@ -1483,7 +1469,6 @@ public class ComputerUtil {
|
||||
if (tgt == null) {
|
||||
continue;
|
||||
}
|
||||
final Player enemy = ComputerUtil.getOpponentFor(ai);
|
||||
if (!sa.canTarget(enemy)) {
|
||||
continue;
|
||||
}
|
||||
@@ -2346,7 +2331,7 @@ public class ComputerUtil {
|
||||
}
|
||||
}
|
||||
else if (logic.equals("ChosenLandwalk")) {
|
||||
for (Card c : ComputerUtil.getOpponentFor(ai).getLandsInPlay()) {
|
||||
for (Card c : AiAttackController.choosePreferredDefenderPlayer(ai).getLandsInPlay()) {
|
||||
for (String t : c.getType()) {
|
||||
if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) {
|
||||
chosen = t;
|
||||
@@ -2364,7 +2349,7 @@ public class ComputerUtil {
|
||||
else if (kindOfType.equals("Land")) {
|
||||
if (logic != null) {
|
||||
if (logic.equals("ChosenLandwalk")) {
|
||||
for (Card c : ComputerUtil.getOpponentFor(ai).getLandsInPlay()) {
|
||||
for (Card c : AiAttackController.choosePreferredDefenderPlayer(ai).getLandsInPlay()) {
|
||||
for (String t : c.getType().getLandTypes()) {
|
||||
if (!invalidTypes.contains(t)) {
|
||||
chosen = t;
|
||||
@@ -2399,15 +2384,18 @@ public class ComputerUtil {
|
||||
case "Torture":
|
||||
return "Torture";
|
||||
case "GraceOrCondemnation":
|
||||
return ai.getCreaturesInPlay().size() > ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size() ? "Grace"
|
||||
: "Condemnation";
|
||||
List<ZoneType> graceZones = new ArrayList<ZoneType>();
|
||||
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":
|
||||
CardCollection cardsInPlay = CardLists
|
||||
.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
|
||||
CardCollection cardsInPlay = CardLists.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
|
||||
CardCollection humanlist = CardLists.filterControlledBy(cardsInPlay, ai.getOpponents());
|
||||
CardCollection computerlist = CardLists.filterControlledBy(cardsInPlay, ai);
|
||||
return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard
|
||||
.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
|
||||
CardCollection computerlist = ai.getCreaturesInPlay();
|
||||
return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
|
||||
case "Judgment":
|
||||
if (votes.isEmpty()) {
|
||||
CardCollection list = new CardCollection();
|
||||
@@ -2934,23 +2922,6 @@ public class ComputerUtil {
|
||||
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) {
|
||||
CardCollection creats = p.getCreaturesInPlay();
|
||||
int count = 0;
|
||||
@@ -3033,7 +3004,7 @@ public class ComputerUtil {
|
||||
// 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.
|
||||
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
|
||||
Combat combat = new Combat(opponent);
|
||||
boolean containsAttacker = false;
|
||||
@@ -3059,6 +3030,7 @@ public class ComputerUtil {
|
||||
if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@@ -550,7 +550,7 @@ public class ComputerUtilCard {
|
||||
*/
|
||||
public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) {
|
||||
AiBlockController aiBlk = new AiBlockController(ai);
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
Combat combat = new Combat(opp);
|
||||
//Use actual attackers if available, else consider all possible attackers
|
||||
Combat currentCombat = ai.getGame().getCombat();
|
||||
|
||||
@@ -97,34 +97,39 @@ public class ComputerUtilCombat {
|
||||
* canAttackNextTurn.
|
||||
* </p>
|
||||
*
|
||||
* @param atacker
|
||||
* @param attacker
|
||||
* a {@link forge.game.card.Card} object.
|
||||
* @param defender
|
||||
* the defending {@link GameEntity}.
|
||||
* @return a boolean.
|
||||
*/
|
||||
public static boolean canAttackNextTurn(final Card atacker, final GameEntity defender) {
|
||||
if (!atacker.isCreature()) {
|
||||
public static boolean canAttackNextTurn(final Card attacker, final GameEntity defender) {
|
||||
if (!attacker.isCreature()) {
|
||||
return false;
|
||||
}
|
||||
if (!CombatUtil.canAttackNextTurn(atacker, defender)) {
|
||||
if (!CombatUtil.canAttackNextTurn(attacker, defender)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (final KeywordInterface inst : atacker.getKeywords()) {
|
||||
for (final KeywordInterface inst : attacker.getKeywords()) {
|
||||
final String keyword = inst.getOriginal();
|
||||
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
|
||||
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)) {
|
||||
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
|
||||
return !atacker.isTapped() || Untap.canUntap(atacker);
|
||||
} // canAttackNextTurn(Card, GameEntity)
|
||||
return !attacker.isTapped() || Untap.canUntap(attacker);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
|
||||
@@ -34,6 +34,7 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.BidLife, BidLifeAi.class)
|
||||
.put(ApiType.Bond, BondAi.class)
|
||||
.put(ApiType.Branch, AlwaysPlayAi.class)
|
||||
.put(ApiType.Camouflage, ChooseCardAi.class)
|
||||
.put(ApiType.ChangeCombatants, ChangeCombatantsAi.class)
|
||||
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
||||
.put(ApiType.ChangeX, AlwaysPlayAi.class)
|
||||
|
||||
@@ -22,7 +22,6 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
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"));
|
||||
if (list.isEmpty()) {
|
||||
@@ -40,6 +39,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
|
||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,8 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
||||
} else {
|
||||
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 {
|
||||
|
||||
@@ -13,18 +13,21 @@ public class BalanceAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
String logic = sa.getParam("AILogic");
|
||||
|
||||
int diff = 0;
|
||||
// TODO Add support for multiplayer logic
|
||||
final Player opp = aiPlayer.getWeakestOpponent();
|
||||
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
|
||||
Player opp = aiPlayer.getWeakestOpponent();
|
||||
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)) {
|
||||
// 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() -
|
||||
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());
|
||||
}
|
||||
else if ("BalancePermanents".equals(logic)) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
@@ -24,7 +25,7 @@ public class BidLifeAi extends SpellAbilityAi {
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
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);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.AiBlockController;
|
||||
import forge.ai.AiCardMemory;
|
||||
import forge.ai.AiController;
|
||||
@@ -263,7 +264,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
final Card source = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
ZoneType origin = null;
|
||||
final Player opponent = ai.getWeakestOpponent();
|
||||
final Player opponent = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||
|
||||
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
|
||||
// make sure this will actually do something:
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Player opp = aiPlayer.getWeakestOpponent();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||
if (tgt != null && tgt.canTgtPlayer()) {
|
||||
boolean isCurse = sa.isCurse();
|
||||
if (isCurse && sa.canTarget(opp)) {
|
||||
@@ -530,7 +531,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
Iterable<Player> pDefined;
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if ((tgt != null) && tgt.canTgtPlayer()) {
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
if (sa.isCurse()) {
|
||||
if (sa.canTarget(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?
|
||||
sa.setXManaCostPaid(xPay);
|
||||
// TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
|
||||
}
|
||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
|
||||
|
||||
@@ -913,9 +913,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
if (sa.isSpell()) {
|
||||
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")) {
|
||||
//System.out.println("isPreferredTarget att " + list);
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
@@ -927,7 +925,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
//System.out.println("isPreferredTarget ok " + list);
|
||||
}
|
||||
|
||||
if (list.size() < sa.getMinTargets()) {
|
||||
@@ -1482,9 +1479,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
|
||||
if ("DeathgorgeScavenger".equals(logic)) {
|
||||
return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa);
|
||||
} else if ("ExtraplanarLens".equals(logic)) {
|
||||
}
|
||||
if ("ExtraplanarLens".equals(logic)) {
|
||||
return SpecialCardAi.ExtraplanarLens.consider(ai, sa);
|
||||
} else if ("ExileCombatThreat".equals(logic)) {
|
||||
}
|
||||
if ("ExileCombatThreat".equals(logic)) {
|
||||
return doExileCombatThreatLogic(ai, sa);
|
||||
}
|
||||
|
||||
@@ -1984,11 +1983,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
||||
}
|
||||
|
||||
if (toPay == 0) {
|
||||
canBeSaved.add(potentialTgt);
|
||||
}
|
||||
|
||||
if (toPay <= usableManaSources) {
|
||||
if (toPay == 0 || toPay <= usableManaSources) {
|
||||
canBeSaved.add(potentialTgt);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
@@ -114,7 +115,7 @@ public class ChooseCardAi extends SpellAbilityAi {
|
||||
return !CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host, sa).isEmpty();
|
||||
} else if (aiLogic.equals("Duneblast")) {
|
||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||
CardCollection oppCreatures = ai.getWeakestOpponent().getCreaturesInPlay();
|
||||
CardCollection oppCreatures = AiAttackController.choosePreferredDefenderPlayer(ai).getCreaturesInPlay();
|
||||
aiCreatures = CardLists.getNotKeyword(aiCreatures, 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 forge.StaticData;
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpecialCardAi;
|
||||
@@ -44,7 +45,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
sa.getTargets().add(ai.getWeakestOpponent());
|
||||
sa.getTargets().add(AiAttackController.choosePreferredDefenderPlayer(ai));
|
||||
} else {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -16,7 +17,7 @@ public class ChooseEvenOddAi extends SpellAbilityAi {
|
||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
Player opp = aiPlayer.getWeakestOpponent();
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -16,7 +17,7 @@ public class ChooseNumberAi extends SpellAbilityAi {
|
||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
Player opp = aiPlayer.getWeakestOpponent();
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
@@ -54,7 +55,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
|
||||
@@ -3,6 +3,7 @@ package forge.ai.ability;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
@@ -29,7 +30,7 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
||||
sa.resetTargets();
|
||||
|
||||
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
|
||||
// purpose
|
||||
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.Lists;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
@@ -26,9 +27,6 @@ import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class DebuffAi extends SpellAbilityAi {
|
||||
// *************************************************************************
|
||||
// ***************************** Debuff ************************************
|
||||
// *************************************************************************
|
||||
|
||||
@Override
|
||||
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)) {
|
||||
Card t = null;
|
||||
// boolean goodt = false;
|
||||
|
||||
if (list.isEmpty()) {
|
||||
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.
|
||||
*/
|
||||
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);
|
||||
if (!list.isEmpty()) {
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.hasAnyKeyword(kws); // don't add duplicate negative
|
||||
// keywords
|
||||
return c.hasAnyKeyword(kws); // don't add duplicate negative keywords
|
||||
}
|
||||
});
|
||||
}
|
||||
return list;
|
||||
} // getCurseCreatures()
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -216,7 +212,7 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
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 Card source = sa.getHostCard();
|
||||
|
||||
@@ -242,8 +238,7 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO - if forced targeting, just pick something without the given
|
||||
// keyword
|
||||
// TODO - if forced targeting, just pick something without the given keyword
|
||||
Card c;
|
||||
if (CardLists.getNotType(forced, "Creature").size() == 0) {
|
||||
c = ComputerUtilCard.getWorstCreatureAI(forced);
|
||||
|
||||
@@ -70,12 +70,12 @@ public class DestroyAllAi extends SpellAbilityAi {
|
||||
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 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")) {
|
||||
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));
|
||||
}
|
||||
|
||||
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield),
|
||||
valid.split(","), source.getController(), source, sa);
|
||||
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
|
||||
source.getController(), source, sa);
|
||||
// TODO should probably sort results when targeted to use on biggest threat instead of first match
|
||||
for (Player opponent: ai.getOpponents()) {
|
||||
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
||||
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
||||
|
||||
opplist = CardLists.filter(opplist, predicate);
|
||||
ailist = CardLists.filter(ailist, predicate);
|
||||
@@ -135,8 +135,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if only creatures are affected evaluate both lists and pass only if
|
||||
// human creatures are more valuable
|
||||
// if only creatures are affected evaluate both lists and pass only if human creatures are more valuable
|
||||
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
|
||||
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
|
||||
return true;
|
||||
@@ -167,7 +166,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
||||
return false;
|
||||
} // only lands involved
|
||||
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;
|
||||
}
|
||||
// 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 false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import java.util.Map;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
@@ -32,7 +33,7 @@ public class DigAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Game game = ai.getGame();
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
final Card host = sa.getHostCard();
|
||||
Player libraryOwner = ai;
|
||||
|
||||
@@ -120,7 +121,7 @@ public class DigAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final SpellAbility root = sa.getRootAbility();
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (mandatory && sa.canTarget(opp)) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
@@ -19,7 +20,7 @@ public class DigMultipleAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Game game = ai.getGame();
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
final Card host = sa.getHostCard();
|
||||
Player libraryOwner = ai;
|
||||
|
||||
@@ -77,7 +78,7 @@ public class DigMultipleAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (mandatory && sa.canTarget(opp)) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
@@ -31,10 +32,8 @@ public class DigUntilAi extends SpellAbilityAi {
|
||||
chance = 1;
|
||||
}
|
||||
|
||||
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||
|
||||
Player libraryOwner = ai;
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
|
||||
if ("DontMillSelf".equals(logic)) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (sa.isCurse()) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
@@ -60,11 +61,9 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
if (players.get(0) == ai) {
|
||||
// the ai should only be using something like this if he has
|
||||
// few cards in hand,
|
||||
// cards like this better have a good drawback to be in the
|
||||
// AIs deck
|
||||
// cards like this better have a good drawback to be in the AIs deck
|
||||
} else {
|
||||
// defined to the human, so that's fine as long the human
|
||||
// has cards
|
||||
// defined to the human, so that's fine as long the human has cards
|
||||
if (!humanHasHand) {
|
||||
return false;
|
||||
}
|
||||
@@ -170,7 +169,7 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
if (!discardTargetAI(ai, sa)) {
|
||||
if (mandatory && sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
|
||||
@@ -23,8 +23,7 @@ public class DrainManaAi extends SpellAbilityAi {
|
||||
|
||||
if (tgt == null) {
|
||||
// assume we are looking to tap human's stuff
|
||||
// TODO - check for things with untap abilities, and don't tap
|
||||
// those.
|
||||
// TODO - check for things with untap abilities, and don't tap those.
|
||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
|
||||
if (!defined.contains(opp)) {
|
||||
|
||||
@@ -29,11 +29,10 @@ public class GameLossAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
// Phage the Untouchable
|
||||
// (Final Fortune would need to attach it's delayed trigger to a
|
||||
// specific turn, which can't be done yet)
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = ai.getGame().getCombat().getDefenderPlayerByAttacker(sa.getHostCard());
|
||||
|
||||
if (!mandatory && opp.cantLose()) {
|
||||
return false;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -19,7 +20,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
final int myLife = aiPlayer.getLife();
|
||||
Player opponent = aiPlayer.getWeakestOpponent();
|
||||
Player opponent = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||
final int hLife = opponent.getLife();
|
||||
|
||||
if (!aiPlayer.canGainLife()) {
|
||||
@@ -75,7 +76,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
|
||||
final boolean mandatory) {
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.AiProps;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
@@ -154,7 +155,7 @@ public class LifeExchangeVariantAi extends SpellAbilityAi {
|
||||
final boolean mandatory) {
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
||||
|
||||
@@ -17,7 +17,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final int myLife = ai.getLife();
|
||||
final Player opponent = ai.getWeakestOpponent();
|
||||
final Player opponent = ai.getStrongestOpponent();
|
||||
final int hlife = opponent.getLife();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
|
||||
@@ -36,8 +36,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO handle proper calculation of X values based on Cost and what
|
||||
// would be paid
|
||||
// TODO handle proper calculation of X values based on Cost and what would be paid
|
||||
int amount;
|
||||
// we shouldn't have to worry too much about PayX for SetLife
|
||||
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
@@ -58,11 +57,9 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
sa.getTargets().add(opponent);
|
||||
// if we can only target the human, and the Human's life
|
||||
// would
|
||||
// go up, don't play it.
|
||||
// would go up, don't play it.
|
||||
// possibly add a combo here for Magister Sphinx and
|
||||
// Higedetsu's
|
||||
// (sp?) Second Rite
|
||||
// Higedetsu's (sp?) Second Rite
|
||||
if ((amount > hlife) || !opponent.canLoseLife()) {
|
||||
return false;
|
||||
}
|
||||
@@ -81,8 +78,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
if (sa.getParam("Defined").equals("Player")) {
|
||||
if (amount == 0) {
|
||||
return false;
|
||||
} else if (myLife > amount) { // will decrease computer's
|
||||
// life
|
||||
} else if (myLife > amount) { // will decrease computer's life
|
||||
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
|
||||
return false;
|
||||
}
|
||||
@@ -104,7 +100,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final int myLife = ai.getLife();
|
||||
final Player opponent = ai.getWeakestOpponent();
|
||||
final Player opponent = ai.getStrongestOpponent();
|
||||
final int hlife = opponent.getLife();
|
||||
final Card source = sa.getHostCard();
|
||||
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 modifying how much life is gained, this needs to
|
||||
// be handled better
|
||||
// if the Target is modifying how much life is gained, this needs to be handled better
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
|
||||
@@ -30,13 +30,12 @@ public class PowerExchangeAi extends SpellAbilityAi {
|
||||
sa.resetTargets();
|
||||
|
||||
List<Card> list =
|
||||
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
// AI won't try to grab cards that are filtered out of AI decks on
|
||||
// purpose
|
||||
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 purpose
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
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);
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
@@ -201,7 +202,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
final Game game = ai.getGame();
|
||||
final Combat combat = game.getCombat();
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
final int newPower = card.getNetCombatDamage() + attack;
|
||||
//int defense = getNumDefense(sa);
|
||||
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)) {
|
||||
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 Player opp = ai.getStrongestOpponent();
|
||||
|
||||
if (tgt != null && sa.canTarget(opp) && sa.hasParam("IsCurse")) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
@@ -85,6 +73,18 @@ public class PumpAllAi extends PumpAiBase {
|
||||
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()) {
|
||||
return pumpAgainstRemoval(ai, sa, comp);
|
||||
}
|
||||
@@ -139,8 +139,7 @@ public class PumpAllAi extends PumpAiBase {
|
||||
return true;
|
||||
}
|
||||
|
||||
// evaluate both lists and pass only if human creatures are more
|
||||
// valuable
|
||||
// evaluate both lists and pass only if human creatures are more valuable
|
||||
return (ComputerUtilCard.evaluateCreatureList(comp) + 200) < ComputerUtilCard.evaluateCreatureList(human);
|
||||
} // end Curse
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
@@ -14,7 +15,7 @@ public class RepeatAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
if (!opp.canBeTargetedBy(sa)) {
|
||||
@@ -44,9 +45,8 @@ public class RepeatAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
|
||||
@@ -20,9 +20,6 @@ import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class SacrificeAi extends SpellAbilityAi {
|
||||
// **************************************************************
|
||||
// *************************** Sacrifice ***********************
|
||||
// **************************************************************
|
||||
|
||||
@Override
|
||||
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:
|
||||
// When ETB, sacrifice a creature. Check to see if the AI has something
|
||||
// to sacrifice
|
||||
// When ETB, sacrifice a creature. Check to see if the AI has something to sacrifice
|
||||
|
||||
// Eventually, we can call the trigger of ETB abilities with not
|
||||
// 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) {
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final boolean destroy = sa.hasParam("Destroy");
|
||||
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = ai.getStrongestOpponent();
|
||||
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
@@ -109,8 +104,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
||||
sa.setXManaCostPaid(Math.min(ComputerUtilCost.getMaxXValue(sa, ai), amount));
|
||||
}
|
||||
|
||||
final int half = (amount / 2) + (amount % 2); // Half of amount
|
||||
// rounded up
|
||||
final int half = (amount / 2) + (amount % 2); // Half of amount rounded up
|
||||
|
||||
// If the Human has at least half rounded up of the amount to be
|
||||
// sacrificed, cast the spell
|
||||
@@ -130,8 +124,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
||||
// If Sacrifice hits both players:
|
||||
// Only cast it if Human has 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
|
||||
// worse than his "worst" valid
|
||||
// TODO: Cast if the type is favorable: my "worst" valid is worse than his "worst" valid
|
||||
final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1";
|
||||
int amount = AbilityUtils.calculateAmount(source, num, sa);
|
||||
|
||||
|
||||
@@ -2,17 +2,12 @@ package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
public class SacrificeAllAi extends SpellAbilityAi {
|
||||
|
||||
@@ -22,22 +17,7 @@ public class SacrificeAllAi extends SpellAbilityAi {
|
||||
// based on what the expected targets could be
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
String valid = "";
|
||||
|
||||
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);
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
if (abCost != null) {
|
||||
// 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
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,6 @@ public class TapAi extends TapAiBase {
|
||||
// Set PayX here to maximum value.
|
||||
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
|
||||
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
|
||||
// TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.List;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
@@ -109,7 +110,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
* @return a boolean.
|
||||
*/
|
||||
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();
|
||||
CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||
tapList = CardLists.getTargetableCards(tapList, sa);
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
@@ -28,7 +29,7 @@ public class TwoPilesAi extends SpellAbilityAi {
|
||||
valid = sa.getParam("ValidCards");
|
||||
}
|
||||
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
|
||||
@@ -56,7 +56,6 @@ public class UnattachAllAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Card card = sa.getHostCard();
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
// Check if there are any valid targets
|
||||
List<GameObject> targets = new ArrayList<>();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
@@ -66,8 +65,8 @@ public class UnattachAllAi extends SpellAbilityAi {
|
||||
|
||||
if (!mandatory && card.isEquipment() && !targets.isEmpty()) {
|
||||
Card newTarget = (Card) targets.get(0);
|
||||
//don't equip human creatures
|
||||
if (newTarget.getController().equals(opp)) {
|
||||
//don't equip opponent creatures
|
||||
if (!newTarget.getController().equals(ai)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.util.Map;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
@@ -130,7 +131,8 @@ public class UntapAi extends SpellAbilityAi {
|
||||
Player targetController = ai;
|
||||
|
||||
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);
|
||||
@@ -149,8 +151,7 @@ public class UntapAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
CardCollection untapList = targetUntapped ? list : CardLists.filter(list, Presets.TAPPED);
|
||||
// filter out enchantments and planeswalkers, their tapped state doesn't
|
||||
// matter.
|
||||
// filter out enchantments and planeswalkers, their tapped state doesn't matter.
|
||||
final String[] tappablePermanents = {"Creature", "Land", "Artifact"};
|
||||
untapList = CardLists.getValidCards(untapList, tappablePermanents, source.getController(), source, sa);
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ public enum ApiType {
|
||||
Block (BlockEffect.class),
|
||||
Bond (BondEffect.class),
|
||||
Branch (BranchEffect.class),
|
||||
Camouflage (CamouflageEffect.class),
|
||||
ChangeCombatants (ChangeCombatantsEffect.class),
|
||||
ChangeTargets (ChangeTargetsEffect.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.FirstTime, firstDiscard);
|
||||
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);
|
||||
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) {
|
||||
return currentState.hasTrigger(t);
|
||||
|
||||
@@ -653,9 +653,16 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
}
|
||||
if (combat.isPlayerAttacked(p)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
else { continue; }
|
||||
|
||||
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() {
|
||||
return getOpponents().min(PlayerPredicates.compareByLife());
|
||||
}
|
||||
public final Player getStrongestOpponent() {
|
||||
return getOpponents().max(PlayerPredicates.compareByLife());
|
||||
}
|
||||
|
||||
public boolean isOpponentOf(Player other) {
|
||||
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),
|
||||
DamageDone(ReplaceDamage.class),
|
||||
DealtDamage(ReplaceDealtDamage.class),
|
||||
DeclareBlocker(ReplaceDeclareBlocker.class),
|
||||
Destroy(ReplaceDestroy.class),
|
||||
Discard(ReplaceDiscard.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.
|
||||
SVar:DBDraw:DB$ Draw | NumCards$ 1 | Defined$ You
|
||||
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.
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
Name:Hellion Eruption
|
||||
ManaCost:5 R
|
||||
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:X:Remembered$Amount
|
||||
AI:RemoveDeck:All
|
||||
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.
|
||||
|
||||
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
|
||||
#BondEffect.java
|
||||
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
|
||||
lblChooseDefenderToAttackWithCard=Welchen Verteidiger mit {0} angreifen?
|
||||
#ChangeTargetsEffect.java
|
||||
|
||||
@@ -1750,6 +1750,9 @@ lblDoYouWantTopBid=Do you want to top bid? Current Bid \=
|
||||
lblTopBidWithValueLife=topped bid with {0} life
|
||||
#BondEffect.java
|
||||
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
|
||||
lblChooseDefenderToAttackWithCard=Choose which defender to attack with {0}
|
||||
#ChangeTargetsEffect.java
|
||||
|
||||
@@ -1750,6 +1750,9 @@ lblDoYouWantTopBid=¿Quieres hacer una puja máxima? Puja actual \=
|
||||
lblTopBidWithValueLife=puja más alta con {0} de vida
|
||||
#BondEffect.java
|
||||
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
|
||||
lblChooseDefenderToAttackWithCard=Elige con qué defensor atacar con {0}
|
||||
#ChangeTargetsEffect.java
|
||||
|
||||
@@ -1750,6 +1750,9 @@ lblDoYouWantTopBid=Do you want to top bid? Current Bid \=
|
||||
lblTopBidWithValueLife=topped bid with {0} life
|
||||
#BondEffect.java
|
||||
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
|
||||
lblChooseDefenderToAttackWithCard=Choose which defender to attack with {0}
|
||||
#ChangeTargetsEffect.java
|
||||
|
||||
@@ -1750,6 +1750,9 @@ lblDoYouWantTopBid=競りの点数を上げますか? 現在の点数 \=
|
||||
lblTopBidWithValueLife={0}点のライフで競りの点数をつけた
|
||||
#BondEffect.java
|
||||
lblSelectACardPair=組にしたいカードを選ぶ
|
||||
#CamouflageEffect.java
|
||||
lblChooseBlockerForAttacker={0}をブロックするクリーチャーを選ぶ
|
||||
lblChooseBlockersForPile={0}番の束に入れるクリーチャーを選ぶ(空にできる)
|
||||
#ChangeCombatantsEffect.java
|
||||
lblChooseDefenderToAttackWithCard={0}が攻撃する対象を選ぶ
|
||||
#ChangeTargetsEffect.java
|
||||
|
||||
@@ -117,8 +117,8 @@ cbpCounterDisplayLocation=计数器显示区域
|
||||
cbpGraveyardOrdering=允许指衍生物进入墓地
|
||||
lblAltLifeDisplay=备用牌手布局(横向模式)
|
||||
nlAltLifeDisplay=启用备用牌手布局以显示玩家的生命以及中毒,能量和经验指示物。
|
||||
lblPreferredArt=Preferred Art
|
||||
nlPreferredArt=Sets the preferred art for cards.
|
||||
lblPreferredArt=首选卡图
|
||||
nlPreferredArt=设置牌张的首选卡图。
|
||||
Troubleshooting=故障排除
|
||||
GeneralConfiguration=常规配置
|
||||
lblPlayerName=玩家名称
|
||||
@@ -1750,6 +1750,9 @@ lblDoYouWantTopBid=你想要喊更高的价? 现在价钱 \=
|
||||
lblTopBidWithValueLife=最高喊价为{0}生命
|
||||
#BondEffect.java
|
||||
lblSelectACardPair=选择要组成搭档的牌
|
||||
#CamouflageEffect.java
|
||||
lblChooseBlockerForAttacker=Choose a creature to block {0}
|
||||
lblChooseBlockersForPile=Choose creatures to put in pile {0} (can be empty)
|
||||
#ChangeCombatantsEffect.java
|
||||
lblChooseDefenderToAttackWith=选择守军进行进攻
|
||||
#ChangeTargetsEffect.java
|
||||
@@ -1933,9 +1936,9 @@ lblTapOrUntapTarget=横置还是重置{0}?
|
||||
#TwoPilesEffect.java
|
||||
lblSelectCardForFaceDownPile=选择一个面朝下的堆
|
||||
lblDivideCardIntoTwoPiles=将牌分为两堆
|
||||
lblSelectCardForLeftPile=Select cards for the left pile
|
||||
lblLeftPile=Left pile
|
||||
lblRightPile=Right pile
|
||||
lblSelectCardForLeftPile=选择左堆中的牌张
|
||||
lblLeftPile=左堆
|
||||
lblRightPile=右堆
|
||||
lblChoosesPile=选择堆
|
||||
lblEmptyPile=空堆
|
||||
#UntapEffect.java
|
||||
@@ -1953,7 +1956,7 @@ lblViewAll=查看所有牌
|
||||
lblSetupGame=设定游戏状态
|
||||
lblDumpGame=转储游戏状态
|
||||
lblTutor=导师牌
|
||||
lblRollbackPhase=Rollback Phase
|
||||
lblRollbackPhase=回滚阶段
|
||||
lblAddCounterPermanent=向牌添加指示物
|
||||
lblSubCounterPermanent=从牌减少指示物
|
||||
lblTapPermanent=横置永久物
|
||||
|
||||
Reference in New Issue
Block a user