Merge remote-tracking branch 'upstream/master' into collector-number-in-card-list-and-card-db-refactoring

This commit is contained in:
leriomaggio
2021-07-06 08:36:40 +01:00
1192 changed files with 9811 additions and 4628 deletions

1
.gitignore vendored
View File

@@ -17,6 +17,7 @@
.vscode/settings.json .vscode/settings.json
.vscode/launch.json .vscode/launch.json
.factorypath
# Ignore NetBeans config files # Ignore NetBeans config files

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>1.6.40-SNAPSHOT</version> <version>1.6.43-SNAPSHOT</version>
</parent> </parent>
<artifactId>forge-ai</artifactId> <artifactId>forge-ai</artifactId>

View File

@@ -18,6 +18,7 @@
package forge.ai; package forge.ai;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
@@ -43,6 +44,7 @@ import forge.game.combat.GlobalAttackRestrictions;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordInterface;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
@@ -391,7 +393,7 @@ public class AiAttackController {
//Calculate the amount of creatures necessary //Calculate the amount of creatures necessary
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
if (!this.doesHumanAttackAndWin(ai, i)) { if (!doesHumanAttackAndWin(ai, i)) {
blockersNeeded = i; blockersNeeded = i;
break; break;
} }
@@ -635,7 +637,7 @@ public class AiAttackController {
if (defs.size() == 1) { if (defs.size() == 1) {
return defs.getFirst(); return defs.getFirst();
} }
Player prefDefender = (Player) (defs.contains(this.defendingOpponent) ? this.defendingOpponent : defs.get(0)); GameEntity prefDefender = defs.contains(this.defendingOpponent) ? this.defendingOpponent : defs.get(0);
// Attempt to see if there's a defined entity that must be attacked strictly this turn... // Attempt to see if there's a defined entity that must be attacked strictly this turn...
GameEntity entity = ai.getMustAttackEntityThisTurn(); GameEntity entity = ai.getMustAttackEntityThisTurn();
@@ -659,7 +661,8 @@ public class AiAttackController {
// 2. attack planeswalkers // 2. attack planeswalkers
List<Card> pwDefending = c.getDefendingPlaneswalkers(); List<Card> pwDefending = c.getDefendingPlaneswalkers();
if (!pwDefending.isEmpty()) { if (!pwDefending.isEmpty()) {
return pwDefending.get(0); final Card pwNearUlti = ComputerUtilCard.getBestPlaneswalkerToDamage(pwDefending);
return pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending);
} else { } else {
return prefDefender; return prefDefender;
} }
@@ -695,14 +698,14 @@ public class AiAttackController {
tradeIfLowerLifePressure = aic.getBooleanProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE); tradeIfLowerLifePressure = aic.getBooleanProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE);
} }
final boolean bAssault = this.doAssault(ai); final boolean bAssault = doAssault(ai);
// TODO: detect Lightmine Field by presence of a card with a specific trigger // TODO: detect Lightmine Field by presence of a card with a specific trigger
final boolean lightmineField = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Lightmine Field"); final boolean lightmineField = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Lightmine Field");
// TODO: detect Season of the Witch by presence of a card with a specific trigger // TODO: detect Season of the Witch by presence of a card with a specific trigger
final boolean seasonOfTheWitch = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Season of the Witch"); final boolean seasonOfTheWitch = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Season of the Witch");
// Determine who will be attacked // Determine who will be attacked
GameEntity defender = this.chooseDefender(combat, bAssault); GameEntity defender = chooseDefender(combat, bAssault);
List<Card> attackersLeft = new ArrayList<>(this.attackers); List<Card> attackersLeft = new ArrayList<>(this.attackers);
// TODO probably use AttackConstraints instead of only GlobalAttackRestrictions? // TODO probably use AttackConstraints instead of only GlobalAttackRestrictions?
@@ -1090,17 +1093,18 @@ public class AiAttackController {
// if enough damage: switch to next planeswalker or player // if enough damage: switch to next planeswalker or player
if (damage >= pw.getCounters(CounterEnumType.LOYALTY)) { if (damage >= pw.getCounters(CounterEnumType.LOYALTY)) {
List<Card> pwDefending = combat.getDefendingPlaneswalkers(); List<Card> pwDefending = combat.getDefendingPlaneswalkers();
boolean found = false;
// look for next planeswalker // look for next planeswalker
for (Card walker : pwDefending) { for (Card walker : Lists.newArrayList(pwDefending)) {
if (combat.getAttackersOf(walker).isEmpty()) { if (!combat.getAttackersOf(walker).isEmpty()) {
defender = walker; pwDefending.remove(walker);
found = true;
break;
} }
} }
if (!found) { if (pwDefending.isEmpty()) {
defender = combat.getDefendingPlayers().get(0); defender = Collections.min(Lists.newArrayList(combat.getDefendingPlayers()), PlayerPredicates.compareByLife());
}
else {
final Card pwNearUlti = ComputerUtilCard.getBestPlaneswalkerToDamage(pwDefending);
defender = pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending);
} }
} }
} }
@@ -1354,8 +1358,7 @@ public class AiAttackController {
} }
// if card has a Exert Trigger which would target, // if card has a Exert Trigger which would target,
// but there are no creatures it can target, no need to exert with // but there are no creatures it can target, no need to exert with it
// it
boolean missTarget = false; boolean missTarget = false;
for (Trigger t : c.getTriggers()) { for (Trigger t : c.getTriggers()) {
if (!TriggerType.Exerted.equals(t.getMode())) { if (!TriggerType.Exerted.equals(t.getMode())) {

View File

@@ -816,7 +816,7 @@ public class AiController {
if(payCosts != null) { if(payCosts != null) {
ManaCost mana = payCosts.getTotalMana(); ManaCost mana = payCosts.getTotalMana();
if (mana != null) { if (mana != null) {
if(mana.countX() > 0) { if (mana.countX() > 0) {
// Set PayX here to maximum value. // Set PayX here to maximum value.
final int xPay = ComputerUtilCost.getMaxXValue(sa, player); final int xPay = ComputerUtilCost.getMaxXValue(sa, player);
if (xPay <= 0) { if (xPay <= 0) {
@@ -1819,7 +1819,7 @@ public class AiController {
// AI would play everything. But limits to one copy of (Leyline of Singularity) and (Gemstone Caverns) // AI would play everything. But limits to one copy of (Leyline of Singularity) and (Gemstone Caverns)
List<SpellAbility> result = Lists.newArrayList(); List<SpellAbility> result = Lists.newArrayList();
for(SpellAbility sa : usableFromOpeningHand) { for (SpellAbility sa : usableFromOpeningHand) {
// Is there a better way for the AI to decide this? // Is there a better way for the AI to decide this?
if (doTrigger(sa, false)) { if (doTrigger(sa, false)) {
result.add(sa); result.add(sa);
@@ -1830,7 +1830,7 @@ public class AiController {
SpellAbility saGemstones = null; SpellAbility saGemstones = null;
List<SpellAbility> toRemove = Lists.newArrayList(); List<SpellAbility> toRemove = Lists.newArrayList();
for(SpellAbility sa : result) { for (SpellAbility sa : result) {
String srcName = sa.getHostCard().getName(); String srcName = sa.getHostCard().getName();
if ("Gemstone Caverns".equals(srcName)) { if ("Gemstone Caverns".equals(srcName)) {
if (saGemstones == null) if (saGemstones == null)
@@ -2227,11 +2227,11 @@ public class AiController {
private boolean checkAiSpecificRestrictions(final SpellAbility sa) { private boolean checkAiSpecificRestrictions(final SpellAbility sa) {
// AI-specific restrictions specified as activation parameters in spell abilities // AI-specific restrictions specified as activation parameters in spell abilities
if (sa.hasParam("AILifeThreshold")) { if (sa.hasParam("AILifeThreshold")) {
return player.getLife() > Integer.parseInt(sa.getParam("AILifeThreshold")); return player.getLife() > Integer.parseInt(sa.getParam("AILifeThreshold"));
} }
return true; return true;
} }

View File

@@ -354,7 +354,6 @@ public class AiCostDecision extends CostDecisionMakerBase {
return PaymentDecision.number(c); return PaymentDecision.number(c);
} }
@Override @Override
public PaymentDecision visit(CostPutCardToLib cost) { public PaymentDecision visit(CostPutCardToLib cost) {
if (cost.payCostFromSource()) { if (cost.payCostFromSource()) {

View File

@@ -331,11 +331,14 @@ public class ComputerUtil {
} }
public static Card getCardPreference(final Player ai, final Card activate, final String pref, final CardCollection typeList) { public static Card getCardPreference(final Player ai, final Card activate, final String pref, final CardCollection typeList) {
return getCardPreference(ai, activate, pref, typeList, null);
}
public static Card getCardPreference(final Player ai, final Card activate, final String pref, final CardCollection typeList, SpellAbility sa) {
final Game game = ai.getGame(); final Game game = ai.getGame();
String prefDef = ""; String prefDef = "";
if (activate != null) { if (activate != null) {
prefDef = activate.getSVar("AIPreference"); prefDef = activate.getSVar("AIPreference");
final String[] prefGroups = activate.getSVar("AIPreference").split("\\|"); final String[] prefGroups = prefDef.split("\\|");
for (String prefGroup : prefGroups) { for (String prefGroup : prefGroups) {
final String[] prefValid = prefGroup.trim().split("\\$"); final String[] prefValid = prefGroup.trim().split("\\$");
if (prefValid[0].equals(pref) && !prefValid[1].startsWith("Special:")) { if (prefValid[0].equals(pref) && !prefValid[1].startsWith("Special:")) {
@@ -346,8 +349,8 @@ public class ComputerUtil {
for (String validItem : prefValid[1].split(",")) { for (String validItem : prefValid[1].split(",")) {
final CardCollection prefList = CardLists.getValidCards(typeList, validItem, activate.getController(), activate, null); final CardCollection prefList = CardLists.getValidCards(typeList, validItem, activate.getController(), activate, null);
int threshold = getAIPreferenceParameter(activate, "CreatureEvalThreshold"); int threshold = getAIPreferenceParameter(activate, "CreatureEvalThreshold", sa);
int minNeeded = getAIPreferenceParameter(activate, "MinCreaturesBelowThreshold"); int minNeeded = getAIPreferenceParameter(activate, "MinCreaturesBelowThreshold", sa);
if (threshold != -1) { if (threshold != -1) {
List<Card> toRemove = Lists.newArrayList(); List<Card> toRemove = Lists.newArrayList();
@@ -390,7 +393,7 @@ public class ComputerUtil {
final CardCollection sacMeList = CardLists.filter(typeList, new Predicate<Card>() { final CardCollection sacMeList = CardLists.filter(typeList, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
return (c.hasSVar("SacMe") && (Integer.parseInt(c.getSVar("SacMe")) == priority)); return c.hasSVar("SacMe") && (Integer.parseInt(c.getSVar("SacMe")) == priority);
} }
}); });
if (!sacMeList.isEmpty()) { if (!sacMeList.isEmpty()) {
@@ -419,6 +422,7 @@ public class ComputerUtil {
if (!nonCreatures.isEmpty()) { if (!nonCreatures.isEmpty()) {
return ComputerUtilCard.getWorstAI(nonCreatures); return ComputerUtilCard.getWorstAI(nonCreatures);
} else if (!typeList.isEmpty()) { } else if (!typeList.isEmpty()) {
// TODO make sure survival is possible in case the creature blocks a trampler
return ComputerUtilCard.getWorstAI(typeList); return ComputerUtilCard.getWorstAI(typeList);
} }
} }
@@ -505,7 +509,7 @@ public class ComputerUtil {
return null; return null;
} }
public static int getAIPreferenceParameter(final Card c, final String paramName) { public static int getAIPreferenceParameter(final Card c, final String paramName, SpellAbility sa) {
if (!c.hasSVar("AIPreferenceParams")) { if (!c.hasSVar("AIPreferenceParams")) {
return -1; return -1;
} }
@@ -520,7 +524,21 @@ public class ComputerUtil {
case "CreatureEvalThreshold": case "CreatureEvalThreshold":
// Threshold of 150 is just below the level of a 1/1 mana dork or a 2/2 baseline creature with no keywords // Threshold of 150 is just below the level of a 1/1 mana dork or a 2/2 baseline creature with no keywords
if (paramName.equals(parName)) { if (paramName.equals(parName)) {
return Integer.parseInt(parValue); int num = 0;
try {
num = Integer.parseInt(parValue);
} catch (NumberFormatException nfe) {
String[] valParts = StringUtils.split(parValue, "/");
CardCollection foundCards = AbilityUtils.getDefinedCards(c, valParts[0], sa);
if (!foundCards.isEmpty()) {
num = ComputerUtilCard.evaluateCreature(foundCards.get(0));
}
valParts[0] = Integer.toString(num);
if (valParts.length > 1) {
num = AbilityUtils.doXMath(num, valParts[1], c, sa);
}
}
return num;
} }
break; break;
case "MinCreaturesBelowThreshold": case "MinCreaturesBelowThreshold":
@@ -543,9 +561,8 @@ public class ComputerUtil {
typeList = CardLists.filter(typeList, CardPredicates.canBeSacrificedBy(ability)); typeList = CardLists.filter(typeList, CardPredicates.canBeSacrificedBy(ability));
if ((target != null) && target.getController() == ai) { // don't sacrifice the card we're pumping
typeList.remove(target); // don't sacrifice the card we're pumping typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, ability, ai);
}
if (typeList.size() < amount) { if (typeList.size() < amount) {
return null; return null;
@@ -573,9 +590,8 @@ public class ComputerUtil {
final Card target, final int amount, SpellAbility sa) { final Card target, final int amount, SpellAbility sa) {
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, sa); CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, sa);
if ((target != null) && target.getController() == ai) { // don't exile the card we're pumping
typeList.remove(target); // don't exile the card we're pumping typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, sa, ai);
}
if (typeList.size() < amount) { if (typeList.size() < amount) {
return null; return null;
@@ -594,9 +610,8 @@ public class ComputerUtil {
final Card target, final int amount, SpellAbility sa) { final Card target, final int amount, SpellAbility sa) {
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, sa); CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, sa);
if ((target != null) && target.getController() == ai) { // don't move the card we're pumping
typeList.remove(target); // don't move the card we're pumping typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, sa, ai);
}
if (typeList.size() < amount) { if (typeList.size() < amount) {
return null; return null;
@@ -718,12 +733,10 @@ public class ComputerUtil {
} }
public static CardCollection chooseReturnType(final Player ai, final String type, final Card activate, final Card target, final int amount, SpellAbility sa) { public static CardCollection chooseReturnType(final Player ai, final String type, final Card activate, final Card target, final int amount, SpellAbility sa) {
final CardCollection typeList = CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
if ((target != null) && target.getController() == ai) { // don't bounce the card we're pumping
// don't bounce the card we're pumping typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, sa, ai);
typeList.remove(target);
}
if (typeList.size() < amount) { if (typeList.size() < amount) {
return new CardCollection(); return new CardCollection();
@@ -743,7 +756,7 @@ public class ComputerUtil {
CardCollection remaining = new CardCollection(cardlist); CardCollection remaining = new CardCollection(cardlist);
final CardCollection sacrificed = new CardCollection(); final CardCollection sacrificed = new CardCollection();
final Card host = source.getHostCard(); final Card host = source.getHostCard();
final int considerSacThreshold = getAIPreferenceParameter(host, "CreatureEvalThreshold"); final int considerSacThreshold = getAIPreferenceParameter(host, "CreatureEvalThreshold", source);
if ("OpponentOnly".equals(source.getParam("AILogic"))) { if ("OpponentOnly".equals(source.getParam("AILogic"))) {
if(!source.getActivatingPlayer().isOpponentOf(ai)) { if(!source.getActivatingPlayer().isOpponentOf(ai)) {
@@ -3026,6 +3039,6 @@ public class ComputerUtil {
} }
} }
return false; return false;
} }
} }

View File

@@ -47,6 +47,7 @@ import forge.game.combat.Combat;
import forge.game.combat.CombatUtil; import forge.game.combat.CombatUtil;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.cost.CostPayEnergy; import forge.game.cost.CostPayEnergy;
import forge.game.cost.CostRemoveCounter;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordCollection; import forge.game.keyword.KeywordCollection;
import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordInterface;
@@ -137,6 +138,56 @@ public class ComputerUtilCard {
return Aggregates.itemWithMin(all, CardPredicates.Accessors.fnGetCmc); return Aggregates.itemWithMin(all, CardPredicates.Accessors.fnGetCmc);
} }
public static Card getBestPlaneswalkerToDamage(final List<Card> pws) {
Card bestTgt = null;
// As of right now, ranks planeswalkers by their Current Loyalty * 10 + Big buff if close to "Ultimate"
int bestScore = 0;
for (Card pw : pws) {
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
int pwScore = curLoyalty * 10;
for (SpellAbility sa : pw.getSpellAbilities()) {
if (sa.hasParam("Ultimate")) {
Integer loyaltyCost = 0;
CostRemoveCounter remLoyalty = sa.getPayCosts().getCostPartByType(CostRemoveCounter.class);
if (remLoyalty != null) {
// if remLoyalty is null, generally there's an AddCounter<0/LOYALTY> cost, like for Gideon Jura.
loyaltyCost = remLoyalty.convertAmount();
}
if (loyaltyCost != null && loyaltyCost != 0 && loyaltyCost - curLoyalty <= 1) {
// Will ultimate soon
pwScore += 10000;
}
if (pwScore > bestScore) {
bestScore = pwScore;
bestTgt = pw;
}
}
}
}
return bestTgt;
}
public static Card getWorstPlaneswalkerToDamage(final List<Card> pws) {
Card bestTgt = null;
int bestScore = Integer.MAX_VALUE;
for (Card pw : pws) {
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
if (curLoyalty < bestScore) {
bestScore = curLoyalty;
bestTgt = pw;
}
}
return bestTgt;
}
// The AI doesn't really pick the best enchantment, just the most expensive. // The AI doesn't really pick the best enchantment, just the most expensive.
/** /**
* <p> * <p>
@@ -217,6 +268,63 @@ public class ComputerUtilCard {
return Aggregates.random(bLand); // random tapped land of least represented type return Aggregates.random(bLand); // random tapped land of least represented type
} }
/**
* <p>
* getWorstLand.
* </p>
*
* @param lands
* @return a {@link forge.game.card.Card} object.
*/
public static Card getWorstLand(final List<Card> lands) {
Card worstLand = null;
int maxScore = Integer.MIN_VALUE;
// first, check for tapped, basic lands
for (Card tmp : lands) {
int score = tmp.isTapped() ? 2 : 0;
score += tmp.isBasicLand() ? 1 : 0;
score -= tmp.isCreature() ? 4 : 0;
for (Card aura : tmp.getEnchantedBy()) {
if (aura.getController().isOpponentOf(tmp.getController())) {
score += 5;
} else {
score -= 5;
}
}
if (score == maxScore &&
CardLists.count(lands, CardPredicates.sharesNameWith(tmp)) > CardLists.count(lands, CardPredicates.sharesNameWith(worstLand))) {
worstLand = tmp;
}
if (score > maxScore) {
worstLand = tmp;
maxScore = score;
}
}
return worstLand;
}
public static Card getBestLandToAnimate(final Iterable<Card> lands) {
Card land = null;
int maxScore = Integer.MIN_VALUE;
// first, check for tapped, basic lands
for (Card tmp : lands) {
int score = tmp.isTapped() ? 0 : 2;
score += tmp.isBasicLand() ? 2 : 0;
score -= tmp.isCreature() ? 4 : 0;
score -= 5 * tmp.getEnchantedBy().size();
if (score == maxScore &&
CardLists.count(lands, CardPredicates.sharesNameWith(tmp)) > CardLists.count(lands, CardPredicates.sharesNameWith(land))) {
land = tmp;
}
if (score > maxScore) {
land = tmp;
maxScore = score;
}
}
return land;
}
/** /**
* <p> * <p>
* getCheapestPermanentAI. * getCheapestPermanentAI.
@@ -825,57 +933,6 @@ public class ComputerUtilCard {
return result; return result;
} }
/**
* <p>
* getWorstLand.
* </p>
*
* @param lands
* @return a {@link forge.game.card.Card} object.
*/
public static Card getWorstLand(final List<Card> lands) {
Card worstLand = null;
int maxScore = Integer.MIN_VALUE;
// first, check for tapped, basic lands
for (Card tmp : lands) {
int score = tmp.isTapped() ? 2 : 0;
score += tmp.isBasicLand() ? 1 : 0;
score -= tmp.isCreature() ? 4 : 0;
for (Card aura : tmp.getEnchantedBy()) {
if (aura.getController().isOpponentOf(tmp.getController())) {
score += 5;
} else {
score -= 5;
}
}
if (score >= maxScore) {
worstLand = tmp;
maxScore = score;
}
}
return worstLand;
} // end getWorstLand
public static Card getBestLandToAnimate(final Iterable<Card> lands) {
Card land = null;
int maxScore = Integer.MIN_VALUE;
// first, check for tapped, basic lands
for (Card tmp : lands) {
// TODO Improve this by choosing basic lands that I have plenty of mana in
int score = tmp.isTapped() ? 0 : 2;
score += tmp.isBasicLand() ? 2 : 0;
score -= tmp.isCreature() ? 4 : 0;
score -= 5 * tmp.getEnchantedBy().size();
if (score >= maxScore) {
land = tmp;
maxScore = score;
}
}
return land;
} // end getBestLandToAnimate
public static final Predicate<Deck> AI_KNOWS_HOW_TO_PLAY_ALL_CARDS = new Predicate<Deck>() { public static final Predicate<Deck> AI_KNOWS_HOW_TO_PLAY_ALL_CARDS = new Predicate<Deck>() {
@Override @Override
public boolean apply(Deck d) { public boolean apply(Deck d) {
@@ -888,6 +945,7 @@ public class ComputerUtilCard {
return true; return true;
} }
}; };
public static List<String> chooseColor(SpellAbility sa, int min, int max, List<String> colorChoices) { public static List<String> chooseColor(SpellAbility sa, int min, int max, List<String> colorChoices) {
List<String> chosen = new ArrayList<>(); List<String> chosen = new ArrayList<>();
Player ai = sa.getActivatingPlayer(); Player ai = sa.getActivatingPlayer();
@@ -1592,7 +1650,7 @@ public class ComputerUtilCard {
*/ */
public static Card getPumpedCreature(final Player ai, final SpellAbility sa, public static Card getPumpedCreature(final Player ai, final SpellAbility sa,
final Card c, int toughness, int power, final List<String> keywords) { final Card c, int toughness, int power, final List<String> keywords) {
Card pumped = CardFactory.copyCard(c, true); Card pumped = CardFactory.copyCard(c, false);
pumped.setSickness(c.hasSickness()); pumped.setSickness(c.hasSickness());
final long timestamp = c.getGame().getNextTimestamp(); final long timestamp = c.getGame().getNextTimestamp();
final List<String> kws = new ArrayList<>(); final List<String> kws = new ArrayList<>();
@@ -1630,7 +1688,7 @@ public class ComputerUtilCard {
pumped.addChangedCardKeywords(kws, null, false, false, timestamp); pumped.addChangedCardKeywords(kws, null, false, false, timestamp);
Set<CounterType> types = c.getCounters().keySet(); Set<CounterType> types = c.getCounters().keySet();
for(CounterType ct : types) { for(CounterType ct : types) {
pumped.addCounterFireNoEvents(ct, c.getCounters(ct), ai, true, null); pumped.addCounterFireNoEvents(ct, c.getCounters(ct), ai, sa, true, null);
} }
//Copies tap-state and extra keywords (auras, equipment, etc.) //Copies tap-state and extra keywords (auras, equipment, etc.)
if (c.isTapped()) { if (c.isTapped()) {

View File

@@ -472,7 +472,6 @@ public class ComputerUtilCombat {
* @return a boolean. * @return a boolean.
*/ */
public static boolean wouldLoseLife(final Player ai, final Combat combat) { public static boolean wouldLoseLife(final Player ai, final Combat combat) {
return (ComputerUtilCombat.lifeThatWouldRemain(ai, combat) < ai.getLife()); return (ComputerUtilCombat.lifeThatWouldRemain(ai, combat) < ai.getLife());
} }

View File

@@ -8,6 +8,8 @@ import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
@@ -20,6 +22,7 @@ import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardFactoryUtil; import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates.Presets; import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.card.CounterEnumType; import forge.game.card.CounterEnumType;
@@ -270,7 +273,10 @@ public class ComputerUtilCost {
} }
final CardCollection sacList = new CardCollection(); final CardCollection sacList = new CardCollection();
final CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sourceAbility); CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sourceAbility);
// don't sacrifice the card we're pumping
typeList = paymentChoicesWithoutTargets(typeList, sourceAbility, ai);
int count = 0; int count = 0;
while (count < amount) { while (count < amount) {
@@ -320,11 +326,14 @@ public class ComputerUtilCost {
} }
final CardCollection sacList = new CardCollection(); final CardCollection sacList = new CardCollection();
final CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sourceAbility); CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sourceAbility);
// don't sacrifice the card we're pumping
typeList = paymentChoicesWithoutTargets(typeList, sourceAbility, ai);
int count = 0; int count = 0;
while (count < amount) { while (count < amount) {
Card prefCard = ComputerUtil.getCardPreference(ai, source, "SacCost", typeList); Card prefCard = ComputerUtil.getCardPreference(ai, source, "SacCost", typeList, sourceAbility);
if (prefCard == null) { if (prefCard == null) {
return false; return false;
} }
@@ -337,6 +346,19 @@ public class ComputerUtilCost {
return true; return true;
} }
/**
* Check sacrifice cost.
*
* @param cost
* the cost
* @param source
* the source
* @return true, if successful
*/
public static boolean checkSacrificeCost(final Player ai, final Cost cost, final Card source, final SpellAbility sourceAbility) {
return checkSacrificeCost(ai, cost, source, sourceAbility, true);
}
public static boolean isSacrificeSelfCost(final Cost cost) { public static boolean isSacrificeSelfCost(final Cost cost) {
if (cost == null) { if (cost == null) {
return false; return false;
@@ -397,19 +419,6 @@ public class ComputerUtilCost {
return true; return true;
} }
/**
* Check sacrifice cost.
*
* @param cost
* the cost
* @param source
* the source
* @return true, if successful
*/
public static boolean checkSacrificeCost(final Player ai, final Cost cost, final Card source, final SpellAbility sourceAbility) {
return checkSacrificeCost(ai, cost, source, sourceAbility,true);
}
/** /**
* <p> * <p>
* shouldPayCost. * shouldPayCost.
@@ -420,8 +429,8 @@ public class ComputerUtilCost {
* @param cost * @param cost
* @return a boolean. * @return a boolean.
*/ */
@Deprecated
public static boolean shouldPayCost(final Player ai, final Card hostCard, final Cost cost) { public static boolean shouldPayCost(final Player ai, final Card hostCard, final Cost cost) {
for (final CostPart part : cost.getCostParts()) { for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostPayLife) { if (part instanceof CostPayLife) {
if (!ai.cantLoseForZeroOrLessLife()) { if (!ai.cantLoseForZeroOrLessLife()) {
@@ -741,4 +750,12 @@ public class ComputerUtilCost {
} }
return ObjectUtils.defaultIfNull(val, 0); return ObjectUtils.defaultIfNull(val, 0);
} }
public static CardCollection paymentChoicesWithoutTargets(Iterable<Card> choices, SpellAbility source, Player ai) {
if (source.usesTargeting()) {
final CardCollection targets = new CardCollection(source.getTargets().getTargetCards());
choices = Iterables.filter(choices, Predicates.not(Predicates.and(CardPredicates.isController(ai), Predicates.in(targets))));
}
return new CardCollection(choices);
}
} }

View File

@@ -788,7 +788,6 @@ public class ComputerUtilMana {
String manaProduced = ignoreColor || ignoreType ? MagicColor.toShortString(toPay.getColorMask()) String manaProduced = ignoreColor || ignoreType ? MagicColor.toShortString(toPay.getColorMask())
: predictManafromSpellAbility(saPayment, ai, toPay); : predictManafromSpellAbility(saPayment, ai, toPay);
// System.out.println(manaProduced);
payMultipleMana(cost, manaProduced, ai); payMultipleMana(cost, manaProduced, ai);
// remove from available lists // remove from available lists
@@ -1072,7 +1071,6 @@ public class ComputerUtilMana {
getComboManaChoice(ai, saPayment, sa, cost); getComboManaChoice(ai, saPayment, sa, cost);
} }
else if (saPayment.getApi() == ApiType.ManaReflected) { else if (saPayment.getApi() == ApiType.ManaReflected) {
//System.out.println("Evaluate reflected mana of: " + saPayment.getHostCard());
Set<String> reflected = CardUtil.getReflectableManaColors(saPayment); Set<String> reflected = CardUtil.getReflectableManaColors(saPayment);
for (byte c : MagicColor.WUBRG) { for (byte c : MagicColor.WUBRG) {
@@ -1281,7 +1279,7 @@ public class ComputerUtilMana {
final AbilityManaPart abMana = manaAb.getManaPart(); final AbilityManaPart abMana = manaAb.getManaPart();
if (abMana.isComboMana()) { if (abMana.isComboMana()) {
int amount = manaAb.hasParam("Amount") ? AbilityUtils.calculateAmount(source, manaAb.getParam("Amount"), saRoot) : 1; int amount = manaAb.hasParam("Amount") ? AbilityUtils.calculateAmount(source, manaAb.getParam("Amount"), manaAb) : 1;
final ManaCostBeingPaid testCost = new ManaCostBeingPaid(cost); final ManaCostBeingPaid testCost = new ManaCostBeingPaid(cost);
final String[] comboColors = abMana.getComboColors().split(" "); final String[] comboColors = abMana.getComboColors().split(" ");
for (int nMana = 1; nMana <= amount; nMana++) { for (int nMana = 1; nMana <= amount; nMana++) {
@@ -1301,7 +1299,7 @@ public class ComputerUtilMana {
if (!testCost.isPaid()) { if (!testCost.isPaid()) {
// Loop over combo colors // Loop over combo colors
for (String color : comboColors) { for (String color : comboColors) {
if (satisfiesColorChoice(abMana, choiceString, choice) && testCost.isAnyPartPayableWith(ManaAtom.fromName(color), ai.getManaPool())) { if (satisfiesColorChoice(abMana, choiceString, choice) && testCost.needsColor(ManaAtom.fromName(color), ai.getManaPool())) {
payMultipleMana(testCost, color, ai); payMultipleMana(testCost, color, ai);
if (nMana != 1) { if (nMana != 1) {
choiceString.append(" "); choiceString.append(" ");
@@ -1880,7 +1878,7 @@ public class ComputerUtilMana {
final Card offering = sa.getSacrificedAsOffering(); final Card offering = sa.getSacrificedAsOffering();
offering.setUsedToPay(false); offering.setUsedToPay(false);
if (costIsPaid && !test) { if (costIsPaid && !test) {
sa.getHostCard().getGame().getAction().sacrifice(offering, sa, null); sa.getHostCard().getGame().getAction().sacrifice(offering, sa, null, null);
} }
sa.resetSacrificedAsOffering(); sa.resetSacrificedAsOffering();
} }
@@ -1888,7 +1886,7 @@ public class ComputerUtilMana {
final Card emerge = sa.getSacrificedAsEmerge(); final Card emerge = sa.getSacrificedAsEmerge();
emerge.setUsedToPay(false); emerge.setUsedToPay(false);
if (costIsPaid && !test) { if (costIsPaid && !test) {
sa.getHostCard().getGame().getAction().sacrifice(emerge, sa, null); sa.getHostCard().getGame().getAction().sacrifice(emerge, sa, null, null);
} }
sa.resetSacrificedAsEmerge(); sa.resetSacrificedAsEmerge();
} }

View File

@@ -49,6 +49,7 @@ public abstract class GameState {
ZONES.put(ZoneType.Library, "library"); ZONES.put(ZoneType.Library, "library");
ZONES.put(ZoneType.Exile, "exile"); ZONES.put(ZoneType.Exile, "exile");
ZONES.put(ZoneType.Command, "command"); ZONES.put(ZoneType.Command, "command");
ZONES.put(ZoneType.Sideboard, "sideboard");
} }
private int humanLife = -1; private int humanLife = -1;
@@ -398,6 +399,12 @@ public abstract class GameState {
// Need to figure out a better way to detect if it's actually on adventure. // Need to figure out a better way to detect if it's actually on adventure.
newText.append("|OnAdventure"); newText.append("|OnAdventure");
} }
if (c.isForetold()) {
newText.append("|Foretold");
}
if (c.isForetoldThisTurn()) {
newText.append("|ForetoldThisTurn");
}
} }
@@ -565,6 +572,13 @@ public abstract class GameState {
aiCardTexts.put(ZoneType.Command, categoryValue); aiCardTexts.put(ZoneType.Command, categoryValue);
} }
else if (categoryName.endsWith("sideboard")) {
if (isHuman)
humanCardTexts.put(ZoneType.Sideboard, categoryValue);
else
aiCardTexts.put(ZoneType.Sideboard, categoryValue);
}
else if (categoryName.startsWith("ability")) { else if (categoryName.startsWith("ability")) {
abilityString.put(categoryName.substring("ability".length()), categoryValue); abilityString.put(categoryName.substring("ability".length()), categoryValue);
} }
@@ -1175,7 +1189,7 @@ public abstract class GameState {
String[] allCounterStrings = counterString.split(","); String[] allCounterStrings = counterString.split(",");
for (final String counterPair : allCounterStrings) { for (final String counterPair : allCounterStrings) {
String[] pair = counterPair.split("=", 2); String[] pair = counterPair.split("=", 2);
entity.addCounter(CounterType.getType(pair[0]), Integer.parseInt(pair[1]), null, false, false, null); entity.addCounter(CounterType.getType(pair[0]), Integer.parseInt(pair[1]), null, null, false, false, null);
} }
} }
@@ -1391,6 +1405,14 @@ public abstract class GameState {
} }
} else if (info.equals("NoETBTrigs")) { } else if (info.equals("NoETBTrigs")) {
cardsWithoutETBTrigs.add(c); cardsWithoutETBTrigs.add(c);
} else if (info.equals("Foretold")) {
c.setForetold(true);
c.turnFaceDown(true);
c.addMayLookTemp(c.getOwner());
} else if (info.equals("ForetoldThisTurn")) {
c.setForetoldThisTurn(true);
} else if (info.equals("IsToken")) {
c.setToken(true);
} }
} }

View File

@@ -137,6 +137,23 @@ public class PlayerControllerAi extends PlayerController {
return new HashMap<>(); return new HashMap<>();
} }
@Override
public Map<Byte, Integer> specifyManaCombo(SpellAbility sa, ColorSet colorSet, int manaAmount, boolean different) {
Map<Byte, Integer> result = new HashMap<>();
for (int i = 0; i < manaAmount; ++i) {
Byte chosen = chooseColor("", sa, colorSet);
if (result.containsKey(chosen)) {
result.put(chosen, result.get(chosen) + 1);
} else {
result.put(chosen, 1);
}
if (different) {
colorSet = ColorSet.fromMask(colorSet.getColor() - chosen);
}
}
return result;
}
@Override @Override
public Integer announceRequirements(SpellAbility ability, String announce) { public Integer announceRequirements(SpellAbility ability, String announce) {
// For now, these "announcements" are made within the AI classes of the appropriate SA effects // For now, these "announcements" are made within the AI classes of the appropriate SA effects

View File

@@ -217,7 +217,7 @@ public class SpecialCardAi {
Card animated = AnimateAi.becomeAnimated(sa.getHostCard(), sa.getSubAbility()); Card animated = AnimateAi.becomeAnimated(sa.getHostCard(), sa.getSubAbility());
if (sa.getHostCard().canReceiveCounters(CounterEnumType.P1P1)) { if (sa.getHostCard().canReceiveCounters(CounterEnumType.P1P1)) {
animated.addCounter(CounterEnumType.P1P1, 2, ai, false, null); animated.addCounter(CounterEnumType.P1P1, 2, ai, sa.getSubAbility(), false, null);
} }
boolean isOppEOT = ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai; boolean isOppEOT = ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai;
boolean isValuableAttacker = ph.is(PhaseType.MAIN1, ai) && ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animated); boolean isValuableAttacker = ph.is(PhaseType.MAIN1, ai) && ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animated);

View File

@@ -289,7 +289,6 @@ public abstract class SpellAbilityAi {
if (sa.isSpell() && !sa.isBuyBackAbility()) { if (sa.isSpell() && !sa.isBuyBackAbility()) {
return false; return false;
} }
return phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai); return phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai);
} }

View File

@@ -171,6 +171,7 @@ public enum SpellApiToAi {
.put(ApiType.UnattachAll, UnattachAllAi.class) .put(ApiType.UnattachAll, UnattachAllAi.class)
.put(ApiType.Untap, UntapAi.class) .put(ApiType.Untap, UntapAi.class)
.put(ApiType.UntapAll, UntapAllAi.class) .put(ApiType.UntapAll, UntapAllAi.class)
.put(ApiType.Venture, VentureAi.class)
.put(ApiType.Vote, VoteAi.class) .put(ApiType.Vote, VoteAi.class)
.put(ApiType.WinsGame, GameWinAi.class) .put(ApiType.WinsGame, GameWinAi.class)
@@ -191,6 +192,6 @@ public enum SpellApiToAi {
result = ReflectionUtil.makeDefaultInstanceOf(clz); result = ReflectionUtil.makeDefaultInstanceOf(clz);
apiToInstance.put(api, result); apiToInstance.put(api, result);
} }
return result; return result;
} }
} }

View File

@@ -984,7 +984,7 @@ public class AttachAi extends SpellAbilityAi {
List<GameObject> targets = new ArrayList<>(); List<GameObject> targets = new ArrayList<>();
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) { if (tgt == null) {
targets = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa); targets = AbilityUtils.getDefinedObjects(card, sa.getParam("Defined"), sa);
} else { } else {
AttachAi.attachPreference(sa, tgt, mandatory); AttachAi.attachPreference(sa, tgt, mandatory);
targets = sa.getTargets(); targets = sa.getTargets();
@@ -1344,7 +1344,7 @@ public class AttachAi extends SpellAbilityAi {
CardCollection list = null; CardCollection list = null;
if (tgt == null) { if (tgt == null) {
list = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa); list = AbilityUtils.getDefinedCards(attachSource, sa.getParam("Defined"), sa);
} else { } else {
list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(tgt.getZone()), tgt.getValidTgts(), sa.getActivatingPlayer(), attachSource, sa); list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(tgt.getZone()), tgt.getValidTgts(), sa.getActivatingPlayer(), attachSource, sa);

View File

@@ -1709,7 +1709,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
return true; return true;
} }
@Override @Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) { public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
// Called when looking for creature to attach aura or equipment // Called when looking for creature to attach aura or equipment

View File

@@ -23,21 +23,23 @@ import forge.util.collect.FCollection;
public class CharmAi extends SpellAbilityAi { public class CharmAi extends SpellAbilityAi {
@Override @Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) { protected boolean checkApiLogic(Player ai, SpellAbility sa) {
// sa is Entwined, no need for extra logic
if (sa.isEntwine()) {
return true;
}
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
final int num = AbilityUtils.calculateAmount(source, sa.getParamOrDefault("CharmNum", "1"), sa); final int num;
final int min = sa.hasParam("MinCharmNum") ? AbilityUtils.calculateAmount(source, sa.getParamOrDefault("MinCharmNum", "1"), sa) : num; final int min;
if (sa.isEntwine()) {
num = min = choices.size();
}
else {
num = AbilityUtils.calculateAmount(source, sa.getParamOrDefault("CharmNum", "1"), sa);
min = sa.hasParam("MinCharmNum") ? AbilityUtils.calculateAmount(source, sa.getParamOrDefault("MinCharmNum", "1"), sa) : num;
}
boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now? boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now?
// Reset the chosen list otherwise it will be locked in forever by earlier calls // Reset the chosen list otherwise it will be locked in forever by earlier calls
sa.setChosenList(null); sa.setChosenList(null);
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
List<AbilitySub> chosenList; List<AbilitySub> chosenList;
if (!ai.equals(sa.getActivatingPlayer())) { if (!ai.equals(sa.getActivatingPlayer())) {
@@ -159,7 +161,7 @@ public class CharmAi extends SpellAbilityAi {
chosenList.add(allyTainted ? gain : lose); chosenList.add(allyTainted ? gain : lose);
} else if (oppTainted || ai.getGame().isCardInPlay("Rain of Gore")) { } else if (oppTainted || ai.getGame().isCardInPlay("Rain of Gore")) {
// Rain of Gore does negate lifegain, so don't benefit the others // Rain of Gore does negate lifegain, so don't benefit the others
// same for if a oppoent does control Tainted Remedy // same for if a opponent does control Tainted Remedy
// but if ai cant gain life, the effects are negated // but if ai cant gain life, the effects are negated
chosenList.add(ai.canGainLife() ? lose : gain); chosenList.add(ai.canGainLife() ? lose : gain);
} else if (ai.getGame().isCardInPlay("Sulfuric Vortex")) { } else if (ai.getGame().isCardInPlay("Sulfuric Vortex")) {
@@ -177,13 +179,13 @@ public class CharmAi extends SpellAbilityAi {
chosenList.add(gain); chosenList.add(gain);
} else if(!ai.canGainLife() && aiLife == 14 ) { } else if(!ai.canGainLife() && aiLife == 14 ) {
// ai cant gain life, but try to avoid falling to 13 // ai cant gain life, but try to avoid falling to 13
// but if a oppoent does control Tainted Remedy its irrelevant // but if a opponent does control Tainted Remedy its irrelevant
chosenList.add(oppTainted ? lose : gain); chosenList.add(oppTainted ? lose : gain);
} else if (allyTainted) { } else if (allyTainted) {
// Tainted Remedy negation logic, try gain instead of lose // Tainted Remedy negation logic, try gain instead of lose
// because negation does turn it into lose for opponents // because negation does turn it into lose for opponents
boolean oppCritical = false; boolean oppCritical = false;
// an oppoent is Critical = 14, and can't gain life, try to lose life instead // an opponent is Critical = 14, and can't gain life, try to lose life instead
// but only if ai doesn't kill itself with that. // but only if ai doesn't kill itself with that.
if (aiLife != 14) { if (aiLife != 14) {
for (Player p : opponents) { for (Player p : opponents) {
@@ -197,7 +199,7 @@ public class CharmAi extends SpellAbilityAi {
} else { } else {
// normal logic, try to gain life if its critical // normal logic, try to gain life if its critical
boolean oppCritical = false; boolean oppCritical = false;
// an oppoent is Critical = 12, and can gain life, try to gain life instead // an opponent is Critical = 12, and can gain life, try to gain life instead
// but only if ai doesn't kill itself with that. // but only if ai doesn't kill itself with that.
if (aiLife != 12) { if (aiLife != 12) {
for (Player p : opponents) { for (Player p : opponents) {
@@ -224,6 +226,8 @@ public class CharmAi extends SpellAbilityAi {
goodChoice = sub; goodChoice = sub;
} else { } else {
// Standard canPlayAi() // Standard canPlayAi()
sub.setActivatingPlayer(ai);
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) { if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
chosenList.add(sub); chosenList.add(sub);
if (chosenList.size() == min) { if (chosenList.size() == min) {

View File

@@ -246,7 +246,6 @@ public class ChooseCardAi extends SpellAbilityAi {
return true; return true;
} }
}); });
System.out.println("Tangle Wire" + options + " - " + betterList);
if (!betterList.isEmpty()) { if (!betterList.isEmpty()) {
choice = betterList.get(0); choice = betterList.get(0);
} else { } else {

View File

@@ -68,7 +68,6 @@ import forge.util.Aggregates;
public class ControlGainAi extends SpellAbilityAi { public class ControlGainAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(final Player ai, final SpellAbility sa) { protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
final List<String> lose = Lists.newArrayList(); final List<String> lose = Lists.newArrayList();
if (sa.hasParam("LoseControl")) { if (sa.hasParam("LoseControl")) {

View File

@@ -29,7 +29,6 @@ import forge.util.collect.FCollection;
public class CountersMoveAi extends SpellAbilityAi { public class CountersMoveAi extends SpellAbilityAi {
@Override @Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) { protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
sa.resetTargets(); sa.resetTargets();
if (!moveTgtAI(ai, sa)) { if (!moveTgtAI(ai, sa)) {
@@ -83,8 +82,7 @@ public class CountersMoveAi extends SpellAbilityAi {
return true; return true;
} }
// something you can't block, try to reduce its // something you can't block, try to reduce its attack
// attack
if (!ComputerUtilCard.canBeBlockedProfitably(ai, cpy)) { if (!ComputerUtilCard.canBeBlockedProfitably(ai, cpy)) {
return true; return true;
} }
@@ -119,7 +117,6 @@ public class CountersMoveAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
sa.resetTargets(); sa.resetTargets();
@@ -237,7 +234,6 @@ public class CountersMoveAi extends SpellAbilityAi {
} }
private boolean moveTgtAI(final Player ai, final SpellAbility sa) { private boolean moveTgtAI(final Player ai, final SpellAbility sa) {
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
final Game game = ai.getGame(); final Game game = ai.getGame();
final String type = sa.getParam("CounterType"); final String type = sa.getParam("CounterType");
@@ -283,8 +279,7 @@ public class CountersMoveAi extends SpellAbilityAi {
// cant use substract on Copy // cant use substract on Copy
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount); srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
// do not steal a P1P1 from Undying if it would die // do not steal a P1P1 from Undying if it would die this way
// this way
if (cType != null && cType.is(CounterEnumType.P1P1) && srcCardCpy.getNetToughness() <= 0) { if (cType != null && cType.is(CounterEnumType.P1P1) && srcCardCpy.getNetToughness() <= 0) {
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING) || card.isToken(); return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING) || card.isToken();
} }
@@ -373,8 +368,13 @@ public class CountersMoveAi extends SpellAbilityAi {
} }
Card lki = CardUtil.getLKICopy(src); Card lki = CardUtil.getLKICopy(src);
lki.clearCounters(); if (cType == null) {
// go for opponent when value implies debuff lki.clearCounters();
}
else {
lki.setCounters(cType, 0);
}
// go for opponent when higher value implies debuff
if (ComputerUtilCard.evaluateCreature(src) > ComputerUtilCard.evaluateCreature(lki)) { if (ComputerUtilCard.evaluateCreature(src) > ComputerUtilCard.evaluateCreature(lki)) {
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai); List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai);
if (!aiList.isEmpty()) { if (!aiList.isEmpty()) {
@@ -419,6 +419,12 @@ public class CountersMoveAi extends SpellAbilityAi {
return true; return true;
} }
} }
final boolean isMandatoryTrigger = (sa.isTrigger() && !sa.isOptionalTrigger())
|| (sa.getRootAbility().isTrigger() && !sa.getRootAbility().isOptionalTrigger());
if (!isMandatoryTrigger) {
// no good target
return false;
}
} }
// move counter to opponents creature but only if you can not steal them // move counter to opponents creature but only if you can not steal them

View File

@@ -64,11 +64,9 @@ public class CountersPutAi extends SpellAbilityAi {
*/ */
@Override @Override
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) { protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
final String type = sa.getParam("CounterType"); final String type = sa.getParam("CounterType");
final String aiLogic = sa.getParamOrDefault("AILogic", ""); final String aiLogic = sa.getParamOrDefault("AILogic", "");
// TODO Auto-generated method stub
if (!super.willPayCosts(ai, sa, cost, source)) { if (!super.willPayCosts(ai, sa, cost, source)) {
return false; return false;
} }
@@ -225,8 +223,7 @@ public class CountersPutAi extends SpellAbilityAi {
} }
if (sa.canTarget(ai)) { if (sa.canTarget(ai)) {
// don't target itself when its forced to add poison // don't target itself when its forced to add poison counters too
// counters too
if (!ai.getCounters().isEmpty()) { if (!ai.getCounters().isEmpty()) {
if (!eachExisting || ai.getPoisonCounters() < 5) { if (!eachExisting || ai.getPoisonCounters() < 5) {
sa.getTargets().add(ai); sa.getTargets().add(ai);
@@ -480,7 +477,6 @@ public class CountersPutAi extends SpellAbilityAi {
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) {
// don't put the counter on the dead creature // don't put the counter on the dead creature
if (sacSelf && c.equals(source)) { if (sacSelf && c.equals(source)) {
return false; return false;
@@ -493,6 +489,8 @@ public class CountersPutAi extends SpellAbilityAi {
Card sacTarget = ComputerUtil.getCardPreference(ai, source, "SacCost", list); Card sacTarget = ComputerUtil.getCardPreference(ai, source, "SacCost", list);
// this card is planned to be sacrificed during cost payment, so don't target it // this card is planned to be sacrificed during cost payment, so don't target it
// (otherwise the AI can cheat by activating this SA and not paying the sac cost, e.g. Extruder) // (otherwise the AI can cheat by activating this SA and not paying the sac cost, e.g. Extruder)
// TODO needs update if amount > 1 gets printed,
// maybe also check putting the counter on that exact creature is more important than sacrificing it (though unlikely?)
list.remove(sacTarget); list.remove(sacTarget);
} }
@@ -617,7 +615,7 @@ public class CountersPutAi extends SpellAbilityAi {
// Instant +1/+1 // Instant +1/+1
if (type.equals("P1P1") && !SpellAbilityAi.isSorcerySpeed(sa)) { if (type.equals("P1P1") && !SpellAbilityAi.isSorcerySpeed(sa)) {
if (!(ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN) && abCost.isReusuableResource())) { if (!(ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN) && abCost.isReusuableResource())) {
return false; // only if next turn and cost is reusable return false; // only if next turn and cost is reusable
} }
} }
} }

View File

@@ -195,7 +195,6 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
*/ */
@Override @Override
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) { public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
if (options.size() > 1) { if (options.size() > 1) {
final Player ai = sa.getActivatingPlayer(); final Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame(); final Game game = ai.getGame();

View File

@@ -81,7 +81,6 @@ public class CountersRemoveAi extends SpellAbilityAi {
*/ */
@Override @Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) { protected boolean checkApiLogic(Player ai, SpellAbility sa) {
final String type = sa.getParam("CounterType"); final String type = sa.getParam("CounterType");
if (sa.usesTargeting()) { if (sa.usesTargeting()) {

View File

@@ -46,7 +46,7 @@ public class DamageAllAi extends SpellAbilityAi {
int x = -1; int x = -1;
final String damage = sa.getParam("NumDmg"); final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa); int dmg = AbilityUtils.calculateAmount(source, damage, sa);
if (damage.equals("X") && sa.getSVar(damage).equals("Count$Converge")) { if (damage.equals("X") && sa.getSVar(damage).equals("Count$Converge")) {
dmg = ComputerUtilMana.getConvergeCount(sa, ai); dmg = ComputerUtilMana.getConvergeCount(sa, ai);
} }
@@ -202,7 +202,7 @@ public class DamageAllAi extends SpellAbilityAi {
dmg = ComputerUtilCost.getMaxXValue(sa, ai); dmg = ComputerUtilCost.getMaxXValue(sa, ai);
sa.setXManaCostPaid(dmg); sa.setXManaCostPaid(dmg);
} else { } else {
dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa); dmg = AbilityUtils.calculateAmount(source, damage, sa);
} }
if (sa.hasParam("ValidPlayers")) { if (sa.hasParam("ValidPlayers")) {
@@ -286,7 +286,7 @@ public class DamageAllAi extends SpellAbilityAi {
dmg = ComputerUtilCost.getMaxXValue(sa, ai); dmg = ComputerUtilCost.getMaxXValue(sa, ai);
sa.setXManaCostPaid(dmg); sa.setXManaCostPaid(dmg);
} else { } else {
dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa); dmg = AbilityUtils.calculateAmount(source, damage, sa);
} }
if (sa.hasParam("ValidPlayers")) { if (sa.hasParam("ValidPlayers")) {

View File

@@ -35,11 +35,11 @@ import forge.game.card.CardPredicates;
import forge.game.card.CounterEnumType; import forge.game.card.CounterEnumType;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.cost.CostPartMana; import forge.game.cost.CostPartMana;
import forge.game.cost.CostRemoveCounter;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.spellability.AbilitySub; import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetChoices; import forge.game.spellability.TargetChoices;
@@ -53,10 +53,9 @@ public class DamageDealAi extends DamageAiBase {
@Override @Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) { public boolean chkAIDrawback(SpellAbility sa, Player ai) {
final String damage = sa.getParam("NumDmg"); final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
final String logic = sa.getParam("AILogic");
Card source = sa.getHostCard(); Card source = sa.getHostCard();
int dmg = AbilityUtils.calculateAmount(source, damage, sa);
final String logic = sa.getParam("AILogic");
if ("MadSarkhanDigDmg".equals(logic)) { if ("MadSarkhanDigDmg".equals(logic)) {
return SpecialCardAi.SarkhanTheMad.considerDig(ai, sa); return SpecialCardAi.SarkhanTheMad.considerDig(ai, sa);
@@ -101,13 +100,12 @@ public class DamageDealAi extends DamageAiBase {
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Cost abCost = sa.getPayCosts(); final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa); final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final String damage = sa.getParam("NumDmg"); final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa); int dmg = AbilityUtils.calculateAmount(source, damage, sa);
if (damage.equals("X")) { if (damage.equals("X")) {
if (sa.getSVar(damage).equals("Count$xPaid") || sourceName.equals("Crater's Claws")) { if (sa.getSVar(damage).equals("Count$xPaid") || sourceName.equals("Crater's Claws")) {
@@ -439,63 +437,12 @@ public class DamageDealAi extends DamageAiBase {
// We can hurt a planeswalker, so rank the one which is the best target // We can hurt a planeswalker, so rank the one which is the best target
if (!hPlay.isEmpty() && pl.isOpponentOf(ai) && activator.equals(ai)) { if (!hPlay.isEmpty() && pl.isOpponentOf(ai) && activator.equals(ai)) {
return getBestPlaneswalkerToDamage(hPlay); return ComputerUtilCard.getBestPlaneswalkerToDamage(hPlay);
} }
return null; return null;
} }
private Card getBestPlaneswalkerToDamage(final List<Card> pws) {
Card bestTgt = null;
// As of right now, ranks planeswalkers by their Current Loyalty * 10 + Big buff if close to "Ultimate"
int bestScore = 0;
for (Card pw : pws) {
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
int pwScore = curLoyalty * 10;
for (SpellAbility sa : pw.getSpellAbilities()) {
if (sa.hasParam("Ultimate")) {
Integer loyaltyCost = 0;
CostRemoveCounter remLoyalty = sa.getPayCosts().getCostPartByType(CostRemoveCounter.class);
if (remLoyalty != null) {
// if remLoyalty is null, generally there's an AddCounter<0/LOYALTY> cost, like for Gideon Jura.
loyaltyCost = remLoyalty.convertAmount();
}
if (loyaltyCost != null && loyaltyCost != 0 && loyaltyCost - curLoyalty <= 1) {
// Will ultimate soon
pwScore += 10000;
}
if (pwScore > bestScore) {
bestScore = pwScore;
bestTgt = pw;
}
}
}
}
return bestTgt;
}
private Card getWorstPlaneswalkerToDamage(final List<Card> pws) {
Card bestTgt = null;
int bestScore = Integer.MAX_VALUE;
for (Card pw : pws) {
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
if (curLoyalty < bestScore) {
bestScore = curLoyalty;
bestTgt = pw;
}
}
return bestTgt;
}
private List<Card> getTargetableCards(Player ai, SpellAbility sa, Player pl, TargetRestrictions tgt, Player activator, Card source, Game game) { private List<Card> getTargetableCards(Player ai, SpellAbility sa, Player pl, TargetRestrictions tgt, Player activator, Card source, Game game) {
List<Card> hPlay = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), activator, source, sa); List<Card> hPlay = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), activator, source, sa);
@@ -584,7 +531,7 @@ public class DamageDealAi extends DamageAiBase {
&& "P1P1".equals(sa.getParent().getParam("CounterType"))) { && "P1P1".equals(sa.getParent().getParam("CounterType"))) {
// assuming the SA parent is of PutCounter type. Perhaps it's possible to predict counter multipliers here somehow? // assuming the SA parent is of PutCounter type. Perhaps it's possible to predict counter multipliers here somehow?
final String amountStr = sa.getParent().getParamOrDefault("CounterNum", "1"); final String amountStr = sa.getParent().getParamOrDefault("CounterNum", "1");
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa); final int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
dmg += amount; dmg += amount;
} }
@@ -897,7 +844,7 @@ public class DamageDealAi extends DamageAiBase {
// this is for Triggered targets that are mandatory // this is for Triggered targets that are mandatory
final boolean noPrevention = sa.hasParam("NoPrevention"); final boolean noPrevention = sa.hasParam("NoPrevention");
final boolean divided = sa.isDividedAsYouChoose(); final boolean divided = sa.isDividedAsYouChoose();
final Player opp = ai.getWeakestOpponent(); PlayerCollection opps = ai.getOpponents();
while (sa.canAddMoreTarget()) { while (sa.canAddMoreTarget()) {
if (tgt.canTgtPlaneswalker()) { if (tgt.canTgtPlaneswalker()) {
@@ -925,13 +872,17 @@ public class DamageDealAi extends DamageAiBase {
} }
} }
if (sa.canTarget(opp)) { if (!opps.isEmpty()) {
if (sa.getTargets().add(opp)) { Player opp = opps.getFirst();
if (divided) { opps.remove(opp);
sa.addDividedAllocation(opp, dmg); if (sa.canTarget(opp)) {
break; if (sa.getTargets().add(opp)) {
if (divided) {
sa.addDividedAllocation(opp, dmg);
break;
}
continue;
} }
continue;
} }
} }
@@ -950,7 +901,7 @@ public class DamageDealAi extends DamageAiBase {
} }
else if (tgt.canTgtPlaneswalker()) { else if (tgt.canTgtPlaneswalker()) {
// Second pass for planeswalkers: choose AI's worst planeswalker // Second pass for planeswalkers: choose AI's worst planeswalker
final Card c = getWorstPlaneswalkerToDamage(CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), Predicates.and(CardPredicates.Presets.PLANESWALKERS), CardPredicates.isTargetableBy(sa))); final Card c = ComputerUtilCard.getWorstPlaneswalkerToDamage(CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), Predicates.and(CardPredicates.Presets.PLANESWALKERS), CardPredicates.isTargetableBy(sa)));
if (c != null) { if (c != null) {
sa.getTargets().add(c); sa.getTargets().add(c);
if (divided) { if (divided) {

View File

@@ -24,7 +24,7 @@ import forge.game.zone.ZoneType;
public class DestroyAi extends SpellAbilityAi { public class DestroyAi extends SpellAbilityAi {
@Override @Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) { public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa); return checkApiLogic(ai, sa);
} }
@Override @Override

View File

@@ -244,7 +244,7 @@ public class DrawAi extends SpellAbilityAi {
int numCards = 1; int numCards = 1;
if (sa.hasParam("NumCards")) { if (sa.hasParam("NumCards")) {
numCards = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa); numCards = AbilityUtils.calculateAmount(source, sa.getParam("NumCards"), sa);
} }
boolean xPaid = false; boolean xPaid = false;

View File

@@ -67,8 +67,7 @@ public class FightAi extends SpellAbilityAi {
for (Card humanCreature : humCreatures) { for (Card humanCreature : humCreatures) {
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= fighter1.getNetPower() if (ComputerUtilCombat.getDamageToKill(humanCreature) <= fighter1.getNetPower()
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(fighter1)) { && humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(fighter1)) {
// todo: check min/max targets; see if we picked the best // todo: check min/max targets; see if we picked the best matchup
// matchup
sa.getTargets().add(humanCreature); sa.getTargets().add(humanCreature);
return true; return true;
} else if (humanCreature.getSVar("Targeting").equals("Dies")) { } else if (humanCreature.getSVar("Targeting").equals("Dies")) {
@@ -85,8 +84,7 @@ public class FightAi extends SpellAbilityAi {
for (Card aiCreature : aiCreatures) { for (Card aiCreature : aiCreatures) {
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower() if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower()
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(aiCreature)) { && humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(aiCreature)) {
// todo: check min/max targets; see if we picked the // todo: check min/max targets; see if we picked the best matchup
// best matchup
sa.getTargets().add(humanCreature); sa.getTargets().add(humanCreature);
sa.getTargets().add(aiCreature); sa.getTargets().add(aiCreature);
return true; return true;

View File

@@ -114,7 +114,7 @@ public class LifeSetAi extends SpellAbilityAi {
sa.setXManaCostPaid(xPay); sa.setXManaCostPaid(xPay);
amount = xPay; amount = xPay;
} else { } else {
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa); amount = AbilityUtils.calculateAmount(source, amountStr, sa);
} }
// special cases when amount can't be calculated without targeting first // special cases when amount can't be calculated without targeting first

View File

@@ -78,7 +78,7 @@ public class MustBlockAi extends SpellAbilityAi {
Card attacker = null; Card attacker = null;
if (sa.hasParam("DefinedAttacker")) { if (sa.hasParam("DefinedAttacker")) {
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DefinedAttacker"), sa); final List<Card> cards = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedAttacker"), sa);
if (cards.isEmpty()) { if (cards.isEmpty()) {
return false; return false;
} }

View File

@@ -33,7 +33,6 @@ public class PermanentAi extends SpellAbilityAi {
*/ */
@Override @Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) { protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final Card card = sa.getHostCard(); final Card card = sa.getHostCard();
if (card.hasKeyword("MayFlashSac") && !ai.couldCastSorcery(sa)) { if (card.hasKeyword("MayFlashSac") && !ai.couldCastSorcery(sa)) {
@@ -51,7 +50,6 @@ public class PermanentAi extends SpellAbilityAi {
*/ */
@Override @Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) { protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
final Card card = sa.getHostCard(); final Card card = sa.getHostCard();
final Game game = ai.getGame(); final Game game = ai.getGame();

View File

@@ -58,7 +58,6 @@ public class PermanentCreatureAi extends PermanentAi {
*/ */
@Override @Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) { protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final Card card = sa.getHostCard(); final Card card = sa.getHostCard();
final Game game = ai.getGame(); final Game game = ai.getGame();
@@ -176,7 +175,6 @@ public class PermanentCreatureAi extends PermanentAi {
} }
} }
if (hasFloatMana || willDiscardNow || willDieNow) { if (hasFloatMana || willDiscardNow || willDieNow) {
// Will lose mana in pool or about to discard a card in cleanup or about to die in combat, so use this opportunity // Will lose mana in pool or about to discard a card in cleanup or about to die in combat, so use this opportunity
return true; return true;
@@ -207,7 +205,6 @@ public class PermanentCreatureAi extends PermanentAi {
@Override @Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) { protected boolean checkApiLogic(Player ai, SpellAbility sa) {
if (!super.checkApiLogic(ai, sa)) { if (!super.checkApiLogic(ai, sa)) {
return false; return false;
} }

View File

@@ -27,7 +27,6 @@ public class PermanentNoncreatureAi extends PermanentAi {
*/ */
@Override @Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) { protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
if (!super.checkApiLogic(ai, sa)) if (!super.checkApiLogic(ai, sa))
return false; return false;

View File

@@ -28,6 +28,7 @@ import forge.game.card.CardPredicates;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.Spell; import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityPredicates; import forge.game.spellability.SpellAbilityPredicates;
@@ -63,7 +64,7 @@ public class PlayAi extends SpellAbilityAi {
return false; return false;
} }
} else if (!sa.hasParam("Valid")) { } else if (!sa.hasParam("Valid")) {
cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa); cards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
if (cards.isEmpty()) { if (cards.isEmpty()) {
return false; return false;
} }
@@ -158,6 +159,11 @@ public class PlayAi extends SpellAbilityAi {
return true; return true;
} }
@Override
public boolean confirmAction(Player ai, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return true;
}
/* (non-Javadoc) /* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean) * @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
*/ */
@@ -192,7 +198,7 @@ public class PlayAi extends SpellAbilityAi {
spell = (Spell) spell.copyWithDefinedCost(abCost); spell = (Spell) spell.copyWithDefinedCost(abCost);
} }
if (AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlayFromEffectAI(spell, !isOptional, true)) { if (AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlayFromEffectAI(spell, !(isOptional || sa.hasParam("Optional")), true)) {
// Before accepting, see if the spell has a valid number of targets (it should at this point). // Before accepting, see if the spell has a valid number of targets (it should at this point).
// Proceeding past this point if the spell is not correctly targeted will result // Proceeding past this point if the spell is not correctly targeted will result
// in "Failed to add to stack" error and the card disappearing from the game completely. // in "Failed to add to stack" error and the card disappearing from the game completely.

View File

@@ -202,11 +202,10 @@ public class ProtectAi extends SpellAbilityAi {
sa.resetTargets(); sa.resetTargets();
CardCollection list = getProtectCreatures(ai, sa); CardCollection list = getProtectCreatures(ai, sa);
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa); list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), source, sa);
if (game.getStack().isEmpty()) { if (game.getStack().isEmpty()) {
// If the cost is tapping, don't activate before declare // If the cost is tapping, don't activate before declare attack/block
// attack/block
if (sa.getPayCosts().hasTapCost()) { if (sa.getPayCosts().hasTapCost()) {
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& game.getPhaseHandler().isPlayerTurn(ai)) { && game.getPhaseHandler().isPlayerTurn(ai)) {
@@ -341,7 +340,7 @@ public class ProtectAi extends SpellAbilityAi {
sa.getTargets().add(c); sa.getTargets().add(c);
} }
if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) { if (sa.getTargets().size() < tgt.getMinTargets(source, sa)) {
sa.resetTargets(); sa.resetTargets();
return false; return false;
} }

View File

@@ -382,8 +382,7 @@ public class PumpAi extends PumpAiBase {
return false; return false;
} }
// when this happens we need to expand AI to consider if its ok for // when this happens we need to expand AI to consider if its ok for everything?
// everything?
for (final Card card : cards) { for (final Card card : cards) {
if (sa.isCurse()) { if (sa.isCurse()) {
if (!card.getController().isOpponentOf(ai)) { if (!card.getController().isOpponentOf(ai)) {
@@ -488,7 +487,31 @@ public class PumpAi extends PumpAiBase {
// each player sacrifices one permanent, e.g. Vaevictis, Asmadi the Dire - grab the worst for allied and // each player sacrifices one permanent, e.g. Vaevictis, Asmadi the Dire - grab the worst for allied and
// the best for opponents // the best for opponents
return SacrificeAi.doSacOneEachLogic(ai, sa); return SacrificeAi.doSacOneEachLogic(ai, sa);
} else if (sa.getParam("AILogic").equals("Destroy")) {
List<Card> tgts = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
if (tgts.isEmpty()) {
return false;
}
List<Card> alliedTgts = CardLists.filter(tgts, Predicates.or(CardPredicates.isControlledByAnyOf(ai.getAllies()), CardPredicates.isController(ai)));
List<Card> oppTgts = CardLists.filter(tgts, CardPredicates.isControlledByAnyOf(ai.getRegisteredOpponents()));
Card destroyTgt = null;
if (!oppTgts.isEmpty()) {
destroyTgt = ComputerUtilCard.getBestAI(oppTgts);
} else {
// TODO: somehow limit this so that the AI doesn't always destroy own stuff when able?
destroyTgt = ComputerUtilCard.getWorstAI(alliedTgts);
}
if (destroyTgt != null) {
sa.getTargets().add(destroyTgt);
return true;
}
return false;
} }
if (isFight) { if (isFight) {
return FightAi.canFightAi(ai, sa, attack, defense); return FightAi.canFightAi(ai, sa, attack, defense);
} }
@@ -517,8 +540,7 @@ public class PumpAi extends PumpAiBase {
list = CardLists.getValidCards(list, tgt.getValidTgts(), ai, source, sa); list = CardLists.getValidCards(list, tgt.getValidTgts(), ai, source, sa);
if (game.getStack().isEmpty()) { if (game.getStack().isEmpty()) {
// If the cost is tapping, don't activate before declare // If the cost is tapping, don't activate before declare attack/block
// attack/block
if (sa.getPayCosts().hasTapCost()) { if (sa.getPayCosts().hasTapCost()) {
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& game.getPhaseHandler().isPlayerTurn(ai)) { && game.getPhaseHandler().isPlayerTurn(ai)) {

View File

@@ -61,20 +61,20 @@ public class PumpAllAi extends PumpAiBase {
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
final Player opp = ai.getStrongestOpponent(); final Player opp = ai.getStrongestOpponent();
if (tgt != null && sa.canTarget(opp) && sa.hasParam("IsCurse")) { if (tgt != null && sa.canTarget(opp) && sa.isCurse()) {
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(opp); sa.getTargets().add(opp);
return true; return true;
} }
if (tgt != null && sa.canTarget(ai) && !sa.hasParam("IsCurse")) { if (tgt != null && sa.canTarget(ai) && !sa.isCurse()) {
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(ai); sa.getTargets().add(ai);
return true; return true;
} }
final int power = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumAtt"), sa); final int power = AbilityUtils.calculateAmount(source, sa.getParam("NumAtt"), sa);
final int defense = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumDef"), sa); final int defense = AbilityUtils.calculateAmount(source, sa.getParam("NumDef"), sa);
final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<>(); final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<>();
final PhaseType phase = game.getPhaseHandler().getPhase(); final PhaseType phase = game.getPhaseHandler().getPhase();
@@ -88,7 +88,7 @@ public class PumpAllAi extends PumpAiBase {
if (!game.getStack().isEmpty() && !sa.isCurse()) { if (!game.getStack().isEmpty() && !sa.isCurse()) {
return pumpAgainstRemoval(ai, sa, comp); return pumpAgainstRemoval(ai, sa, comp);
} }
if (sa.hasParam("IsCurse")) { if (sa.isCurse()) {
if (defense < 0) { // try to destroy creatures if (defense < 0) { // try to destroy creatures
comp = CardLists.filter(comp, new Predicate<Card>() { comp = CardLists.filter(comp, new Predicate<Card>() {
@Override @Override

View File

@@ -23,7 +23,6 @@ public class SacrificeAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
return sacrificeTgtAI(ai, sa); return sacrificeTgtAI(ai, sa);
} }

View File

@@ -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.player.PlayerActionConfirmMode; import forge.game.player.PlayerActionConfirmMode;
@@ -8,16 +9,33 @@ import forge.game.spellability.SpellAbility;
public class SkipPhaseAi extends SpellAbilityAi { public class SkipPhaseAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return true; return targetPlayer(aiPlayer, sa, false);
} }
@Override @Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return mandatory || canPlayAI(aiPlayer, sa); return targetPlayer(aiPlayer, sa, mandatory);
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return true; return true;
} }
public boolean targetPlayer(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
sa.resetTargets();
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
}
else if (mandatory && sa.canTarget(ai)) {
sa.getTargets().add(ai);
}
else {
return false;
}
}
return true;
}
} }

View File

@@ -1,6 +1,5 @@
package forge.ai.ability; package forge.ai.ability;
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;

View File

@@ -50,7 +50,6 @@ public class TokenAi extends SpellAbilityAi {
@Override @Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) { protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
// Planeswalker-related flags // Planeswalker-related flags
boolean pwMinus = false; boolean pwMinus = false;
@@ -143,7 +142,7 @@ public class TokenAi extends SpellAbilityAi {
final Player opp = ai.getWeakestOpponent(); final Player opp = ai.getWeakestOpponent();
if (ComputerUtil.preventRunAwayActivations(sa)) { if (ComputerUtil.preventRunAwayActivations(sa)) {
return false; // prevent infinite tokens? return false; // prevent infinite tokens?
} }
Card actualToken = spawnToken(ai, sa); Card actualToken = spawnToken(ai, sa);

View File

@@ -0,0 +1,75 @@
package forge.ai.ability;
import com.google.common.collect.Lists;
import forge.ai.AiPlayDecision;
import forge.ai.AiProps;
import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi;
import forge.card.ICardFace;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.util.Aggregates;
import java.util.List;
import java.util.Map;
public class VentureAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
// TODO: is it ever a bad idea to venture into a dungeon?
return true;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return mandatory || canPlayAI(aiPlayer, sa);
}
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return true;
}
// AI that handles choosing the next room in a dungeon
@Override
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells, Map<String, Object> params) {
List<SpellAbility> viableRooms = Lists.newArrayList();
for (SpellAbility room : spells) {
if (player.getController().isAI()) { // FIXME: is this needed? Can simulation ever run this for a non-AI player?
if (((PlayerControllerAi)player.getController()).getAi().canPlaySa(room) == AiPlayDecision.WillPlay) {
viableRooms.add(room);
}
}
}
if (!viableRooms.isEmpty()) {
// choose a room at random from the ones that are deemed playable
return Aggregates.random(viableRooms);
}
return Aggregates.random(spells); // If we're here, we should choose at least something, so choose a random thing then
}
// AI that chooses which dungeon to venture into
@Override
public String chooseCardName(Player ai, SpellAbility sa, List<ICardFace> faces) {
// TODO: improve the conditions that define which dungeon is a viable option to choose
List<String> dungeonNames = Lists.newArrayList();
for (ICardFace face : faces) {
dungeonNames.add(face.getName());
}
// Don't choose Tomb of Annihilation when life in danger unless we can win right away or can't lose for 0 life
if (ai.getController().isAI()) { // FIXME: is this needed? Can simulation ever run this for a non-AI player?
int lifeInDanger = (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD));
if ((ai.getLife() <= lifeInDanger && !ai.cantLoseForZeroOrLessLife())
&& !(ai.getLife() > 1 && ai.getWeakestOpponent().getLife() == 1)) {
dungeonNames.remove("Tomb of Annihilation");
}
}
return Aggregates.random(dungeonNames);
}
}

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>1.6.40-SNAPSHOT</version> <version>1.6.43-SNAPSHOT</version>
</parent> </parent>
<artifactId>forge-core</artifactId> <artifactId>forge-core</artifactId>

View File

@@ -167,6 +167,22 @@ public class StaticData {
return sortedEditions; return sortedEditions;
} }
private TreeMap<CardEdition.Type, List<CardEdition>> editionsTypeMap;
public final Map<CardEdition.Type, List<CardEdition>> getEditionsTypeMap(){
if (editionsTypeMap == null){
editionsTypeMap = new TreeMap<>();
for (CardEdition.Type editionType : CardEdition.Type.values()){
editionsTypeMap.put(editionType, new ArrayList<>());
}
for (CardEdition edition : this.getSortedEditions()){
CardEdition.Type key = edition.getType();
List<CardEdition> editionsOfType = editionsTypeMap.get(key);
editionsOfType.add(edition);
}
}
return editionsTypeMap;
}
public CardEdition getCardEdition(String setCode){ public CardEdition getCardEdition(String setCode){
CardEdition edition = this.editions.get(setCode); CardEdition edition = this.editions.get(setCode);
if (edition == null) // try custom editions if (edition == null) // try custom editions
@@ -284,7 +300,7 @@ public class StaticData {
return CardDb.CardArtPreference.getPreferences(); return CardDb.CardArtPreference.getPreferences();
} }
public void setStandardPredicate(Predicate<PaperCard> standardPredicate) { this.standardPredicate = standardPredicate; } public void setStandardPredicate(Predicate<PaperCard> standardPredicate) { this.standardPredicate = standardPredicate; }

View File

@@ -716,7 +716,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
} catch (Exception ex) { } catch (Exception ex) {
return false; return false;
} }
return edition != null && edition.getType() != Type.PROMOS; return edition != null && edition.getType() != Type.PROMO;
} }
})); }));
} }
@@ -728,7 +728,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
CardEdition edition = null; CardEdition edition = null;
try { try {
edition = editions.getEditionByCodeOrThrow(paperCard.getEdition()); edition = editions.getEditionByCodeOrThrow(paperCard.getEdition());
if (edition.getType() == Type.PROMOS || edition.getType() == Type.REPRINT) if (edition.getType() == Type.PROMO||edition.getType() == Type.REPRINT)
return false; return false;
} catch (Exception ex) { } catch (Exception ex) {
return false; return false;

View File

@@ -56,19 +56,23 @@ public final class CardEdition implements Comparable<CardEdition> {
CORE, CORE,
EXPANSION, EXPANSION,
REPRINT,
ONLINE,
STARTER, STARTER,
REPRINT,
BOXED_SET,
DUEL_DECKS, COLLECTOR_EDITION,
PREMIUM_DECK_SERIES, DUEL_DECK,
FROM_THE_VAULT, PROMO,
ONLINE,
OTHER, DRAFT,
PROMOS,
COMMANDER,
MULTIPLAYER,
FUNNY, FUNNY,
THIRDPARTY; // custom sets
OTHER, // FALLBACK CATEGORY
CUSTOM_SET; // custom sets
public String getBoosterBoxDefault() { public String getBoosterBoxDefault() {
switch (this) { switch (this) {
@@ -79,6 +83,19 @@ public final class CardEdition implements Comparable<CardEdition> {
return "0"; return "0";
} }
} }
public String toString(){
String[] names = TextUtil.splitWithParenthesis(this.name().toLowerCase(), '_');
for (int i = 0; i < names.length; i++)
names[i] = TextUtil.capitalize(names[i]);
return TextUtil.join(Arrays.asList(names), " ");
}
public static Type fromString(String label){
List<String> names = Arrays.asList(TextUtil.splitWithParenthesis(label.toUpperCase(), ' '));
String value = TextUtil.join(names, "_");
return Type.valueOf(value);
}
} }
public enum FoilType { public enum FoilType {
@@ -103,6 +120,7 @@ public final class CardEdition implements Comparable<CardEdition> {
SPECIAL_SLOT("special slot"), //to help with convoluted boosters SPECIAL_SLOT("special slot"), //to help with convoluted boosters
PRECON_PRODUCT("precon product"), PRECON_PRODUCT("precon product"),
BORDERLESS("borderless"), BORDERLESS("borderless"),
ETCHED("etched"),
SHOWCASE("showcase"), SHOWCASE("showcase"),
EXTENDED_ART("extended art"), EXTENDED_ART("extended art"),
ALTERNATE_ART("alternate art"), ALTERNATE_ART("alternate art"),
@@ -247,6 +265,7 @@ public final class CardEdition implements Comparable<CardEdition> {
private int boosterArts = 1; private int boosterArts = 1;
private SealedProduct.Template boosterTpl = null; private SealedProduct.Template boosterTpl = null;
private final Map<String, SealedProduct.Template> boosterTemplates = new HashMap<>();
private CardEdition(ListMultimap<String, CardInSet> cardMap, Map<String, Integer> tokens, Map<String, List<String>> customPrintSheetsToParse) { private CardEdition(ListMultimap<String, CardInSet> cardMap, Map<String, Integer> tokens, Map<String, List<String>> customPrintSheetsToParse) {
this.cardMap = cardMap; this.cardMap = cardMap;
@@ -393,11 +412,28 @@ public final class CardEdition implements Comparable<CardEdition> {
} }
public SealedProduct.Template getBoosterTemplate() { public SealedProduct.Template getBoosterTemplate() {
return boosterTpl; return getBoosterTemplate("Draft");
}
public SealedProduct.Template getBoosterTemplate(String boosterType) {
return boosterTemplates.get(boosterType);
}
public String getRandomBoosterKind() {
List<String> boosterTypes = Lists.newArrayList(boosterTemplates.keySet());
if (boosterTypes.isEmpty()) {
return null;
}
Collections.shuffle(boosterTypes);
return boosterTypes.get(0);
}
public Set<String> getAvailableBoosterTypes() {
return boosterTemplates.keySet();
} }
public boolean hasBoosterTemplate() { public boolean hasBoosterTemplate() {
return boosterTpl != null; return boosterTemplates.containsKey("Draft");
} }
public List<PrintSheet> getPrintSheetsBySection() { public List<PrintSheet> getPrintSheetsBySection() {
@@ -435,8 +471,16 @@ public final class CardEdition implements Comparable<CardEdition> {
} }
public static class Reader extends StorageReaderFolder<CardEdition> { public static class Reader extends StorageReaderFolder<CardEdition> {
private boolean isCustomEditions;
public Reader(File path) { public Reader(File path) {
super(path, CardEdition.FN_GET_CODE); super(path, CardEdition.FN_GET_CODE);
this.isCustomEditions = false;
}
public Reader(File path, boolean isCustomEditions) {
super(path, CardEdition.FN_GET_CODE);
this.isCustomEditions = isCustomEditions;
} }
@Override @Override
@@ -537,19 +581,38 @@ public final class CardEdition implements Comparable<CardEdition> {
res.boosterArts = section.getInt("BoosterCovers", 1); res.boosterArts = section.getInt("BoosterCovers", 1);
String boosterDesc = section.get("Booster"); String boosterDesc = section.get("Booster");
res.boosterTpl = boosterDesc == null ? null : new SealedProduct.Template(res.code, SealedProduct.Template.Reader.parseSlots(boosterDesc));
if (section.contains("Booster")) {
// Historical naming convention in Forge for "DraftBooster"
res.boosterTpl = new SealedProduct.Template(res.code, SealedProduct.Template.Reader.parseSlots(boosterDesc));
res.boosterTemplates.put("Draft", res.boosterTpl);
}
String[] boostertype = { "Draft", "Collector", "Set" };
// Theme boosters aren't here because they are closer to preconstructed decks, and should be treated as such
for (String type : boostertype) {
String name = type + "Booster";
if (section.contains(name)) {
res.boosterTemplates.put(type, new SealedProduct.Template(res.code, SealedProduct.Template.Reader.parseSlots(section.get(name))));
}
}
res.alias = section.get("alias"); res.alias = section.get("alias");
res.borderColor = BorderColor.valueOf(section.get("border", "Black").toUpperCase(Locale.ENGLISH)); res.borderColor = BorderColor.valueOf(section.get("border", "Black").toUpperCase(Locale.ENGLISH));
String type = section.get("type");
Type enumType = Type.UNKNOWN; Type enumType = Type.UNKNOWN;
if (null != type && !type.isEmpty()) { if (this.isCustomEditions){
try { enumType = Type.CUSTOM_SET; // Forcing ThirdParty Edition Type to avoid inconsistencies
enumType = Type.valueOf(type.toUpperCase(Locale.ENGLISH)); } else {
} catch (IllegalArgumentException ignored) { String type = section.get("type");
// ignore; type will get UNKNOWN if (null != type && !type.isEmpty()) {
System.err.println("Ignoring unknown type in set definitions: name: " + res.name + "; type: " + type); try {
enumType = Type.valueOf(type.toUpperCase(Locale.ENGLISH));
} catch (IllegalArgumentException ignored) {
// ignore; type will get UNKNOWN
System.err.println("Ignoring unknown type in set definitions: name: " + res.name + "; type: " + type);
}
} }
} }
res.type = enumType; res.type = enumType;
res.prerelease = section.get("Prerelease", null); res.prerelease = section.get("Prerelease", null);
@@ -684,14 +747,16 @@ public final class CardEdition implements Comparable<CardEdition> {
}; };
public IItemReader<SealedProduct.Template> getBoosterGenerator() { public IItemReader<SealedProduct.Template> getBoosterGenerator() {
// TODO Auto-generated method stub
return new StorageReaderBase<SealedProduct.Template>(null) { return new StorageReaderBase<SealedProduct.Template>(null) {
@Override @Override
public Map<String, SealedProduct.Template> readAll() { public Map<String, SealedProduct.Template> readAll() {
Map<String, SealedProduct.Template> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); Map<String, SealedProduct.Template> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for(CardEdition ce : Collection.this) { for(CardEdition ce : Collection.this) {
if (ce.hasBoosterTemplate()) { List<String> boosterTypes = Lists.newArrayList(ce.getAvailableBoosterTypes());
map.put(ce.getCode(), ce.getBoosterTemplate()); for (String type : boosterTypes) {
String setAffix = type.equals("Draft") ? "" : type;
map.put(ce.getCode() + setAffix, ce.getBoosterTemplate(type));
} }
} }
return map; return map;

View File

@@ -6,12 +6,12 @@
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
@@ -34,7 +34,7 @@ import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;
/** /**
* A collection of methods containing full * A collection of methods containing full
* meta and gameplay properties of a card. * meta and gameplay properties of a card.
* *
* @author Forge * @author Forge
* @version $Id: CardRules.java 9708 2011-08-09 19:34:12Z jendave $ * @version $Id: CardRules.java 9708 2011-08-09 19:34:12Z jendave $
*/ */
@@ -116,7 +116,7 @@ public final class CardRules implements ICardCharacteristics {
public boolean isVariant() { public boolean isVariant() {
CardType t = getType(); CardType t = getType();
return t.isVanguard() || t.isScheme() || t.isPlane() || t.isPhenomenon() || t.isConspiracy(); return t.isVanguard() || t.isScheme() || t.isPlane() || t.isPhenomenon() || t.isConspiracy() || t.isDungeon();
} }
public CardSplitType getSplitType() { public CardSplitType getSplitType() {
@@ -211,11 +211,20 @@ public final class CardRules implements ICardCharacteristics {
} }
public boolean canBeCommander() { public boolean canBeCommander() {
CardType type = mainPart.getType(); if (mainPart.getOracleText().contains("can be your commander")) {
if (type.isLegendary() && type.isCreature()) {
return true; return true;
} }
return mainPart.getOracleText().contains("can be your commander"); CardType type = mainPart.getType();
boolean creature = type.isCreature();
for (String staticAbility : mainPart.getStaticAbilities()) { // Check for Grist
if (staticAbility.contains("CharacteristicDefining$ True") && staticAbility.contains("AddType$ Creature")) {
creature = true;
}
}
if (type.isLegendary() && creature) {
return true;
}
return false;
} }
public boolean canBePartnerCommander() { public boolean canBePartnerCommander() {
@@ -234,12 +243,38 @@ public final class CardRules implements ICardCharacteristics {
public boolean canBeBrawlCommander() { public boolean canBeBrawlCommander() {
CardType type = mainPart.getType(); CardType type = mainPart.getType();
return type.isLegendary() && (type.isCreature() || type.isPlaneswalker()); if (!type.isLegendary()) {
return false;
}
if (type.isCreature() || type.isPlaneswalker()) {
return true;
}
// Grist is checked above, but new cards might work this way
for (String staticAbility : mainPart.getStaticAbilities()) {
if (staticAbility.contains("CharacteristicDefining$ True") && staticAbility.contains("AddType$ Creature")) {
return true;
}
}
return false;
} }
public boolean canBeTinyLeadersCommander() { public boolean canBeTinyLeadersCommander() {
CardType type = mainPart.getType(); CardType type = mainPart.getType();
return type.isLegendary() && (type.isCreature() || type.isPlaneswalker()); if (!type.isLegendary()) {
return false;
}
if (type.isCreature() || type.isPlaneswalker()) {
return true;
}
// Grist is checked above, but new cards might work this way
for (String staticAbility : mainPart.getStaticAbilities()) {
if (staticAbility.contains("CharacteristicDefining$ True") && staticAbility.contains("AddType$ Creature")) {
return true;
}
}
return false;
} }
public String getMeldWith() { public String getMeldWith() {
@@ -334,7 +369,7 @@ public final class CardRules implements ICardCharacteristics {
/** /**
* Gets the card. * Gets the card.
* *
* @return the card * @return the card
*/ */
public final CardRules getCard() { public final CardRules getCard() {
@@ -370,7 +405,7 @@ public final class CardRules implements ICardCharacteristics {
/** /**
* Parses the line. * Parses the line.
* *
* @param line * @param line
* the line * the line
*/ */
@@ -528,7 +563,7 @@ public final class CardRules implements ICardCharacteristics {
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* @see java.util.Iterator#hasNext() * @see java.util.Iterator#hasNext()
*/ */
@Override @Override
@@ -538,7 +573,7 @@ public final class CardRules implements ICardCharacteristics {
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* @see java.util.Iterator#next() * @see java.util.Iterator#next()
*/ */
@Override @Override
@@ -554,7 +589,7 @@ public final class CardRules implements ICardCharacteristics {
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* @see java.util.Iterator#remove() * @see java.util.Iterator#remove()
*/ */
@Override @Override

View File

@@ -598,6 +598,7 @@ public final class CardRulesPredicates {
public static final Predicate<CardRules> IS_SCHEME = CardRulesPredicates.coreType(true, CardType.CoreType.Scheme); public static final Predicate<CardRules> IS_SCHEME = CardRulesPredicates.coreType(true, CardType.CoreType.Scheme);
public static final Predicate<CardRules> IS_VANGUARD = CardRulesPredicates.coreType(true, CardType.CoreType.Vanguard); public static final Predicate<CardRules> IS_VANGUARD = CardRulesPredicates.coreType(true, CardType.CoreType.Vanguard);
public static final Predicate<CardRules> IS_CONSPIRACY = CardRulesPredicates.coreType(true, CardType.CoreType.Conspiracy); public static final Predicate<CardRules> IS_CONSPIRACY = CardRulesPredicates.coreType(true, CardType.CoreType.Conspiracy);
public static final Predicate<CardRules> IS_DUNGEON = CardRulesPredicates.coreType(true, CardType.CoreType.Dungeon);
public static final Predicate<CardRules> IS_NON_LAND = CardRulesPredicates.coreType(false, CardType.CoreType.Land); public static final Predicate<CardRules> IS_NON_LAND = CardRulesPredicates.coreType(false, CardType.CoreType.Land);
public static final Predicate<CardRules> CAN_BE_BRAWL_COMMANDER = Predicates.and(Presets.IS_LEGENDARY, public static final Predicate<CardRules> CAN_BE_BRAWL_COMMANDER = Predicates.and(Presets.IS_LEGENDARY,
Predicates.or(Presets.IS_CREATURE, Presets.IS_PLANESWALKER)); Predicates.or(Presets.IS_CREATURE, Presets.IS_PLANESWALKER));

View File

@@ -57,6 +57,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
Artifact(true, "artifacts"), Artifact(true, "artifacts"),
Conspiracy(false, "conspiracies"), Conspiracy(false, "conspiracies"),
Creature(true, "creatures"), Creature(true, "creatures"),
Dungeon(false, "dungeons"),
Emblem(false, "emblems"), Emblem(false, "emblems"),
Enchantment(true, "enchantments"), Enchantment(true, "enchantments"),
Instant(false, "instants"), Instant(false, "instants"),
@@ -446,6 +447,11 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
return coreTypes.contains(CoreType.Tribal); return coreTypes.contains(CoreType.Tribal);
} }
@Override
public boolean isDungeon() {
return coreTypes.contains(CoreType.Dungeon);
}
@Override @Override
public String toString() { public String toString() {
if (calculatedType == null) { if (calculatedType == null) {
@@ -686,13 +692,11 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
} }
private static boolean isMultiwordType(final String type) { private static boolean isMultiwordType(final String type) {
final String[] multiWordTypes = { "Serra's Realm", "Bolas's Meditation Realm" }; final String[] multiWordTypes = { "Serra's Realm", "Bolas's Meditation Realm", "Dungeon Master" };
// no need to loop for only 2 exceptions! for (int i = 0; i < multiWordTypes.length; ++i) {
if (multiWordTypes[0].startsWith(type) && !multiWordTypes[0].equals(type)) { if (multiWordTypes[i].startsWith(type) && !multiWordTypes[i].equals(type)) {
return true; return true;
} }
if (multiWordTypes[1].startsWith(type) && !multiWordTypes[1].equals(type)) {
return true;
} }
return false; return false;
} }

View File

@@ -44,5 +44,6 @@ public interface CardTypeView extends Iterable<String>, Serializable {
boolean isPhenomenon(); boolean isPhenomenon();
boolean isEmblem(); boolean isEmblem();
boolean isTribal(); boolean isTribal();
boolean isDungeon();
CardTypeView getTypeWithChanges(Iterable<CardChangedType> changedCardTypes); CardTypeView getTypeWithChanges(Iterable<CardChangedType> changedCardTypes);
} }

View File

@@ -35,9 +35,12 @@ public class BoosterPack extends SealedProduct {
public static final Function<CardEdition, BoosterPack> FN_FROM_SET = new Function<CardEdition, BoosterPack>() { public static final Function<CardEdition, BoosterPack> FN_FROM_SET = new Function<CardEdition, BoosterPack>() {
@Override @Override
public BoosterPack apply(final CardEdition arg1) { public BoosterPack apply(final CardEdition edition) {
Template d = StaticData.instance().getBoosters().get(arg1.getCode()); String boosterKind = edition.getRandomBoosterKind();
return new BoosterPack(arg1.getName(), d); Template d = edition.getBoosterTemplate(boosterKind);
StringBuilder sb = new StringBuilder(edition.getName());
sb.append(" ").append(boosterKind);
return new BoosterPack(sb.toString(), d);
} }
}; };

View File

@@ -132,7 +132,7 @@ public abstract class SealedProduct implements InventoryItemFromSet {
public static class Template { public static class Template {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public final static Template genericBooster = new Template(null, Lists.newArrayList( public final static Template genericDraftBooster = new Template(null, Lists.newArrayList(
Pair.of(BoosterSlots.COMMON, 10), Pair.of(BoosterSlots.UNCOMMON, 3), Pair.of(BoosterSlots.COMMON, 10), Pair.of(BoosterSlots.UNCOMMON, 3),
Pair.of(BoosterSlots.RARE_MYTHIC, 1), Pair.of(BoosterSlots.BASIC_LAND, 1) Pair.of(BoosterSlots.RARE_MYTHIC, 1), Pair.of(BoosterSlots.BASIC_LAND, 1)
)); ));

View File

@@ -236,6 +236,11 @@ public class BoosterGenerator {
String slotType = slot.getLeft(); // add expansion symbol here? String slotType = slot.getLeft(); // add expansion symbol here?
int numCards = slot.getRight(); int numCards = slot.getRight();
boolean convertCardFoil = slotType.endsWith("+");
if (convertCardFoil) {
slotType = slotType.substring(0, slotType.length() - 1);
}
String[] sType = TextUtil.splitWithParenthesis(slotType, ' '); String[] sType = TextUtil.splitWithParenthesis(slotType, ' ');
String setCode = sType.length == 1 && template.getEdition() != null ? template.getEdition() : null; String setCode = sType.length == 1 && template.getEdition() != null ? template.getEdition() : null;
String sheetKey = StaticData.instance().getEditions().contains(setCode) ? slotType.trim() + " " + setCode String sheetKey = StaticData.instance().getEditions().contains(setCode) ? slotType.trim() + " " + setCode
@@ -280,7 +285,19 @@ public class BoosterGenerator {
} }
PrintSheet ps = getPrintSheet(sheetKey); PrintSheet ps = getPrintSheet(sheetKey);
result.addAll(ps.random(numCards, true)); List<PaperCard> paperCards;
// For cards that end in '+', attempt to convert this card to foil.
if (convertCardFoil) {
paperCards = Lists.newArrayList();
for(PaperCard pc : ps.random(numCards, true)) {
paperCards.add(pc.getFoiled());
}
} else {
paperCards = ps.random(numCards, true);
}
result.addAll(paperCards);
sheetsUsed.add(ps); sheetsUsed.add(ps);
if (foilInThisSlot) { if (foilInThisSlot) {
@@ -508,6 +525,7 @@ public class BoosterGenerator {
mainCode.regionMatches(true, 0, "wholeSheet", 0, 10) mainCode.regionMatches(true, 0, "wholeSheet", 0, 10)
) { // custom print sheet ) { // custom print sheet
String sheetName = StringUtils.strip(mainCode.substring(10), "()\" "); String sheetName = StringUtils.strip(mainCode.substring(10), "()\" ");
System.out.println("Attempting to lookup :" + sheetName);
src = StaticData.instance().getPrintSheets().get(sheetName).toFlatList(); src = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
setPred = Predicates.alwaysTrue(); setPred = Predicates.alwaysTrue();

View File

@@ -18,7 +18,7 @@ public class TokenDb implements ITokenDatabase {
// colors_power_toughness_cardtypes_sub_types_keywords // colors_power_toughness_cardtypes_sub_types_keywords
// Some examples: // Some examples:
// c_3_3_a_wurm_lifelink // c_3_3_a_phyrexian_wurm_lifelink
// w_2_2_knight_first_strike // w_2_2_knight_first_strike
// The image names should be the same as the script name + _set // The image names should be the same as the script name + _set

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>1.6.40-SNAPSHOT</version> <version>1.6.43-SNAPSHOT</version>
</parent> </parent>
<artifactId>forge-game</artifactId> <artifactId>forge-game</artifactId>

View File

@@ -11,6 +11,7 @@ import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbility;
import forge.game.zone.ZoneType;
import forge.util.Expressions; import forge.util.Expressions;
public class ForgeScript { public class ForgeScript {
@@ -122,10 +123,8 @@ public class ForgeScript {
return Expressions.compare(y, property, x); return Expressions.compare(y, property, x);
} else return cardState.getTypeWithChanges().hasStringType(property); } else return cardState.getTypeWithChanges().hasStringType(property);
} }
public static boolean spellAbilityHasProperty(SpellAbility sa, String property, Player sourceController, public static boolean spellAbilityHasProperty(SpellAbility sa, String property, Player sourceController,
Card source, CardTraitBase spellAbility) { Card source, CardTraitBase spellAbility) {
if (property.equals("ManaAbility")) { if (property.equals("ManaAbility")) {
@@ -155,6 +154,8 @@ public class ForgeScript {
return sa.isAftermath(); return sa.isAftermath();
} else if (property.equals("MorphUp")) { } else if (property.equals("MorphUp")) {
return sa.isMorphUp(); return sa.isMorphUp();
} else if (property.equals("Modular")) {
return sa.hasParam("Modular");
} else if (property.equals("Equip")) { } else if (property.equals("Equip")) {
return sa.hasParam("Equip"); return sa.hasParam("Equip");
} else if (property.equals("Boast")) { } else if (property.equals("Boast")) {
@@ -186,7 +187,14 @@ public class ForgeScript {
} else if (property.equals("OppCtrl")) { } else if (property.equals("OppCtrl")) {
return sa.getActivatingPlayer().isOpponentOf(sourceController); return sa.getActivatingPlayer().isOpponentOf(sourceController);
} else if (property.startsWith("cmc")) { } else if (property.startsWith("cmc")) {
int y = sa.getPayCosts().getTotalMana().getCMC(); int y = 0;
// spell was on the stack
if (sa.getCardState().getCard().isInZone(ZoneType.Stack)) {
y = sa.getHostCard().getCMC();
}
else {
y = sa.getPayCosts().getTotalMana().getCMC();
}
int x = AbilityUtils.calculateAmount(spellAbility.getHostCard(), property.substring(5), spellAbility); int x = AbilityUtils.calculateAmount(spellAbility.getHostCard(), property.substring(5), spellAbility);
if (!Expressions.compare(y, property, x)) { if (!Expressions.compare(y, property, x)) {
return false; return false;

View File

@@ -197,6 +197,15 @@ public class Game {
} }
} }
public CardCollectionView copyLastStateBattlefield() {
CardCollection result = new CardCollection();
Map<Integer, Card> cachedMap = Maps.newHashMap();
for (final Player p : getPlayers()) {
result.addAll(p.getZone(ZoneType.Battlefield).getLKICopy(cachedMap));
}
return result;
}
public void updateLastStateForCard(Card c) { public void updateLastStateForCard(Card c) {
if (c == null || c.getZone() == null) { if (c == null || c.getZone() == null) {
return; return;

View File

@@ -251,6 +251,7 @@ public class GameAction {
copied.setChangedCardTraits(c.getChangedCardTraits()); copied.setChangedCardTraits(c.getChangedCardTraits());
copied.copyChangedTextFrom(c); copied.copyChangedTextFrom(c);
copied.setTimestamp(c.getTimestamp());
// copy exiled properties when adding to stack // copy exiled properties when adding to stack
// will be cleanup later in MagicStack // will be cleanup later in MagicStack
@@ -672,6 +673,23 @@ public class GameAction {
c = changeZone(zoneFrom, zoneTo, c, position, cause, params); c = changeZone(zoneFrom, zoneTo, c, position, cause, params);
// need to refresh ability text for affected cards
for (final StaticAbility stAb : c.getStaticAbilities()) {
if (stAb.isSecondary() ||
!stAb.getParam("Mode").equals("CantBlockBy") ||
stAb.isSuppressed() || !stAb.checkConditions() ||
!stAb.hasParam("ValidAttacker") ||
(stAb.hasParam("ValidBlocker") && stAb.getParam("ValidBlocker").equals("Creature.Self"))) {
continue;
}
final Card host = stAb.getHostCard();
for (Card creature : Iterables.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)) {
if (creature.isValid(stAb.getParam("ValidAttacker").split(","), host.getController(), host, stAb)) {
creature.updateAbilityTextForView();
}
}
}
// Move card in maingame if take card from subgame // Move card in maingame if take card from subgame
// 720.4a // 720.4a
if (zoneFrom != null && zoneFrom.is(ZoneType.Sideboard) && game.getMaingame() != null) { if (zoneFrom != null && zoneFrom.is(ZoneType.Sideboard) && game.getMaingame() != null) {
@@ -1175,6 +1193,8 @@ public class GameAction {
for (final Card c : cards) { for (final Card c : cards) {
// If a token is in a zone other than the battlefield, it ceases to exist. // If a token is in a zone other than the battlefield, it ceases to exist.
checkAgain |= stateBasedAction704_5d(c); checkAgain |= stateBasedAction704_5d(c);
// Dungeon Card won't affect other cards, so don't need to set checkAgain
stateBasedAction_Dungeon(c);
} }
} }
} }
@@ -1235,7 +1255,7 @@ public class GameAction {
int loyal = c.getCounters(CounterEnumType.LOYALTY); int loyal = c.getCounters(CounterEnumType.LOYALTY);
if (loyal < beeble) { if (loyal < beeble) {
GameEntityCounterTable counterTable = new GameEntityCounterTable(); GameEntityCounterTable counterTable = new GameEntityCounterTable();
c.addCounter(CounterEnumType.LOYALTY, beeble - loyal, c.getController(), false, counterTable); c.addCounter(CounterEnumType.LOYALTY, beeble - loyal, c.getController(), null, false, counterTable);
counterTable.triggerCountersPutAll(game); counterTable.triggerCountersPutAll(game);
} else if (loyal > beeble) { } else if (loyal > beeble) {
c.subtractCounter(CounterEnumType.LOYALTY, loyal - beeble); c.subtractCounter(CounterEnumType.LOYALTY, loyal - beeble);
@@ -1261,7 +1281,7 @@ public class GameAction {
orderedNoRegCreats = true; orderedNoRegCreats = true;
} }
for (Card c : noRegCreats) { for (Card c : noRegCreats) {
sacrificeDestroy(c, null, table); sacrificeDestroy(c, null, table, null);
} }
} }
if (desCreats != null) { if (desCreats != null) {
@@ -1273,7 +1293,7 @@ public class GameAction {
orderedDesCreats = true; orderedDesCreats = true;
} }
for (Card c : desCreats) { for (Card c : desCreats) {
destroy(c, null, true, table); destroy(c, null, true, table, null);
} }
} }
setHoldCheckingStaticAbilities(false); setHoldCheckingStaticAbilities(false);
@@ -1359,12 +1379,21 @@ public class GameAction {
return false; return false;
} }
if (!game.getStack().hasSourceOnStack(c, SpellAbilityPredicates.isChapter())) { if (!game.getStack().hasSourceOnStack(c, SpellAbilityPredicates.isChapter())) {
sacrifice(c, null, table); sacrifice(c, null, table, null);
checkAgain = true; checkAgain = true;
} }
return checkAgain; return checkAgain;
} }
private void stateBasedAction_Dungeon(Card c) {
if (!c.getType().isDungeon() || !c.isInLastRoom()) {
return;
}
if (!game.getStack().hasSourceOnStack(c, null)) {
completeDungeon(c.getController(), c);
}
}
private boolean stateBasedAction704_attach(Card c, CardZoneTable table) { private boolean stateBasedAction704_attach(Card c, CardZoneTable table) {
boolean checkAgain = false; boolean checkAgain = false;
@@ -1387,7 +1416,7 @@ public class GameAction {
// cleanup aura // cleanup aura
if (c.isAura() && c.isInPlay() && !c.isEnchanting()) { if (c.isAura() && c.isInPlay() && !c.isEnchanting()) {
sacrificeDestroy(c, null, table); sacrificeDestroy(c, null, table, null);
checkAgain = true; checkAgain = true;
} }
return checkAgain; return checkAgain;
@@ -1539,7 +1568,7 @@ public class GameAction {
for (Card c : list) { for (Card c : list) {
if (c.getCounters(CounterEnumType.LOYALTY) <= 0) { if (c.getCounters(CounterEnumType.LOYALTY) <= 0) {
sacrificeDestroy(c, null, table); sacrificeDestroy(c, null, table, null);
// Play the Destroy sound // Play the Destroy sound
game.fireEvent(new GameEventCardDestroyed()); game.fireEvent(new GameEventCardDestroyed());
recheck = true; recheck = true;
@@ -1602,7 +1631,7 @@ public class GameAction {
"You have multiple legendary permanents named \""+name+"\" in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)", null); "You have multiple legendary permanents named \""+name+"\" in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)", null);
for (Card c: cc) { for (Card c: cc) {
if (c != toKeep) { if (c != toKeep) {
sacrificeDestroy(c, null, table); sacrificeDestroy(c, null, table, null);
} }
} }
game.fireEvent(new GameEventCardDestroyed()); game.fireEvent(new GameEventCardDestroyed());
@@ -1636,28 +1665,24 @@ public class GameAction {
} }
for (Card c : worlds) { for (Card c : worlds) {
sacrificeDestroy(c, null, table); sacrificeDestroy(c, null, table, null);
game.fireEvent(new GameEventCardDestroyed()); game.fireEvent(new GameEventCardDestroyed());
} }
return true; return true;
} }
@Deprecated public final Card sacrifice(final Card c, final SpellAbility source, CardZoneTable table, Map<AbilityKey, Object> params) {
public final Card sacrifice(final Card c, final SpellAbility source) {
return sacrifice(c, source, null);
}
public final Card sacrifice(final Card c, final SpellAbility source, CardZoneTable table) {
if (!c.canBeSacrificedBy(source)) { if (!c.canBeSacrificedBy(source)) {
return null; return null;
} }
c.getController().addSacrificedThisTurn(c, source); c.getController().addSacrificedThisTurn(c, source);
return sacrificeDestroy(c, source, table); return sacrificeDestroy(c, source, table, params);
} }
public final boolean destroy(final Card c, final SpellAbility sa, final boolean regenerate, CardZoneTable table) { public final boolean destroy(final Card c, final SpellAbility sa, final boolean regenerate, CardZoneTable table, Map<AbilityKey, Object> params) {
Player activator = null; Player activator = null;
if (!c.canBeDestroyed()) { if (!c.canBeDestroyed()) {
return false; return false;
@@ -1668,6 +1693,9 @@ public class GameAction {
repRunParams.put(AbilityKey.Source, sa); repRunParams.put(AbilityKey.Source, sa);
repRunParams.put(AbilityKey.Affected, c); repRunParams.put(AbilityKey.Affected, c);
repRunParams.put(AbilityKey.Regeneration, regenerate); repRunParams.put(AbilityKey.Regeneration, regenerate);
if (params != null) {
repRunParams.putAll(params);
}
if (game.getReplacementHandler().run(ReplacementType.Destroy, repRunParams) != ReplacementResult.NotReplaced) { if (game.getReplacementHandler().run(ReplacementType.Destroy, repRunParams) != ReplacementResult.NotReplaced) {
return false; return false;
@@ -1684,11 +1712,14 @@ public class GameAction {
// Run triggers // Run triggers
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(c); final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(c);
runParams.put(AbilityKey.Causer, activator); runParams.put(AbilityKey.Causer, activator);
if (params != null) {
runParams.putAll(params);
}
game.getTriggerHandler().runTrigger(TriggerType.Destroyed, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.Destroyed, runParams, false);
// in case the destroyed card has such a trigger // in case the destroyed card has such a trigger
game.getTriggerHandler().registerActiveLTBTrigger(c); game.getTriggerHandler().registerActiveLTBTrigger(c);
final Card sacrificed = sacrificeDestroy(c, sa, table); final Card sacrificed = sacrificeDestroy(c, sa, table, params);
return sacrificed != null; return sacrificed != null;
} }
@@ -1696,12 +1727,12 @@ public class GameAction {
* @return the sacrificed Card in its new location, or {@code null} if the * @return the sacrificed Card in its new location, or {@code null} if the
* sacrifice wasn't successful. * sacrifice wasn't successful.
*/ */
protected final Card sacrificeDestroy(final Card c, SpellAbility cause, CardZoneTable table) { protected final Card sacrificeDestroy(final Card c, SpellAbility cause, CardZoneTable table, Map<AbilityKey, Object> params) {
if (!c.isInPlay()) { if (!c.isInPlay()) {
return null; return null;
} }
final Card newCard = moveToGraveyard(c, cause, null); final Card newCard = moveToGraveyard(c, cause, params);
if (table != null && newCard != null && newCard.getZone() != null) { if (table != null && newCard != null && newCard.getZone() != null) {
table.put(ZoneType.Battlefield, newCard.getZone().getZoneType(), newCard); table.put(ZoneType.Battlefield, newCard.getZone().getZoneType(), newCard);
} }
@@ -2168,4 +2199,14 @@ public class GameAction {
counterTable.triggerCountersPutAll(game); counterTable.triggerCountersPutAll(game);
counterTable.clear(); counterTable.clear();
} }
public void completeDungeon(Player player, Card dungeon) {
player.addCompletedDungeon(dungeon);
ceaseToExist(dungeon, true);
// Run RoomEntered trigger
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(dungeon);
runParams.put(AbilityKey.Player, player);
game.getTriggerHandler().runTrigger(TriggerType.DungeonCompleted, runParams, false);
}
} }

View File

@@ -638,6 +638,9 @@ public final class GameActionUtil {
} }
public static CardCollectionView orderCardsByTheirOwners(Game game, CardCollectionView list, ZoneType dest, SpellAbility sa) { public static CardCollectionView orderCardsByTheirOwners(Game game, CardCollectionView list, ZoneType dest, SpellAbility sa) {
if (list.size() <= 1) {
return list;
}
CardCollection completeList = new CardCollection(); CardCollection completeList = new CardCollection();
for (Player p : game.getPlayers()) { for (Player p : game.getPlayers()) {
CardCollection subList = new CardCollection(); CardCollection subList = new CardCollection();

View File

@@ -205,7 +205,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
public boolean canBeAttached(final Card attach) { public boolean canBeAttached(final Card attach) {
return canBeAttached(attach, false); return canBeAttached(attach, false);
} }
public boolean canBeAttached(final Card attach, boolean checkSBA) { public boolean canBeAttached(final Card attach, boolean checkSBA) {
// master mode // master mode
if (!attach.isAttachment() || attach.isCreature() || equals(attach)) { if (!attach.isAttachment() || attach.isCreature() || equals(attach)) {
@@ -250,7 +249,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
} }
protected boolean canBeEnchantedBy(final Card aura) { protected boolean canBeEnchantedBy(final Card aura) {
SpellAbility sa = aura.getFirstAttachSpell(); SpellAbility sa = aura.getFirstAttachSpell();
TargetRestrictions tgt = null; TargetRestrictions tgt = null;
if (sa != null) { if (sa != null) {
@@ -263,7 +261,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
public boolean hasProtectionFrom(final Card source) { public boolean hasProtectionFrom(final Card source) {
return hasProtectionFrom(source, false); return hasProtectionFrom(source, false);
} }
public abstract boolean hasProtectionFrom(final Card source, final boolean checkSBA); public abstract boolean hasProtectionFrom(final Card source, final boolean checkSBA);
// Counters! // Counters!
@@ -280,7 +277,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
Integer value = counters.get(counterName); Integer value = counters.get(counterName);
return value == null ? 0 : value; return value == null ? 0 : value;
} }
public final int getCounters(final CounterEnumType counterType) { public final int getCounters(final CounterEnumType counterType) {
return getCounters(CounterType.get(counterType)); return getCounters(CounterType.get(counterType));
} }
@@ -292,7 +288,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
counters.put(counterType, num); counters.put(counterType, num);
} }
} }
public void setCounters(final CounterEnumType counterType, final Integer num) { public void setCounters(final CounterEnumType counterType, final Integer num) {
setCounters(CounterType.get(counterType), num); setCounters(CounterType.get(counterType), num);
} }
@@ -300,7 +295,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
abstract public void setCounters(final Map<CounterType, Integer> allCounters); abstract public void setCounters(final Map<CounterType, Integer> allCounters);
abstract public boolean canReceiveCounters(final CounterType type); abstract public boolean canReceiveCounters(final CounterType type);
abstract public int addCounter(final CounterType counterType, final int n, final Player source, final boolean applyMultiplier, final boolean fireEvents, GameEntityCounterTable table); abstract public int addCounter(final CounterType counterType, final int n, final Player source, final SpellAbility cause, final boolean applyMultiplier, final boolean fireEvents, GameEntityCounterTable table);
abstract public void subtractCounter(final CounterType counterName, final int n); abstract public void subtractCounter(final CounterType counterName, final int n);
abstract public void clearCounters(); abstract public void clearCounters();
@@ -308,8 +303,8 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
return canReceiveCounters(CounterType.get(type)); return canReceiveCounters(CounterType.get(type));
} }
public int addCounter(final CounterEnumType counterType, final int n, final Player source, final boolean applyMultiplier, final boolean fireEvents, GameEntityCounterTable table) { public int addCounter(final CounterEnumType counterType, final int n, final Player source, final SpellAbility cause, final boolean applyMultiplier, final boolean fireEvents, GameEntityCounterTable table) {
return addCounter(CounterType.get(counterType), n, source, applyMultiplier, fireEvents, table); return addCounter(CounterType.get(counterType), n, source, cause, applyMultiplier, fireEvents, table);
} }
public void subtractCounter(final CounterEnumType counterName, final int n) { public void subtractCounter(final CounterEnumType counterName, final int n) {
subtractCounter(CounterType.get(counterName), n); subtractCounter(CounterType.get(counterName), n);

View File

@@ -57,8 +57,28 @@ import forge.util.storage.StorageReaderRecursiveFolderWithUserFolder;
public class GameFormat implements Comparable<GameFormat> { public class GameFormat implements Comparable<GameFormat> {
private final String name; private final String name;
public enum FormatType {Sanctioned, Casual, Historic, Digital, Custom} public enum FormatType {
public enum FormatSubType {Block, Standard, Extended, Pioneer, Modern, Legacy, Vintage, Commander, Planechase, Videogame, MTGO, Arena, Custom} SANCTIONED,
CASUAL,
HISTORIC,
DIGITAL,
CUSTOM
}
public enum FormatSubType {
BLOCK,
STANDARD,
EXTENDED,
PIONEER,
MODERN,
LEGACY,
VINTAGE,
COMMANDER,
PLANECHASE,
VIDEOGAME,
MTGO,
ARENA,
CUSTOM
}
// contains allowed sets, when empty allows all sets // contains allowed sets, when empty allows all sets
private FormatType formatType; private FormatType formatType;
@@ -85,11 +105,11 @@ public class GameFormat implements Comparable<GameFormat> {
private final int index; private final int index;
public GameFormat(final String fName, final Iterable<String> sets, final List<String> bannedCards) { public GameFormat(final String fName, final Iterable<String> sets, final List<String> bannedCards) {
this(fName, parseDate(DEFAULTDATE), sets, bannedCards, null, false, null, null, 0, FormatType.Custom, FormatSubType.Custom); this(fName, parseDate(DEFAULTDATE), sets, bannedCards, null, false, null, null, 0, FormatType.CUSTOM, FormatSubType.CUSTOM);
} }
public static final GameFormat NoFormat = new GameFormat("(none)", parseDate(DEFAULTDATE) , null, null, null, false public static final GameFormat NoFormat = new GameFormat("(none)", parseDate(DEFAULTDATE) , null, null, null, false
, null, null, Integer.MAX_VALUE, FormatType.Custom, FormatSubType.Custom); , null, null, Integer.MAX_VALUE, FormatType.CUSTOM, FormatSubType.CUSTOM);
public GameFormat(final String fName, final Date effectiveDate, final Iterable<String> sets, final List<String> bannedCards, public GameFormat(final String fName, final Date effectiveDate, final Iterable<String> sets, final List<String> bannedCards,
final List<String> restrictedCards, Boolean restrictedLegendary, final List<String> additionalCards, final List<String> restrictedCards, Boolean restrictedLegendary, final List<String> additionalCards,
@@ -276,6 +296,7 @@ public class GameFormat implements Comparable<GameFormat> {
if (null == other) { if (null == other) {
return 1; return 1;
} }
if (other.formatType != formatType){ if (other.formatType != formatType){
return formatType.compareTo(other.formatType); return formatType.compareTo(other.formatType);
}else{ }else{
@@ -283,10 +304,11 @@ public class GameFormat implements Comparable<GameFormat> {
return formatSubType.compareTo(other.formatSubType); return formatSubType.compareTo(other.formatSubType);
} }
} }
if (formatType.equals(FormatType.Historic)){ if (formatType.equals(FormatType.HISTORIC)){
if(effectiveDate!=other.effectiveDate) {//for matching dates or default dates default to name sorting int compareDates = this.effectiveDate.compareTo(other.effectiveDate);
return other.effectiveDate.compareTo(effectiveDate); if (compareDates != 0)
} return compareDates;
return (this.index - other.index);
} }
return name.compareTo(other.name); return name.compareTo(other.name);
//return index - other.index; //return index - other.index;
@@ -338,15 +360,15 @@ public class GameFormat implements Comparable<GameFormat> {
String title = section.get("name"); String title = section.get("name");
FormatType formatType; FormatType formatType;
try { try {
formatType = FormatType.valueOf(section.get("type")); formatType = FormatType.valueOf(section.get("type").toUpperCase());
} catch (Exception e) { } catch (Exception e) {
formatType = FormatType.Custom; formatType = FormatType.CUSTOM;
} }
FormatSubType formatsubType; FormatSubType formatsubType;
try { try {
formatsubType = FormatSubType.valueOf(section.get("subtype")); formatsubType = FormatSubType.valueOf(section.get("subtype").toUpperCase());
} catch (Exception e) { } catch (Exception e) {
formatsubType = FormatSubType.Custom; formatsubType = FormatSubType.CUSTOM;
} }
Integer idx = section.getInt("order"); Integer idx = section.getInt("order");
String dateStr = section.get("effective"); String dateStr = section.get("effective");
@@ -432,7 +454,7 @@ public class GameFormat implements Comparable<GameFormat> {
public Iterable<GameFormat> getSanctionedList() { public Iterable<GameFormat> getSanctionedList() {
List<GameFormat> coreList = new ArrayList<>(); List<GameFormat> coreList = new ArrayList<>();
for(GameFormat format: naturallyOrdered){ for(GameFormat format: naturallyOrdered){
if(format.getFormatType().equals(FormatType.Sanctioned)){ if(format.getFormatType().equals(FormatType.SANCTIONED)){
coreList.add(format); coreList.add(format);
} }
} }
@@ -442,8 +464,8 @@ public class GameFormat implements Comparable<GameFormat> {
public Iterable<GameFormat> getFilterList() { public Iterable<GameFormat> getFilterList() {
List<GameFormat> coreList = new ArrayList<>(); List<GameFormat> coreList = new ArrayList<>();
for(GameFormat format: naturallyOrdered){ for(GameFormat format: naturallyOrdered){
if(!format.getFormatType().equals(FormatType.Historic) if(!format.getFormatType().equals(FormatType.HISTORIC)
&&!format.getFormatType().equals(FormatType.Digital)){ &&!format.getFormatType().equals(FormatType.DIGITAL)){
coreList.add(format); coreList.add(format);
} }
} }
@@ -453,7 +475,7 @@ public class GameFormat implements Comparable<GameFormat> {
public Iterable<GameFormat> getHistoricList() { public Iterable<GameFormat> getHistoricList() {
List<GameFormat> coreList = new ArrayList<>(); List<GameFormat> coreList = new ArrayList<>();
for(GameFormat format: naturallyOrdered){ for(GameFormat format: naturallyOrdered){
if(format.getFormatType().equals(FormatType.Historic)){ if(format.getFormatType().equals(FormatType.HISTORIC)){
coreList.add(format); coreList.add(format);
} }
} }
@@ -463,7 +485,7 @@ public class GameFormat implements Comparable<GameFormat> {
public Map<String, List<GameFormat>> getHistoricMap() { public Map<String, List<GameFormat>> getHistoricMap() {
Map<String, List<GameFormat>> coreList = new HashMap<>(); Map<String, List<GameFormat>> coreList = new HashMap<>();
for(GameFormat format: naturallyOrdered){ for(GameFormat format: naturallyOrdered){
if(format.getFormatType().equals(FormatType.Historic)){ if(format.getFormatType().equals(FormatType.HISTORIC)){
String alpha = format.getName().substring(0,1); String alpha = format.getName().substring(0,1);
if(!coreList.containsKey(alpha)){ if(!coreList.containsKey(alpha)){
coreList.put(alpha,new ArrayList<>()); coreList.put(alpha,new ArrayList<>());
@@ -528,15 +550,15 @@ public class GameFormat implements Comparable<GameFormat> {
Set<FormatSubType> coveredTypes = new HashSet<>(); Set<FormatSubType> coveredTypes = new HashSet<>();
CardPool allCards = deck.getAllCardsInASinglePool(); CardPool allCards = deck.getAllCardsInASinglePool();
for(GameFormat gf : reverseDateOrdered) { for(GameFormat gf : reverseDateOrdered) {
if (gf.getFormatType().equals(FormatType.Digital) && !exhaustive){ if (gf.getFormatType().equals(FormatType.DIGITAL) && !exhaustive){
//exclude Digital formats from lists for now //exclude Digital formats from lists for now
continue; continue;
} }
if (gf.getFormatSubType().equals(FormatSubType.Commander)){ if (gf.getFormatSubType().equals(FormatSubType.COMMANDER)){
//exclude Commander format as other deck checks are not performed here //exclude Commander format as other deck checks are not performed here
continue; continue;
} }
if (gf.getFormatType().equals(FormatType.Historic) && coveredTypes.contains(gf.getFormatSubType()) if (gf.getFormatType().equals(FormatType.HISTORIC) && coveredTypes.contains(gf.getFormatSubType())
&& !exhaustive){ && !exhaustive){
//exclude duplicate formats - only keep first of e.g. Standard historical //exclude duplicate formats - only keep first of e.g. Standard historical
continue; continue;
@@ -570,7 +592,7 @@ public class GameFormat implements Comparable<GameFormat> {
return gf1.formatSubType.compareTo(gf2.formatSubType); return gf1.formatSubType.compareTo(gf2.formatSubType);
} }
} }
if (gf1.formatType.equals(FormatType.Historic)){ if (gf1.formatType.equals(FormatType.HISTORIC)){
if(gf1.effectiveDate!=gf2.effectiveDate) {//for matching dates or default dates default to name sorting if(gf1.effectiveDate!=gf2.effectiveDate) {//for matching dates or default dates default to name sorting
return gf1.effectiveDate.compareTo(gf2.effectiveDate); return gf1.effectiveDate.compareTo(gf2.effectiveDate);
} }

View File

@@ -349,7 +349,7 @@ public class Match {
if (!lostOwnership.isEmpty()) { if (!lostOwnership.isEmpty()) {
List<PaperCard> lostPaperOwnership = new ArrayList<>(); List<PaperCard> lostPaperOwnership = new ArrayList<>();
for(Card c : lostOwnership) { for (Card c : lostOwnership) {
lostPaperOwnership.add((PaperCard)c.getPaperCard()); lostPaperOwnership.add((PaperCard)c.getPaperCard());
} }
outcome.addAnteLost(registered, lostPaperOwnership); outcome.addAnteLost(registered, lostPaperOwnership);
@@ -357,7 +357,7 @@ public class Match {
if (!gainedOwnership.isEmpty()) { if (!gainedOwnership.isEmpty()) {
List<PaperCard> gainedPaperOwnership = new ArrayList<>(); List<PaperCard> gainedPaperOwnership = new ArrayList<>();
for(Card c : gainedOwnership) { for (Card c : gainedOwnership) {
gainedPaperOwnership.add((PaperCard)c.getPaperCard()); gainedPaperOwnership.add((PaperCard)c.getPaperCard());
} }
outcome.addAnteWon(registered, gainedPaperOwnership); outcome.addAnteWon(registered, gainedPaperOwnership);

View File

@@ -6,12 +6,12 @@
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
@@ -28,6 +28,7 @@ import forge.card.CardStateName;
import forge.game.CardTraitBase; import forge.game.CardTraitBase;
import forge.game.IHasSVars; import forge.game.IHasSVars;
import forge.game.ability.effects.CharmEffect; import forge.game.ability.effects.CharmEffect;
import forge.game.ability.effects.RollDiceEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardState; import forge.game.card.CardState;
import forge.game.cost.Cost; import forge.game.cost.Cost;
@@ -45,7 +46,7 @@ import io.sentry.event.BreadcrumbBuilder;
* <p> * <p>
* AbilityFactory class. * AbilityFactory class.
* </p> * </p>
* *
* @author Forge * @author Forge
* @version $Id$ * @version $Id$
*/ */
@@ -71,7 +72,7 @@ public final class AbilityFactory {
Spell("SP"), Spell("SP"),
StaticAbility("ST"), StaticAbility("ST"),
SubAbility("DB"); SubAbility("DB");
private final String prefix; private final String prefix;
AbilityRecordType(String prefix) { AbilityRecordType(String prefix) {
this.prefix = prefix; this.prefix = prefix;
@@ -79,7 +80,7 @@ public final class AbilityFactory {
public String getPrefix() { public String getPrefix() {
return prefix; return prefix;
} }
public SpellAbility buildSpellAbility(ApiType api, Card hostCard, Cost abCost, TargetRestrictions abTgt, Map<String, String> mapParams ) { public SpellAbility buildSpellAbility(ApiType api, Card hostCard, Cost abCost, TargetRestrictions abTgt, Map<String, String> mapParams ) {
switch(this) { switch(this) {
case Ability: return new AbilityApiBased(api, hostCard, abCost, abTgt, mapParams); case Ability: return new AbilityApiBased(api, hostCard, abCost, abTgt, mapParams);
@@ -89,11 +90,11 @@ public final class AbilityFactory {
} }
return null; // exception here would be fine! return null; // exception here would be fine!
} }
public ApiType getApiTypeOf(Map<String, String> abParams) { public ApiType getApiTypeOf(Map<String, String> abParams) {
return ApiType.smartValueOf(abParams.get(this.getPrefix())); return ApiType.smartValueOf(abParams.get(this.getPrefix()));
} }
public static AbilityRecordType getRecordType(Map<String, String> abParams) { public static AbilityRecordType getRecordType(Map<String, String> abParams) {
if (abParams.containsKey(AbilityRecordType.Ability.getPrefix())) { if (abParams.containsKey(AbilityRecordType.Ability.getPrefix())) {
return AbilityRecordType.Ability; return AbilityRecordType.Ability;
@@ -108,7 +109,7 @@ public final class AbilityFactory {
} }
} }
} }
public static final SpellAbility getAbility(final String abString, final Card card) { public static final SpellAbility getAbility(final String abString, final Card card) {
return getAbility(abString, card, card.getCurrentState()); return getAbility(abString, card, card.getCurrentState());
} }
@@ -119,7 +120,7 @@ public final class AbilityFactory {
* <p> * <p>
* getAbility. * getAbility.
* </p> * </p>
* *
* @param abString * @param abString
* a {@link java.lang.String} object. * a {@link java.lang.String} object.
* @param state * @param state
@@ -129,7 +130,7 @@ public final class AbilityFactory {
public static final SpellAbility getAbility(final String abString, final CardState state) { public static final SpellAbility getAbility(final String abString, final CardState state) {
return getAbility(abString, state, state); return getAbility(abString, state, state);
} }
private static final SpellAbility getAbility(final String abString, final CardState state, final IHasSVars sVarHolder) { private static final SpellAbility getAbility(final String abString, final CardState state, final IHasSVars sVarHolder) {
Map<String, String> mapParams; Map<String, String> mapParams;
try { try {
@@ -155,7 +156,7 @@ public final class AbilityFactory {
throw new RuntimeException(msg + " of card: " + state.getName(), ex); throw new RuntimeException(msg + " of card: " + state.getName(), ex);
} }
} }
public static final SpellAbility getAbility(final Card hostCard, final String svar) { public static final SpellAbility getAbility(final Card hostCard, final String svar) {
return getAbility(hostCard, svar, hostCard.getCurrentState()); return getAbility(hostCard, svar, hostCard.getCurrentState());
} }
@@ -163,7 +164,7 @@ public final class AbilityFactory {
public static final SpellAbility getAbility(final Card hostCard, final String svar, final IHasSVars sVarHolder) { public static final SpellAbility getAbility(final Card hostCard, final String svar, final IHasSVars sVarHolder) {
return getAbility(hostCard.getCurrentState(), svar, sVarHolder); return getAbility(hostCard.getCurrentState(), svar, sVarHolder);
} }
public static final SpellAbility getAbility(final CardState state, final String svar, final IHasSVars sVarHolder) { public static final SpellAbility getAbility(final CardState state, final String svar, final IHasSVars sVarHolder) {
if (!sVarHolder.hasSVar(svar)) { if (!sVarHolder.hasSVar(svar)) {
String source = state.getCard().getName(); String source = state.getCard().getName();
@@ -285,15 +286,18 @@ public final class AbilityFactory {
@Override @Override
public AbilitySub apply(String input) { public AbilitySub apply(String input) {
return getSubAbility(state, input, sVarHolder); return getSubAbility(state, input, sVarHolder);
} }
})); }));
} }
} }
if (api == ApiType.RollDice) { if (api == ApiType.RollDice) {
for (String param : mapParams.keySet()) { final String key = "ResultSubAbilities";
if (param.startsWith("On") || param.equals("Else")) { if (mapParams.containsKey(key)) {
spellAbility.setAdditionalAbility(param, getSubAbility(state, mapParams.get(param), sVarHolder)); String [] diceAbilities = mapParams.get(key).split(",");
for (String ab : diceAbilities) {
String [] kv = ab.split(":");
spellAbility.setAdditionalAbility(kv[0], getSubAbility(state, kv[1], sVarHolder));
} }
} }
} }
@@ -311,7 +315,6 @@ public final class AbilityFactory {
} }
sb.append(mapParams.get("SpellDescription")); sb.append(mapParams.get("SpellDescription"));
spellAbility.setDescription(sb.toString()); spellAbility.setDescription(sb.toString());
} else if (api == ApiType.Charm) { } else if (api == ApiType.Charm) {
spellAbility.setDescription(CharmEffect.makeFormatedDescription(spellAbility)); spellAbility.setDescription(CharmEffect.makeFormatedDescription(spellAbility));
@@ -319,6 +322,12 @@ public final class AbilityFactory {
spellAbility.setDescription(""); spellAbility.setDescription("");
} }
if (api == ApiType.RollDice) {
spellAbility.setDescription(spellAbility.getDescription() + RollDiceEffect.makeFormatedDescription(spellAbility));
} else if (api == ApiType.Repeat) {
spellAbility.setDescription(spellAbility.getDescription() + spellAbility.getAdditionalAbility("RepeatSubAbility").getDescription());
}
initializeParams(spellAbility); initializeParams(spellAbility);
makeRestrictions(spellAbility); makeRestrictions(spellAbility);
makeConditions(spellAbility); makeConditions(spellAbility);
@@ -399,7 +408,7 @@ public final class AbilityFactory {
* <p> * <p>
* initializeParams. * initializeParams.
* </p> * </p>
* *
* @param sa * @param sa
* a {@link forge.game.spellability.SpellAbility} object. * a {@link forge.game.spellability.SpellAbility} object.
*/ */
@@ -414,7 +423,7 @@ public final class AbilityFactory {
* <p> * <p>
* makeRestrictions. * makeRestrictions.
* </p> * </p>
* *
* @param sa * @param sa
* a {@link forge.game.spellability.SpellAbility} object. * a {@link forge.game.spellability.SpellAbility} object.
*/ */
@@ -428,7 +437,7 @@ public final class AbilityFactory {
* <p> * <p>
* makeConditions. * makeConditions.
* </p> * </p>
* *
* @param sa * @param sa
* a {@link forge.game.spellability.SpellAbility} object. * a {@link forge.game.spellability.SpellAbility} object.
*/ */
@@ -444,7 +453,7 @@ public final class AbilityFactory {
* getSubAbility. * getSubAbility.
* </p> * </p>
* @param sSub * @param sSub
* *
* @return a {@link forge.game.spellability.AbilitySub} object. * @return a {@link forge.game.spellability.AbilitySub} object.
*/ */
private static final AbilitySub getSubAbility(CardState state, String sSub, final IHasSVars sVarHolder) { private static final AbilitySub getSubAbility(CardState state, String sSub, final IHasSVars sVarHolder) {
@@ -466,19 +475,19 @@ public final class AbilityFactory {
List<ZoneType> origin = ZoneType.listValueOf(params.get("Origin")); List<ZoneType> origin = ZoneType.listValueOf(params.get("Origin"));
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
// Don't set the zone if it targets a player // Don't set the zone if it targets a player
if ((tgt != null) && !tgt.canTgtPlayer()) { if ((tgt != null) && !tgt.canTgtPlayer()) {
sa.getTargetRestrictions().setZone(origin); sa.getTargetRestrictions().setZone(origin);
} }
} }
} }
public static final SpellAbility buildFusedAbility(final Card card) { public static final SpellAbility buildFusedAbility(final Card card) {
if(!card.isSplitCard()) if(!card.isSplitCard())
throw new IllegalStateException("Fuse ability may be built only on split cards"); throw new IllegalStateException("Fuse ability may be built only on split cards");
CardState leftState = card.getState(CardStateName.LeftSplit); CardState leftState = card.getState(CardStateName.LeftSplit);
SpellAbility leftAbility = leftState.getFirstAbility(); SpellAbility leftAbility = leftState.getFirstAbility();
Map<String, String> leftMap = Maps.newHashMap(leftAbility.getMapParams()); Map<String, String> leftMap = Maps.newHashMap(leftAbility.getMapParams());

View File

@@ -73,6 +73,7 @@ public enum AbilityKey {
IsCombatDamage("IsCombatDamage"), IsCombatDamage("IsCombatDamage"),
IndividualCostPaymentInstance("IndividualCostPaymentInstance"), IndividualCostPaymentInstance("IndividualCostPaymentInstance"),
IsMadness("IsMadness"), IsMadness("IsMadness"),
LastStateBattlefield("LastStateBattlefield"),
LifeAmount("LifeAmount"), //TODO confirm that this and LifeGained can be merged LifeAmount("LifeAmount"), //TODO confirm that this and LifeGained can be merged
LifeGained("LifeGained"), LifeGained("LifeGained"),
Mana("Mana"), Mana("Mana"),
@@ -106,6 +107,7 @@ public enum AbilityKey {
ReplacementResult("ReplacementResult"), ReplacementResult("ReplacementResult"),
ReplacementResultMap("ReplacementResultMap"), ReplacementResultMap("ReplacementResultMap"),
Result("Result"), Result("Result"),
RoomName("RoomName"),
Scheme("Scheme"), Scheme("Scheme"),
Source("Source"), Source("Source"),
Sources("Sources"), Sources("Sources"),

View File

@@ -173,15 +173,13 @@ public class AbilityUtils {
} }
} }
else if (defined.equals("Targeted") && sa instanceof SpellAbility) { else if (defined.equals("Targeted") && sa instanceof SpellAbility) {
final SpellAbility saTargeting = ((SpellAbility)sa).getSATargetingCard(); for (TargetChoices tc : ((SpellAbility)sa).getAllTargetChoices()) {
if (saTargeting != null) { Iterables.addAll(cards, tc.getTargetCards());
Iterables.addAll(cards, saTargeting.getTargets().getTargetCards());
} }
} }
else if (defined.equals("TargetedSource") && sa instanceof SpellAbility) { else if (defined.equals("TargetedSource") && sa instanceof SpellAbility) {
final SpellAbility saTargeting = ((SpellAbility)sa).getSATargetingSA(); for (TargetChoices tc : ((SpellAbility)sa).getAllTargetChoices()) {
if (saTargeting != null) { for (SpellAbility s : tc.getTargetSpells()) {
for (SpellAbility s : saTargeting.getTargets().getTargetSpells()) {
cards.add(s.getHostCard()); cards.add(s.getHostCard());
} }
} }
@@ -742,7 +740,18 @@ public class AbilityUtils {
final SpellAbility root = sa.getRootAbility(); final SpellAbility root = sa.getRootAbility();
final String[] l = calcX[1].split("/"); final String[] l = calcX[1].split("/");
final String m = CardFactoryUtil.extractOperators(calcX[1]); final String m = CardFactoryUtil.extractOperators(calcX[1]);
final Integer count = (Integer) root.getTriggeringObject(AbilityKey.fromString(l[0])); Integer count = null;
if (calcX[0].endsWith("Max")) {
@SuppressWarnings("unchecked")
Iterable<Integer> numbers = (Iterable<Integer>) root.getTriggeringObject(AbilityKey.fromString(l[0]));
for (Integer n : numbers) {
if (count == null || n > count) {
count = n;
}
}
} else {
count = (Integer) root.getTriggeringObject(AbilityKey.fromString(l[0]));
}
val = doXMath(ObjectUtils.firstNonNull(count, 0), m, card, ability); val = doXMath(ObjectUtils.firstNonNull(count, 0), m, card, ability);
} }
@@ -807,9 +816,9 @@ public class AbilityUtils {
final SpellAbility root = sa.getRootAbility(); final SpellAbility root = sa.getRootAbility();
list = new CardCollection((Card) root.getReplacingObject(AbilityKey.fromString(calcX[0].substring(8)))); list = new CardCollection((Card) root.getReplacingObject(AbilityKey.fromString(calcX[0].substring(8))));
} }
// there could be null inside!
list = Iterables.filter(list, Card.class);
if (list != null) { if (list != null) {
// there could be null inside!
list = Iterables.filter(list, Card.class);
val = handlePaid(list, calcX[1], card, ability); val = handlePaid(list, calcX[1], card, ability);
} }
} }
@@ -1008,9 +1017,8 @@ public class AbilityUtils {
players.addAll(getDefinedPlayers(card, "TargetedController", sa)); players.addAll(getDefinedPlayers(card, "TargetedController", sa));
} }
else if ((defined.equals("Targeted") || defined.equals("TargetedPlayer")) && sa instanceof SpellAbility) { else if ((defined.equals("Targeted") || defined.equals("TargetedPlayer")) && sa instanceof SpellAbility) {
final SpellAbility saTargeting = ((SpellAbility)sa).getSATargetingPlayer(); for (TargetChoices tc : ((SpellAbility)sa).getAllTargetChoices()) {
if (saTargeting != null) { players.addAll(tc.getTargetPlayers());
players.addAll(saTargeting.getTargets().getTargetPlayers());
} }
} }
else if (defined.equals("ParentTarget") && sa instanceof SpellAbility) { else if (defined.equals("ParentTarget") && sa instanceof SpellAbility) {
@@ -1289,8 +1297,7 @@ public class AbilityUtils {
SpellAbility s = null; SpellAbility s = null;
// TODO - this probably needs to be fleshed out a bit, but the basics // TODO - this probably needs to be fleshed out a bit, but the basics work
// work
if (defined.equals("Self") && sa instanceof SpellAbility) { if (defined.equals("Self") && sa instanceof SpellAbility) {
s = (SpellAbility)sa; s = (SpellAbility)sa;
} }
@@ -1298,9 +1305,8 @@ public class AbilityUtils {
s = ((SpellAbility)sa).getRootAbility(); s = ((SpellAbility)sa).getRootAbility();
} }
else if (defined.equals("Targeted") && sa instanceof SpellAbility) { else if (defined.equals("Targeted") && sa instanceof SpellAbility) {
final SpellAbility saTargeting = ((SpellAbility)sa).getSATargetingSA(); for (TargetChoices tc : ((SpellAbility)sa).getAllTargetChoices()) {
if (saTargeting != null) { for (SpellAbility targetSpell : tc.getTargetSpells()) {
for (SpellAbility targetSpell : saTargeting.getTargets().getTargetSpells()) {
SpellAbilityStackInstance stackInstance = game.getStack().getInstanceFromSpellAbility(targetSpell); SpellAbilityStackInstance stackInstance = game.getStack().getInstanceFromSpellAbility(targetSpell);
if (stackInstance != null) { if (stackInstance != null) {
SpellAbility instanceSA = stackInstance.getSpellAbility(true); SpellAbility instanceSA = stackInstance.getSpellAbility(true);
@@ -1412,7 +1418,6 @@ public class AbilityUtils {
} }
return; return;
} }
AbilityUtils.resolveApiAbility(sa, game); AbilityUtils.resolveApiAbility(sa, game);
} }
@@ -2575,7 +2580,6 @@ public class AbilityUtils {
// Count$ThisTurnCast <Valid> // Count$ThisTurnCast <Valid>
// Count$LastTurnCast <Valid> // Count$LastTurnCast <Valid>
if (sq[0].startsWith("ThisTurnCast") || sq[0].startsWith("LastTurnCast")) { if (sq[0].startsWith("ThisTurnCast") || sq[0].startsWith("LastTurnCast")) {
final String[] workingCopy = l[0].split("_"); final String[] workingCopy = l[0].split("_");
final String validFilter = workingCopy[1]; final String validFilter = workingCopy[1];
@@ -2736,7 +2740,6 @@ public class AbilityUtils {
return doXMath(powers.size(), expr, c, ctb); return doXMath(powers.size(), expr, c, ctb);
} }
if (sq[0].startsWith("MostProminentCreatureType")) { if (sq[0].startsWith("MostProminentCreatureType")) {
String restriction = l[0].split(" ")[1]; String restriction = l[0].split(" ")[1];
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb); CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb);
@@ -3366,6 +3369,29 @@ public class AbilityUtils {
return doXMath(opps == null ? 0 : opps.size(), m, source, ctb); return doXMath(opps == null ? 0 : opps.size(), m, source, ctb);
} }
if (value.equals("DungeonsCompleted")) {
return doXMath(player.getCompletedDungeons().size(), m, source, ctb);
}
if (value.equals("DifferentlyNamedDungeonsCompleted")) {
int amount = 0;
List<Card> dungeons = player.getCompletedDungeons();
for (int i = 0; i < dungeons.size(); ++i) {
Card d1 = dungeons.get(i);
boolean hasSameName = false;
for (int j = i - 1; j >= 0; --j) {
Card d2 = dungeons.get(j);
if (d1.getName().equals(d2.getName())) {
hasSameName = true;
break;
}
}
if (!hasSameName) {
++amount;
}
}
return doXMath(amount, m, source, ctb);
}
return doXMath(0, m, source, ctb); return doXMath(0, m, source, ctb);
} }
@@ -3395,7 +3421,6 @@ public class AbilityUtils {
return doXMath(n, CardFactoryUtil.extractOperators(s), source, ctb); return doXMath(n, CardFactoryUtil.extractOperators(s), source, ctb);
} }
/** /**
* <p> * <p>
* handlePaid. * handlePaid.

View File

@@ -7,7 +7,7 @@ import java.util.Map;
import forge.game.ability.effects.*; import forge.game.ability.effects.*;
import forge.util.ReflectionUtil; import forge.util.ReflectionUtil;
/** /**
* TODO: Write javadoc for this type. * TODO: Write javadoc for this type.
* *
*/ */
@@ -172,6 +172,7 @@ public enum ApiType {
UnattachAll (UnattachAllEffect.class), UnattachAll (UnattachAllEffect.class),
Untap (UntapEffect.class), Untap (UntapEffect.class),
UntapAll (UntapAllEffect.class), UntapAll (UntapAllEffect.class),
Venture (VentureEffect.class),
Vote (VoteEffect.class), Vote (VoteEffect.class),
WinsGame (GameWinEffect.class), WinsGame (GameWinEffect.class),
@@ -187,7 +188,7 @@ public enum ApiType {
private final Class<? extends SpellAbilityEffect> clsEffect; private final Class<? extends SpellAbilityEffect> clsEffect;
private static final Map<String, ApiType> allValues = new HashMap<>(); private static final Map<String, ApiType> allValues = new HashMap<>();
static { static {
for(ApiType t : ApiType.values()) { for(ApiType t : ApiType.values()) {
allValues.put(t.name().toLowerCase(), t); allValues.put(t.name().toLowerCase(), t);
@@ -197,7 +198,7 @@ public enum ApiType {
ApiType(Class<? extends SpellAbilityEffect> clsEf) { this(clsEf, true); } ApiType(Class<? extends SpellAbilityEffect> clsEf) { this(clsEf, true); }
ApiType(Class<? extends SpellAbilityEffect> clsEf, final boolean isStateLess) { ApiType(Class<? extends SpellAbilityEffect> clsEf, final boolean isStateLess) {
clsEffect = clsEf; clsEffect = clsEf;
instanceEffect = isStateLess ? ReflectionUtil.makeDefaultInstanceOf(clsEf) : null; instanceEffect = isStateLess ? ReflectionUtil.makeDefaultInstanceOf(clsEf) : null;
} }
public static ApiType smartValueOf(String value) { public static ApiType smartValueOf(String value) {

View File

@@ -329,7 +329,6 @@ public abstract class SpellAbilityEffect {
} }
protected static void addSelfTrigger(final SpellAbility sa, String location, final Card card) { protected static void addSelfTrigger(final SpellAbility sa, String location, final Card card) {
String trigStr = "Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield " + String trigStr = "Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield " +
"| TriggerDescription$ At the beginning of the end step, " + location.toLowerCase() + " CARDNAME."; "| TriggerDescription$ At the beginning of the end step, " + location.toLowerCase() + " CARDNAME.";
@@ -511,7 +510,7 @@ public abstract class SpellAbilityEffect {
} }
} }
// build an Effect with that infomation // build an Effect with that information
String name = host.getName() + "'s Effect"; String name = host.getName() + "'s Effect";
final Card eff = createEffect(sa, controller, name, host.getImageKey()); final Card eff = createEffect(sa, controller, name, host.getImageKey());
@@ -617,8 +616,29 @@ public abstract class SpellAbilityEffect {
combat.addBlocker(attacker, c); combat.addBlocker(attacker, c);
combat.orderAttackersForDamageAssignment(c); combat.orderAttackersForDamageAssignment(c);
{
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Attacker, attacker);
runParams.put(AbilityKey.Blocker, c);
game.getTriggerHandler().runTrigger(TriggerType.AttackerBlockedByCreature, runParams, false);
}
{
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Attackers, attacker);
game.getTriggerHandler().runTrigger(TriggerType.AttackerBlockedOnce, runParams, false);
}
// Run triggers for new blocker and add it to damage assignment order // Run triggers for new blocker and add it to damage assignment order
if (!wasBlocked) { if (!wasBlocked) {
final CardCollection blockers = combat.getBlockers(attacker);
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Attacker, attacker);
runParams.put(AbilityKey.Blockers, blockers);
runParams.put(AbilityKey.NumBlockers, blockers.size());
runParams.put(AbilityKey.Defender, combat.getDefenderByAttacker(attacker));
runParams.put(AbilityKey.DefendingPlayer, combat.getDefenderPlayerByAttacker(attacker));
game.getTriggerHandler().runTrigger(TriggerType.AttackerBlocked, runParams, false);
combat.setBlocked(attacker, true); combat.setBlocked(attacker, true);
combat.addBlockerToDamageAssignmentOrder(attacker, c); combat.addBlockerToDamageAssignmentOrder(attacker, c);
} }

View File

@@ -93,7 +93,7 @@ public class AmassEffect extends TokenEffectBase {
GameEntityCounterTable table = new GameEntityCounterTable(); GameEntityCounterTable table = new GameEntityCounterTable();
for(final Card tgtCard : tgtCards) { for(final Card tgtCard : tgtCards) {
tgtCard.addCounter(CounterEnumType.P1P1, amount, activator, true, table); tgtCard.addCounter(CounterEnumType.P1P1, amount, activator, sa, true, table);
game.updateLastStateForCard(tgtCard); game.updateLastStateForCard(tgtCard);
if (remember) { if (remember) {

View File

@@ -59,6 +59,9 @@ public class AnimateAllEffect extends AnimateEffectBase {
if (types.hasSubtype("ChosenType")) { if (types.hasSubtype("ChosenType")) {
types.clear(); types.clear();
types.add(host.getChosenType()); types.add(host.getChosenType());
} else if (types.hasSubtype("ChosenType2")) {
types.clear();
types.add(host.getChosenType2());
} }
final List<String> keywords = new ArrayList<>(); final List<String> keywords = new ArrayList<>();
@@ -125,7 +128,7 @@ public class AnimateAllEffect extends AnimateEffectBase {
} }
CardCollectionView list; CardCollectionView list;
if (!sa.usesTargeting() && !sa.hasParam("Defined")) { if (!sa.usesTargeting() && !sa.hasParam("Defined")) {
list = game.getCardsIn(ZoneType.Battlefield); list = game.getCardsIn(ZoneType.Battlefield);
} else { } else {

View File

@@ -71,6 +71,9 @@ public class AnimateEffect extends AnimateEffectBase {
if (types.hasSubtype("ChosenType")) { if (types.hasSubtype("ChosenType")) {
types.clear(); types.clear();
types.add(source.getChosenType()); types.add(source.getChosenType());
} else if (types.hasSubtype("ChosenType2")) {
types.clear();
types.add(source.getChosenType2());
} }
final List<String> keywords = Lists.newArrayList(); final List<String> keywords = Lists.newArrayList();
@@ -227,7 +230,7 @@ public class AnimateEffect extends AnimateEffectBase {
} }
// allow SVar substitution for keywords // allow SVar substitution for keywords
for (int i = 0; i < keywords.size(); i++) { for (int i = 0; i < keywords.size(); i++) {
final String k = keywords.get(i); final String k = keywords.get(i);
if (sa.hasSVar(k)) { if (sa.hasSVar(k)) {
keywords.add("\"" + k + "\""); keywords.add("\"" + k + "\"");
keywords.remove(k); keywords.remove(k);

View File

@@ -120,7 +120,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
if (!addType.isEmpty() || !removeType.isEmpty() || removeCreatureTypes) { if (!addType.isEmpty() || !removeType.isEmpty() || removeCreatureTypes) {
c.addChangedCardTypes(addType, removeType, removeSuperTypes, removeCardTypes, removeSubTypes, c.addChangedCardTypes(addType, removeType, removeSuperTypes, removeCardTypes, removeSubTypes,
removeLandTypes, removeCreatureTypes, removeArtifactTypes, removeEnchantmentTypes, timestamp); removeLandTypes, removeCreatureTypes, removeArtifactTypes, removeEnchantmentTypes, timestamp, true, false);
} }
c.addChangedCardKeywords(keywords, removeKeywords, removeAll, removeLandTypes, timestamp); c.addChangedCardKeywords(keywords, removeKeywords, removeAll, removeLandTypes, timestamp);
@@ -133,7 +133,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
c.addHiddenExtrinsicKeyword(k); c.addHiddenExtrinsicKeyword(k);
} }
c.addColor(colors, !sa.hasParam("OverwriteColors"), timestamp); c.addColor(colors, !sa.hasParam("OverwriteColors"), timestamp, false);
if (sa.hasParam("LeaveBattlefield")) { if (sa.hasParam("LeaveBattlefield")) {
addLeaveBattlefieldReplacement(c, sa, sa.getParam("LeaveBattlefield")); addLeaveBattlefieldReplacement(c, sa, sa.getParam("LeaveBattlefield"));

View File

@@ -90,7 +90,7 @@ public class AttachEffect extends SpellAbilityEffect {
// If Cast Targets will be checked on the Stack // If Cast Targets will be checked on the Stack
for (final Card attachment : attachments) { for (final Card attachment : attachments) {
String message = Localizer.getInstance().getMessage("lblDoYouWantAttachSourceToTarget", CardTranslation.getTranslatedName(attachment.getName()), attachToName); String message = Localizer.getInstance().getMessage("lblDoYouWantAttachSourceToTarget", CardTranslation.getTranslatedName(attachment.getName()), attachToName);
if ( sa.hasParam("Optional") && !p.getController().confirmAction(sa, null, message) ) if (sa.hasParam("Optional") && !p.getController().confirmAction(sa, null, message))
continue; continue;
handleAttachment(attachment, attachTo, sa); handleAttachment(attachment, attachTo, sa);
} }
@@ -118,7 +118,6 @@ public class AttachEffect extends SpellAbilityEffect {
* the o * the o
*/ */
public static void handleAttachment(final Card card, final Object o, final SpellAbility sa) { public static void handleAttachment(final Card card, final Object o, final SpellAbility sa) {
if (card == null) { return; } if (card == null) { return; }
if (o instanceof Card) { if (o instanceof Card) {

View File

@@ -7,6 +7,7 @@ import java.util.Map;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
@@ -46,7 +47,8 @@ public class BalanceEffect extends SpellAbilityEffect {
validCards.add(CardLists.getValidCards(players.get(i).getCardsIn(zone), valid, activator, source, sa)); validCards.add(CardLists.getValidCards(players.get(i).getCardsIn(zone), valid, activator, source, sa));
min = Math.min(min, validCards.get(i).size()); min = Math.min(min, validCards.get(i).size());
} }
Map<AbilityKey, Object> params = AbilityKey.newMap();
CardZoneTable table = new CardZoneTable(); CardZoneTable table = new CardZoneTable();
for (int i = 0; i < players.size(); i++) { for (int i = 0; i < players.size(); i++) {
Player p = players.get(i); Player p = players.get(i);
@@ -59,7 +61,7 @@ public class BalanceEffect extends SpellAbilityEffect {
} else { // Battlefield } else { // Battlefield
for (Card card : p.getController().choosePermanentsToSacrifice(sa, numToBalance, numToBalance, validCards.get(i), valid)) { for (Card card : p.getController().choosePermanentsToSacrifice(sa, numToBalance, numToBalance, validCards.get(i), valid)) {
if ( null == card ) continue; if ( null == card ) continue;
game.getAction().sacrifice(card, sa, table); game.getAction().sacrifice(card, sa, table, params);
} }
} }
} }

View File

@@ -291,7 +291,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
* @return a {@link java.lang.String} object. * @return a {@link java.lang.String} object.
*/ */
private static String changeKnownOriginStackDescription(final SpellAbility sa) { private static String changeKnownOriginStackDescription(final SpellAbility sa) {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
@@ -709,7 +708,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (sa.hasParam("WithCountersType")) { if (sa.hasParam("WithCountersType")) {
CounterType cType = CounterType.getType(sa.getParam("WithCountersType")); CounterType cType = CounterType.getType(sa.getParam("WithCountersType"));
int cAmount = AbilityUtils.calculateAmount(hostCard, sa.getParamOrDefault("WithCountersAmount", "1"), sa); int cAmount = AbilityUtils.calculateAmount(hostCard, sa.getParamOrDefault("WithCountersAmount", "1"), sa);
movedCard.addCounter(cType, cAmount, player, true, counterTable); movedCard.addCounter(cType, cAmount, player, sa, true, counterTable);
} }
if (sa.hasParam("ExileFaceDown") || sa.hasParam("FaceDown")) { if (sa.hasParam("ExileFaceDown") || sa.hasParam("FaceDown")) {
@@ -1047,7 +1046,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
// only multi-select if player can select more than one // only multi-select if player can select more than one
if (changeNum > 1 && allowMultiSelect(decider, sa)) { if (changeNum > 1 && allowMultiSelect(decider, sa)) {
List<Card> selectedCards; List<Card> selectedCards;
if (! sa.hasParam("SelectPrompt")) { if (!sa.hasParam("SelectPrompt")) {
// new default messaging for multi select // new default messaging for multi select
if (fetchList.size() > changeNum) { if (fetchList.size() > changeNum) {
//Select up to %changeNum cards from %players %origin //Select up to %changeNum cards from %players %origin

View File

@@ -200,7 +200,6 @@ public class CharmEffect extends SpellAbilityEffect {
} }
private static void chainAbilities(SpellAbility sa, List<AbilitySub> chosen) { private static void chainAbilities(SpellAbility sa, List<AbilitySub> chosen) {
if (chosen == null) { if (chosen == null) {
return; return;
} }
@@ -243,5 +242,4 @@ public class CharmEffect extends SpellAbilityEffect {
} }
} }

View File

@@ -65,7 +65,7 @@ public class ChooseTypeEffect extends SpellAbilityEffect {
for (final Player p : tgtPlayers) { for (final Player p : tgtPlayers) {
if ((tgt == null) || p.canBeTargetedBy(sa)) { if ((tgt == null) || p.canBeTargetedBy(sa)) {
String choice = p.getController().chooseSomeType(type, sa, validTypes, invalidTypes); String choice = p.getController().chooseSomeType(type, sa, validTypes, invalidTypes);
if (!sa.hasParam("Secondary")) { if (!sa.hasParam("ChooseType2")) {
card.setChosenType(choice); card.setChosenType(choice);
} else { } else {
card.setChosenType2(choice); card.setChosenType2(choice);

View File

@@ -143,7 +143,7 @@ public class CloneEffect extends SpellAbilityEffect {
tgtCard.updateStateForView(); tgtCard.updateStateForView();
// when clone is itself, cleanup from old abilities // when clone is itself, cleanup from old abilities
if (host.equals(tgtCard)) { if (host.equals(tgtCard) && !sa.hasParam("ImprintRememberedNoCleanup")) {
tgtCard.clearImprintedCards(); tgtCard.clearImprintedCards();
tgtCard.clearRemembered(); tgtCard.clearRemembered();
} }

View File

@@ -4,13 +4,14 @@ import java.util.List;
import forge.GameCommand; import forge.GameCommand;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.util.Lang; import forge.util.Lang;
import forge.util.TextUtil; import forge.util.TextUtil;
/** /**
* TODO: Write javadoc for this type. * TODO: Write javadoc for this type.
* *
*/ */
@@ -18,16 +19,18 @@ public class ControlPlayerEffect extends SpellAbilityEffect {
@Override @Override
protected String getStackDescription(SpellAbility sa) { protected String getStackDescription(SpellAbility sa) {
List<Player> tgtPlayers = getTargetPlayers(sa); List<Player> tgtPlayers = getTargetPlayers(sa);
return TextUtil.concatWithSpace(sa.getActivatingPlayer().toString(),"controls", Lang.joinHomogenous(tgtPlayers),"during their next turn"); return TextUtil.concatWithSpace(sa.getActivatingPlayer().toString(),"controls", Lang.joinHomogenous(tgtPlayers),"during their next turn");
} }
@SuppressWarnings("serial") @SuppressWarnings("serial")
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final Player activator = sa.getActivatingPlayer(); final Player activator = sa.getActivatingPlayer();
final Game game = activator.getGame(); final Game game = activator.getGame();
final Player controller = sa.hasParam("Controller") ? AbilityUtils.getDefinedPlayers(
sa.getHostCard(), sa.getParam("Controller"), sa).get(0) : activator;
List<Player> tgtPlayers = getTargetPlayers(sa); List<Player> tgtPlayers = getTargetPlayers(sa);
@@ -37,13 +40,13 @@ public class ControlPlayerEffect extends SpellAbilityEffect {
@Override @Override
public void run() { public void run() {
// CR 800.4b // CR 800.4b
if (activator.hasLost()) { if (controller.hasLost()) {
return; return;
} }
long ts = game.getNextTimestamp(); long ts = game.getNextTimestamp();
pTarget.addController(ts, activator); pTarget.addController(ts, controller);
// on following cleanup release control // on following cleanup release control
game.getEndOfTurn().addUntil(new GameCommand() { game.getEndOfTurn().addUntil(new GameCommand() {
@Override @Override

View File

@@ -99,9 +99,9 @@ public class CopyPermanentEffect extends TokenEffectBase {
if (sa.hasParam("RandomCopied")) { if (sa.hasParam("RandomCopied")) {
List<PaperCard> copysource = Lists.newArrayList(cards); List<PaperCard> copysource = Lists.newArrayList(cards);
List<Card> choice = Lists.newArrayList(); List<Card> choice = Lists.newArrayList();
final String num = sa.hasParam("RandomNum") ? sa.getParam("RandomNum") : "1"; final String num = sa.getParamOrDefault("RandomNum","1");
int ncopied = AbilityUtils.calculateAmount(host, num, sa); int ncopied = AbilityUtils.calculateAmount(host, num, sa);
while(ncopied > 0 && !copysource.isEmpty()) { while (ncopied > 0 && !copysource.isEmpty()) {
final PaperCard cp = Aggregates.random(copysource); final PaperCard cp = Aggregates.random(copysource);
Card possibleCard = Card.fromPaperCard(cp, activator); // Need to temporarily set the Owner so the Game is set Card possibleCard = Card.fromPaperCard(cp, activator); // Need to temporarily set the Owner so the Game is set

View File

@@ -73,10 +73,8 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
controllers = AbilityUtils.getDefinedPlayers(card, sa.getParam("Controller"), sa); controllers = AbilityUtils.getDefinedPlayers(card, sa.getParam("Controller"), sa);
} }
final List<SpellAbility> tgtSpells = getTargetSpells(sa); final List<SpellAbility> tgtSpells = getTargetSpells(sa);
if (tgtSpells.size() == 0 || amount == 0) { if (tgtSpells.size() == 0 || amount == 0) {
return; return;
} }

View File

@@ -12,6 +12,7 @@ import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardFactoryUtil; import forge.game.card.CardFactoryUtil;
import forge.game.card.CardZoneTable;
import forge.game.replacement.ReplacementResult; import forge.game.replacement.ReplacementResult;
import forge.game.replacement.ReplacementType; import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -113,6 +114,8 @@ public class CounterEffect extends SpellAbilityEffect {
} }
} }
Map<AbilityKey, Object> params = AbilityKey.newMap();
CardZoneTable table = new CardZoneTable();
for (final SpellAbility tgtSA : sas) { for (final SpellAbility tgtSA : sas) {
final Card tgtSACard = tgtSA.getHostCard(); final Card tgtSACard = tgtSA.getHostCard();
// should remember even that spell cannot be countered, e.g. Dovescape // should remember even that spell cannot be countered, e.g. Dovescape
@@ -137,7 +140,7 @@ public class CounterEffect extends SpellAbilityEffect {
// Destroy Permanent may be able to be turned into a SubAbility // Destroy Permanent may be able to be turned into a SubAbility
if (tgtSA.isAbility() && sa.hasParam("DestroyPermanent")) { if (tgtSA.isAbility() && sa.hasParam("DestroyPermanent")) {
game.getAction().destroy(tgtSACard, sa, true, null); game.getAction().destroy(tgtSACard, sa, true, table, params);
} }
if (sa.hasParam("RememberCountered")) { if (sa.hasParam("RememberCountered")) {
@@ -152,6 +155,7 @@ public class CounterEffect extends SpellAbilityEffect {
} }
} }
} }
table.triggerChangesZoneAll(game, sa);
} // end counterResolve } // end counterResolve
/** /**
@@ -189,7 +193,7 @@ public class CounterEffect extends SpellAbilityEffect {
params.put(AbilityKey.StackSi, si); params.put(AbilityKey.StackSi, si);
String destination = srcSA.hasParam("Destination") ? srcSA.getParam("Destination") : tgtSA.isAftermath() ? "Exile" : "Graveyard"; String destination = srcSA.hasParam("Destination") ? srcSA.getParam("Destination") : tgtSA.isAftermath() ? "Exile" : "Graveyard";
if (srcSA.hasParam("DestinationChoice")) {//Hinder if (srcSA.hasParam("DestinationChoice")) { //Hinder
List<String> pos = Arrays.asList(srcSA.getParam("DestinationChoice").split(",")); List<String> pos = Arrays.asList(srcSA.getParam("DestinationChoice").split(","));
destination = srcSA.getActivatingPlayer().getController().chooseSomeType(Localizer.getInstance().getMessage("lblRemoveDestination"), tgtSA, pos, null); destination = srcSA.getActivatingPlayer().getController().chooseSomeType(Localizer.getInstance().getMessage("lblRemoveDestination"), tgtSA, pos, null);
} }
@@ -207,7 +211,6 @@ public class CounterEffect extends SpellAbilityEffect {
} else if (destination.equals("Battlefield")) { } else if (destination.equals("Battlefield")) {
if (tgtSA instanceof SpellPermanent) { if (tgtSA instanceof SpellPermanent) {
Card c = tgtSA.getHostCard(); Card c = tgtSA.getHostCard();
System.out.println(c + " is SpellPermanent");
c.setController(srcSA.getActivatingPlayer(), 0); c.setController(srcSA.getActivatingPlayer(), 0);
game.getAction().moveToPlay(c, srcSA.getActivatingPlayer(), srcSA, params); game.getAction().moveToPlay(c, srcSA.getActivatingPlayer(), srcSA, params);
} else { } else {

View File

@@ -147,8 +147,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
Map<CounterType, Integer> countersToAdd = Maps.newHashMap(); Map<CounterType, Integer> countersToAdd = Maps.newHashMap();
for (Card src : srcCards) { for (Card src : srcCards) {
// rule 121.5: If the first and second objects are the same object, nothing // rule 121.5: If the first and second objects are the same object, nothing happens
// happens
if (src.equals(dest)) { if (src.equals(dest)) {
continue; continue;
} }
@@ -163,7 +162,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
} }
} }
for (Map.Entry<CounterType, Integer> e : countersToAdd.entrySet()) { for (Map.Entry<CounterType, Integer> e : countersToAdd.entrySet()) {
dest.addCounter(e.getKey(), e.getValue(), player, true, table); dest.addCounter(e.getKey(), e.getValue(), player, sa, true, table);
} }
game.updateLastStateForCard(dest); game.updateLastStateForCard(dest);
@@ -199,8 +198,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
boolean updateSource = false; boolean updateSource = false;
for (final Card dest : tgtCards) { for (final Card dest : tgtCards) {
// rule 121.5: If the first and second objects are the same object, nothing // rule 121.5: If the first and second objects are the same object, nothing happens
// happens
if (source.equals(dest)) { if (source.equals(dest)) {
continue; continue;
} }
@@ -225,7 +223,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
if (cnum > 0) { if (cnum > 0) {
source.subtractCounter(cType, cnum); source.subtractCounter(cType, cnum);
cur.addCounter(cType, cnum, player, true, table); cur.addCounter(cType, cnum, player, sa, true, table);
game.updateLastStateForCard(cur); game.updateLastStateForCard(cur);
updateSource = true; updateSource = true;
} }
@@ -262,8 +260,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
for (final Card dest : tgtCards) { for (final Card dest : tgtCards) {
if (null != dest) { if (null != dest) {
// rule 121.5: If the first and second objects are the same object, nothing // rule 121.5: If the first and second objects are the same object, nothing happens
// happens
if (source.equals(dest)) { if (source.equals(dest)) {
continue; continue;
} }
@@ -311,7 +308,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
} }
for (Map.Entry<CounterType, Integer> e : countersToAdd.entrySet()) { for (Map.Entry<CounterType, Integer> e : countersToAdd.entrySet()) {
cur.addCounter(e.getKey(), e.getValue(), player, true, table); cur.addCounter(e.getKey(), e.getValue(), player, sa, true, table);
} }
game.updateLastStateForCard(cur); game.updateLastStateForCard(cur);
} }
@@ -329,8 +326,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
final PlayerController pc = player.getController(); final PlayerController pc = player.getController();
final Game game = host.getGame(); final Game game = host.getGame();
// rule 121.5: If the first and second objects are the same object, nothing // rule 121.5: If the first and second objects are the same object, nothing happens
// happens
if (src.equals(dest)) { if (src.equals(dest)) {
return; return;
} }

View File

@@ -54,10 +54,10 @@ public class CountersMultiplyEffect extends SpellAbilityEffect {
continue; continue;
} }
if (counterType != null) { if (counterType != null) {
gameCard.addCounter(counterType, gameCard.getCounters(counterType) * n, player, true, table); gameCard.addCounter(counterType, gameCard.getCounters(counterType) * n, player, sa, true, table);
} else { } else {
for (Map.Entry<CounterType, Integer> e : gameCard.getCounters().entrySet()) { for (Map.Entry<CounterType, Integer> e : gameCard.getCounters().entrySet()) {
gameCard.addCounter(e.getKey(), e.getValue() * n, player, true, table); gameCard.addCounter(e.getKey(), e.getValue() * n, player, sa, true, table);
} }
} }
game.updateLastStateForCard(gameCard); game.updateLastStateForCard(gameCard);

View File

@@ -30,7 +30,7 @@ public class CountersNoteEffect extends SpellAbilityEffect {
if (mode.equals(MODE_STORE)) { if (mode.equals(MODE_STORE)) {
noteCounters(c, source); noteCounters(c, source);
} else if (mode.equals(MODE_LOAD)) { } else if (mode.equals(MODE_LOAD)) {
loadCounters(c, source, p, table); loadCounters(c, source, p, sa, table);
} }
} }
table.triggerCountersPutAll(game); table.triggerCountersPutAll(game);
@@ -44,13 +44,13 @@ public class CountersNoteEffect extends SpellAbilityEffect {
} }
} }
private void loadCounters(Card notee, Card source, final Player p, GameEntityCounterTable table) { private void loadCounters(Card notee, Card source, final Player p, final SpellAbility sa, GameEntityCounterTable table) {
for(Entry<String, String> svar : source.getSVars().entrySet()) { for(Entry<String, String> svar : source.getSVars().entrySet()) {
String key = svar.getKey(); String key = svar.getKey();
if (key.startsWith(NOTE_COUNTERS)) { if (key.startsWith(NOTE_COUNTERS)) {
notee.addCounter( notee.addCounter(
CounterType.getType(key.substring(NOTE_COUNTERS.length())), CounterType.getType(key.substring(NOTE_COUNTERS.length())),
Integer.parseInt(svar.getValue()), p, false, table); Integer.parseInt(svar.getValue()), p, sa, false, table);
} }
// TODO Probably should "remove" the svars that were temporarily used // TODO Probably should "remove" the svars that were temporarily used
} }

View File

@@ -48,7 +48,7 @@ public class CountersProliferateEffect extends SpellAbilityEffect {
GameEntityCounterTable table = new GameEntityCounterTable(); GameEntityCounterTable table = new GameEntityCounterTable();
for (final GameEntity ge : result) { for (final GameEntity ge : result) {
for (final CounterType ct : ge.getCounters().keySet()) { for (final CounterType ct : ge.getCounters().keySet()) {
ge.addCounter(ct, 1, p, true, true, table); ge.addCounter(ct, 1, p, sa, true, true, table);
} }
if (ge instanceof Card) { if (ge instanceof Card) {
Card c = (Card) ge; Card c = (Card) ge;

View File

@@ -71,7 +71,7 @@ public class CountersPutAllEffect extends SpellAbilityEffect {
if (etbcounter) { if (etbcounter) {
tgtCard.addEtbCounter(CounterType.getType(type), counterAmount, placer); tgtCard.addEtbCounter(CounterType.getType(type), counterAmount, placer);
} else { } else {
tgtCard.addCounter(CounterType.getType(type), counterAmount, placer, inBattlefield, table); tgtCard.addCounter(CounterType.getType(type), counterAmount, placer, sa, inBattlefield, table);
} }
game.updateLastStateForCard(tgtCard); game.updateLastStateForCard(tgtCard);
} }

View File

@@ -226,10 +226,10 @@ public class CountersPutEffect extends SpellAbilityEffect {
if (eachExistingCounter) { if (eachExistingCounter) {
for (CounterType ct : choices) { for (CounterType ct : choices) {
if (obj instanceof Player) { if (obj instanceof Player) {
((Player) obj).addCounter(ct, counterAmount, placer, true, table); ((Player) obj).addCounter(ct, counterAmount, placer, sa, true, table);
} }
if (obj instanceof Card) { if (obj instanceof Card) {
gameCard.addCounter(ct, counterAmount, placer, true, table); gameCard.addCounter(ct, counterAmount, placer, sa, true, table);
} }
} }
continue; continue;
@@ -253,7 +253,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
for (Card c : AbilityUtils.getDefinedCards(card, sa.getParam("EachFromSource"), sa)) { for (Card c : AbilityUtils.getDefinedCards(card, sa.getParam("EachFromSource"), sa)) {
for (Entry<CounterType, Integer> cti : c.getCounters().entrySet()) { for (Entry<CounterType, Integer> cti : c.getCounters().entrySet()) {
if (gameCard != null && gameCard.canReceiveCounters(cti.getKey())) { if (gameCard != null && gameCard.canReceiveCounters(cti.getKey())) {
gameCard.addCounter(cti.getKey(), cti.getValue(), placer, true, table); gameCard.addCounter(cti.getKey(), cti.getValue(), placer, sa, true, table);
} }
} }
} }
@@ -338,7 +338,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
if (etbcounter) { if (etbcounter) {
gameCard.addEtbCounter(counterType, counterAmount, placer); gameCard.addEtbCounter(counterType, counterAmount, placer);
} else { } else {
int addedAmount = gameCard.addCounter(counterType, counterAmount, placer, true, table); int addedAmount = gameCard.addCounter(counterType, counterAmount, placer, sa, true, table);
if (addedAmount > 0) { if (addedAmount > 0) {
counterAdded = true; counterAdded = true;
} }
@@ -372,7 +372,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
if (etbcounter) { if (etbcounter) {
gameCard.addEtbCounter(counterType, counterAmount, placer); gameCard.addEtbCounter(counterType, counterAmount, placer);
} else { } else {
if (gameCard.addCounter(counterType, counterAmount, placer, false, table) > 0) { if (gameCard.addCounter(counterType, counterAmount, placer, sa, false, table) > 0) {
counterAdded = true; counterAdded = true;
} }
} }
@@ -388,7 +388,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
} else if (obj instanceof Player) { } else if (obj instanceof Player) {
// Add Counters to players! // Add Counters to players!
Player pl = (Player) obj; Player pl = (Player) obj;
pl.addCounter(counterType, counterAmount, placer, true, table); pl.addCounter(counterType, counterAmount, placer, sa, true, table);
} }
} }
} }

View File

@@ -111,7 +111,7 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
boolean apply = zone == null || zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Stack); boolean apply = zone == null || zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Stack);
tgtCard.addCounter(chosenType, counterAmount, pl, apply, table); tgtCard.addCounter(chosenType, counterAmount, pl, sa, apply, table);
} else { } else {
tgtCard.subtractCounter(chosenType, counterAmount); tgtCard.subtractCounter(chosenType, counterAmount);
} }

View File

@@ -60,9 +60,9 @@ public class DamageDealEffect extends DamageBaseEffect {
// if use targeting we show all targets and corresponding damage // if use targeting we show all targets and corresponding damage
if (spellAbility.usesTargeting()) { if (spellAbility.usesTargeting()) {
if (spellAbility.hasParam("DivideEvenly")) { if (spellAbility.hasParam("DivideEvenly")) {
stringBuilder.append("divided evenly (rounded down) to\n"); stringBuilder.append("divided evenly (rounded down) to \n");
} else if (spellAbility.isDividedAsYouChoose()) { } else if (spellAbility.isDividedAsYouChoose()) {
stringBuilder.append("divided to\n"); stringBuilder.append("divided to \n");
} else } else
stringBuilder.append("to "); stringBuilder.append("to ");

View File

@@ -31,7 +31,6 @@ public class DelayedTriggerEffect extends SpellAbilityEffect {
} }
return ""; return "";
} }
@Override @Override

View File

@@ -6,6 +6,7 @@ import com.google.common.collect.Maps;
import forge.game.Game; import forge.game.Game;
import forge.game.GameActionUtil; import forge.game.GameActionUtil;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -44,7 +45,6 @@ public class DestroyAllEffect extends SpellAbilityEffect {
*/ */
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final boolean noRegen = sa.hasParam("NoRegen"); final boolean noRegen = sa.hasParam("NoRegen");
final Card card = sa.getHostCard(); final Card card = sa.getHostCard();
final Game game = sa.getActivatingPlayer().getGame(); final Game game = sa.getActivatingPlayer().getGame();
@@ -90,10 +90,12 @@ public class DestroyAllEffect extends SpellAbilityEffect {
} }
CardZoneTable table = new CardZoneTable(); CardZoneTable table = new CardZoneTable();
Map<AbilityKey, Object> params = AbilityKey.newMap();
params.put(AbilityKey.LastStateBattlefield, game.copyLastStateBattlefield());
Map<Integer, Card> cachedMap = Maps.newHashMap(); Map<Integer, Card> cachedMap = Maps.newHashMap();
for (Card c : list) { for (Card c : list) {
if (game.getAction().destroy(c, sa, !noRegen, table) && remDestroyed) { if (game.getAction().destroy(c, sa, !noRegen, table, params) && remDestroyed) {
card.addRemembered(CardUtil.getLKICopy(c, cachedMap)); card.addRemembered(CardUtil.getLKICopy(c, cachedMap));
} }
} }

View File

@@ -8,9 +8,10 @@ import com.google.common.collect.Maps;
import forge.game.Game; import forge.game.Game;
import forge.game.GameActionUtil; import forge.game.GameActionUtil;
import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollectionView;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -72,13 +73,16 @@ public class DestroyEffect extends SpellAbilityEffect {
card.clearRemembered(); card.clearRemembered();
} }
CardCollection tgtCards = getTargetCards(sa); CardCollectionView tgtCards = getTargetCards(sa);
CardCollection untargetedCards = CardUtil.getRadiance(sa); CardCollectionView untargetedCards = CardUtil.getRadiance(sa);
if (tgtCards.size() > 1) { if (tgtCards.size() > 1) {
tgtCards = (CardCollection) GameActionUtil.orderCardsByTheirOwners(game, tgtCards, ZoneType.Graveyard, sa); tgtCards = GameActionUtil.orderCardsByTheirOwners(game, tgtCards, ZoneType.Graveyard, sa);
} }
Map<AbilityKey, Object> params = AbilityKey.newMap();
params.put(AbilityKey.LastStateBattlefield, game.copyLastStateBattlefield());
CardZoneTable table = new CardZoneTable(); CardZoneTable table = new CardZoneTable();
Map<Integer, Card> cachedMap = Maps.newHashMap(); Map<Integer, Card> cachedMap = Maps.newHashMap();
for (final Card tgtC : tgtCards) { for (final Card tgtC : tgtCards) {
@@ -90,24 +94,24 @@ public class DestroyEffect extends SpellAbilityEffect {
if (gameCard == null || !tgtC.equalsWithTimestamp(gameCard)) { if (gameCard == null || !tgtC.equalsWithTimestamp(gameCard)) {
continue; continue;
} }
internalDestroy(gameCard, sa, table, cachedMap); internalDestroy(gameCard, sa, table, cachedMap, params);
} }
} }
if (untargetedCards.size() > 1) { if (untargetedCards.size() > 1) {
untargetedCards = (CardCollection) GameActionUtil.orderCardsByTheirOwners(game, untargetedCards, ZoneType.Graveyard, sa); untargetedCards = GameActionUtil.orderCardsByTheirOwners(game, untargetedCards, ZoneType.Graveyard, sa);
} }
for (final Card unTgtC : untargetedCards) { for (final Card unTgtC : untargetedCards) {
if (unTgtC.isInPlay()) { if (unTgtC.isInPlay()) {
internalDestroy(unTgtC, sa, table, cachedMap); internalDestroy(unTgtC, sa, table, cachedMap, params);
} }
} }
table.triggerChangesZoneAll(game, sa); table.triggerChangesZoneAll(game, sa);
} }
protected void internalDestroy(Card gameCard, SpellAbility sa, CardZoneTable table, Map<Integer, Card> cachedMap) { protected void internalDestroy(Card gameCard, SpellAbility sa, CardZoneTable table, Map<Integer, Card> cachedMap, Map<AbilityKey, Object> params) {
final Card card = sa.getHostCard(); final Card card = sa.getHostCard();
final Game game = card.getGame(); final Game game = card.getGame();
@@ -122,9 +126,9 @@ public class DestroyEffect extends SpellAbilityEffect {
card.addRemembered(gameCard.getAttachedCards()); card.addRemembered(gameCard.getAttachedCards());
} }
if (sac) { if (sac) {
destroyed = game.getAction().sacrifice(gameCard, sa, table) != null; destroyed = game.getAction().sacrifice(gameCard, sa, table, params) != null;
} else { } else {
destroyed = game.getAction().destroy(gameCard, sa, !noRegen, table); destroyed = game.getAction().destroy(gameCard, sa, !noRegen, table, params);
} }
if (destroyed && remDestroyed) { if (destroyed && remDestroyed) {
card.addRemembered(gameCard); card.addRemembered(gameCard);

View File

@@ -336,7 +336,7 @@ public class DigEffect extends SpellAbilityEffect {
} else if (destZone1.equals(ZoneType.Exile)) { } else if (destZone1.equals(ZoneType.Exile)) {
if (sa.hasParam("ExileWithCounter")) { if (sa.hasParam("ExileWithCounter")) {
c.addCounter(CounterType.getType(sa.getParam("ExileWithCounter")), c.addCounter(CounterType.getType(sa.getParam("ExileWithCounter")),
1, player, true, counterTable); 1, player, sa, true, counterTable);
} }
c.setExiledWith(effectHost); c.setExiledWith(effectHost);
c.setExiledBy(effectHost.getController()); c.setExiledBy(effectHost.getController());
@@ -408,7 +408,7 @@ public class DigEffect extends SpellAbilityEffect {
if (destZone2 == ZoneType.Exile) { if (destZone2 == ZoneType.Exile) {
if (sa.hasParam("ExileWithCounter")) { if (sa.hasParam("ExileWithCounter")) {
c.addCounter(CounterType.getType(sa.getParam("ExileWithCounter")), c.addCounter(CounterType.getType(sa.getParam("ExileWithCounter")),
1, player, true, counterTable); 1, player, sa, true, counterTable);
} }
c.setExiledWith(effectHost); c.setExiledWith(effectHost);
c.setExiledBy(effectHost.getController()); c.setExiledBy(effectHost.getController());

View File

@@ -173,26 +173,25 @@ public class DigUntilEffect extends SpellAbilityEffect {
if (optionalFound && !p.getController().confirmAction(sa, null, if (optionalFound && !p.getController().confirmAction(sa, null,
Localizer.getInstance().getMessage("lblDoYouWantPutCardToZone", foundDest.getTranslatedName()))) { Localizer.getInstance().getMessage("lblDoYouWantPutCardToZone", foundDest.getTranslatedName()))) {
continue; continue;
}
Card m = null;
if (sa.hasParam("GainControl") && foundDest.equals(ZoneType.Battlefield)) {
c.setController(sa.getActivatingPlayer(), game.getNextTimestamp());
m = game.getAction().moveTo(c.getController().getZone(foundDest), c, sa);
if (sa.hasParam("Tapped")) {
c.setTapped(true);
}
if (addToCombat(c, c.getController(), sa, "Attacking", "Blocking")) {
combatChanged = true;
}
} else if (sa.hasParam("NoMoveFound") && foundDest.equals(ZoneType.Library)) {
//Don't do anything
} else { } else {
Card m = null; m = game.getAction().moveTo(foundDest, c, foundLibPos, sa);
if (sa.hasParam("GainControl") && foundDest.equals(ZoneType.Battlefield)) { }
c.setController(sa.getActivatingPlayer(), game.getNextTimestamp()); revealed.remove(c);
m = game.getAction().moveTo(c.getController().getZone(foundDest), c, sa); if (m != null && !origin.equals(m.getZone().getZoneType())) {
if (sa.hasParam("Tapped")) { table.put(origin, m.getZone().getZoneType(), m);
c.setTapped(true);
}
if (addToCombat(c, c.getController(), sa, "Attacking", "Blocking")) {
combatChanged = true;
}
} else if (sa.hasParam("NoMoveFound") && foundDest.equals(ZoneType.Library)) {
//Don't do anything
} else {
m = game.getAction().moveTo(foundDest, c, foundLibPos, sa);
}
revealed.remove(c);
if (m != null && !origin.equals(m.getZone().getZoneType())) {
table.put(origin, m.getZone().getZoneType(), m);
}
} }
} }
} }

View File

@@ -18,6 +18,8 @@ public class ETBReplacementEffect extends SpellAbilityEffect {
Map<AbilityKey, Object> params = AbilityKey.newMap(); Map<AbilityKey, Object> params = AbilityKey.newMap();
params.put(AbilityKey.CardLKI, sa.getReplacingObject(AbilityKey.CardLKI)); params.put(AbilityKey.CardLKI, sa.getReplacingObject(AbilityKey.CardLKI));
params.put(AbilityKey.ReplacementEffect, sa.getReplacementEffect()); params.put(AbilityKey.ReplacementEffect, sa.getReplacementEffect());
sa.getActivatingPlayer().getGame().getAction().moveToPlay(card, card.getController(), sa, params); final SpellAbility root = sa.getRootAbility();
SpellAbility cause = (SpellAbility) root.getReplacingObject(AbilityKey.Cause);
sa.getActivatingPlayer().getGame().getAction().moveToPlay(card, card.getController(), cause, params);
} }
} }

View File

@@ -247,9 +247,12 @@ public class EffectEffect extends SpellAbilityEffect {
} }
// Set Chosen Type // Set Chosen Type
if (!hostCard.getChosenType().isEmpty()) { if (hostCard.hasChosenType()) {
eff.setChosenType(hostCard.getChosenType()); eff.setChosenType(hostCard.getChosenType());
} }
if (hostCard.hasChosenType2()) {
eff.setChosenType2(hostCard.getChosenType2());
}
// Set Chosen name // Set Chosen name
if (!hostCard.getNamedCard().isEmpty()) { if (!hostCard.getNamedCard().isEmpty()) {

View File

@@ -83,7 +83,7 @@ public class ExploreEffect extends SpellAbilityEffect {
// if the card is not more in the game anymore // if the card is not more in the game anymore
// this might still return true but its no problem // this might still return true but its no problem
if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.equalsWithTimestamp(c)) { if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.equalsWithTimestamp(c)) {
c.addCounter(CounterEnumType.P1P1, 1, pl, true, table); c.addCounter(CounterEnumType.P1P1, 1, pl, sa, true, table);
} }
} }

View File

@@ -65,19 +65,18 @@ public class FightEffect extends DamageBaseEffect {
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeFight", CardTranslation.getTranslatedName(fighters.get(0).getName()), CardTranslation.getTranslatedName(fighters.get(1).getName())))) { if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeFight", CardTranslation.getTranslatedName(fighters.get(0).getName()), CardTranslation.getTranslatedName(fighters.get(1).getName())))) {
return; return;
} else {
dealDamage(sa, fighters.get(0), fighters.get(1));
for (Card c : fighters) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Fighter, c);
game.getTriggerHandler().runTrigger(TriggerType.Fight, runParams, false);
}
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Fighters, fighters);
game.getTriggerHandler().runTrigger(TriggerType.FightOnce, runParams, false);
} }
dealDamage(sa, fighters.get(0), fighters.get(1));
for (Card c : fighters) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Fighter, c);
game.getTriggerHandler().runTrigger(TriggerType.Fight, runParams, false);
}
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Fighters, fighters);
} }
private static List<Card> getFighters(SpellAbility sa) { private static List<Card> getFighters(SpellAbility sa) {

View File

@@ -58,8 +58,8 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect {
Card lki = CardUtil.getLKICopy(gameCard); Card lki = CardUtil.getLKICopy(gameCard);
lki.clearControllers(); lki.clearControllers();
lki.setOwner(sa.getActivatingPlayer()); lki.setOwner(sa.getActivatingPlayer());
// if this trigger is part of ETBReplacement it shouldn't run with LKI from incomplete zone change (Wall of Stolen Identity) // if this trigger is part of ETBReplacement it shouldn't run with LKI from incomplete zone change (Mimic Vat + Wall of Stolen Identity)
final Card trigHost = sa.getRootAbility().getReplacementEffect() != null && sa.getRootAbility().getReplacementEffect().getMode().equals(ReplacementType.Moved) ? gameCard : lki; final Card trigHost = sa.getRootAbility().getReplacementEffect() != null && sa.getRootAbility().getReplacementEffect().getMode().equals(ReplacementType.Moved) && gameCard.getZone() == null ? gameCard : lki;
final Trigger immediateTrig = TriggerHandler.parseTrigger(mapParams, trigHost, sa.isIntrinsic(), null); final Trigger immediateTrig = TriggerHandler.parseTrigger(mapParams, trigHost, sa.isIntrinsic(), null);
immediateTrig.setSpawningAbility(sa.copy(lki, sa.getActivatingPlayer(), true)); immediateTrig.setSpawningAbility(sa.copy(lki, sa.getActivatingPlayer(), true));

Some files were not shown because too many files have changed in this diff Show More