mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 04:08:01 +00:00
Merge branch 'master' into '1111-dismantle-rework'
# Conflicts: # forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java
This commit is contained in:
@@ -50,7 +50,6 @@ import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.Expressions;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.collect.FCollection;
|
||||
import forge.util.collect.FCollectionView;
|
||||
|
||||
@@ -333,25 +332,6 @@ public class AiAttackController {
|
||||
return attackers;
|
||||
}
|
||||
|
||||
// no need to block if an effect is in play which untaps all creatures (pseudo-Vigilance akin to
|
||||
// Awakening or Prophet of Kruphix)
|
||||
for (Card card : ai.getGame().getCardsIn(ZoneType.Battlefield)) {
|
||||
boolean untapsEachTurn = card.hasSVar("UntapsEachTurn");
|
||||
boolean untapsEachOtherTurn = card.hasSVar("UntapsEachOtherPlayerTurn");
|
||||
|
||||
if (untapsEachTurn || untapsEachOtherTurn) {
|
||||
String affected = untapsEachTurn ? card.getSVar("UntapsEachTurn")
|
||||
: card.getSVar("UntapsEachOtherPlayerTurn");
|
||||
|
||||
for (String aff : TextUtil.split(affected, ',')) {
|
||||
if (aff.equals("Creature")
|
||||
&& (untapsEachTurn || (untapsEachOtherTurn && ai.equals(card.getController())))) {
|
||||
return attackers;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Card> opponentsAttackers = new ArrayList<>(oppList);
|
||||
opponentsAttackers = CardLists.filter(opponentsAttackers, new Predicate<Card>() {
|
||||
@Override
|
||||
@@ -370,7 +350,9 @@ public class AiAttackController {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (c.hasKeyword(Keyword.VIGILANCE)) {
|
||||
// no need to block if an effect is in play which untaps all creatures
|
||||
// (pseudo-Vigilance akin to Awakening or or Prophet of Kruphix)
|
||||
if (c.hasKeyword(Keyword.VIGILANCE) || ComputerUtilCard.willUntap(ai, c)) {
|
||||
vigilantes.add(c);
|
||||
notNeededAsBlockers.remove(c); // they will be re-added later
|
||||
if (canBlockAnAttacker(c, opponentsAttackers, false)) {
|
||||
@@ -392,7 +374,7 @@ public class AiAttackController {
|
||||
}
|
||||
}
|
||||
int blockersStillNeeded = blockersNeeded - fixedBlockers;
|
||||
blockersStillNeeded = Math.min(blockersNeeded, list.size());
|
||||
blockersStillNeeded = Math.min(blockersStillNeeded, list.size());
|
||||
for (int i = 0; i < blockersStillNeeded; i++) {
|
||||
notNeededAsBlockers.remove(list.get(i));
|
||||
}
|
||||
@@ -850,7 +832,7 @@ public class AiAttackController {
|
||||
return aiAggression;
|
||||
}
|
||||
|
||||
if (simAI && ai.isCardInPlay("Reconnaissance")) {
|
||||
if (simAI && ComputerUtilCard.isNonDisabledCardInPlay(ai, "Reconnaissance")) {
|
||||
for (Card attacker : attackersLeft) {
|
||||
if (canAttackWrapper(attacker, defender)) {
|
||||
// simulation will decide if attacker stays in combat based on blocks
|
||||
|
||||
@@ -927,30 +927,8 @@ public class AiController {
|
||||
return AiPlayDecision.WillPlay;
|
||||
}
|
||||
|
||||
public boolean isNonDisabledCardInPlay(final String cardName) {
|
||||
for (Card card : player.getCardsIn(ZoneType.Battlefield)) {
|
||||
if (card.getName().equals(cardName)) {
|
||||
// TODO - Better logic to determine if a permanent is disabled by local effects
|
||||
// currently assuming any permanent enchanted by another player
|
||||
// is disabled and a second copy is necessary
|
||||
// will need actual logic that determines if the enchantment is able
|
||||
// to disable the permanent or it's still functional and a duplicate is unneeded.
|
||||
boolean disabledByEnemy = false;
|
||||
for (Card card2 : card.getEnchantedBy()) {
|
||||
if (card2.getOwner() != player) {
|
||||
disabledByEnemy = true;
|
||||
}
|
||||
}
|
||||
if (!disabledByEnemy) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private AiPlayDecision canPlaySpellBasic(final Card card, final SpellAbility sa) {
|
||||
if ("True".equals(card.getSVar("NonStackingEffect")) && isNonDisabledCardInPlay(card.getName())) {
|
||||
if ("True".equals(card.getSVar("NonStackingEffect")) && ComputerUtilCard.isNonDisabledCardInPlay(player, card.getName())) {
|
||||
return AiPlayDecision.NeedsToPlayCriteriaNotMet;
|
||||
}
|
||||
|
||||
@@ -1170,12 +1148,11 @@ public class AiController {
|
||||
public CardCollection getCardsToDiscard(final int numDiscard, final String[] uTypes, final SpellAbility sa) {
|
||||
return getCardsToDiscard(numDiscard, uTypes, sa, CardCollection.EMPTY);
|
||||
}
|
||||
|
||||
public CardCollection getCardsToDiscard(final int numDiscard, final String[] uTypes, final SpellAbility sa, final CardCollectionView exclude) {
|
||||
boolean noFiltering = (sa != null) && "DiscardCMCX".equals(sa.getParam("AILogic")); // list AI logic for which filtering is taken care of elsewhere
|
||||
boolean noFiltering = sa != null && "DiscardCMCX".equals(sa.getParam("AILogic")); // list AI logic for which filtering is taken care of elsewhere
|
||||
CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand));
|
||||
hand.removeAll(exclude);
|
||||
if ((uTypes != null) && (sa != null) && !noFiltering) {
|
||||
if (uTypes != null && sa != null && !noFiltering) {
|
||||
hand = CardLists.getValidCards(hand, uTypes, sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||
}
|
||||
return getCardsToDiscard(numDiscard, numDiscard, hand, sa);
|
||||
@@ -1214,7 +1191,7 @@ public class AiController {
|
||||
if ("DiscardUncastableAndExcess".equals(sa.getParam("AILogic"))) {
|
||||
CardCollection discards = new CardCollection();
|
||||
final CardCollectionView inHand = player.getCardsIn(ZoneType.Hand);
|
||||
final int numLandsOTB = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS).size();
|
||||
final int numLandsOTB = CardLists.count(player.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS);
|
||||
int numOppInHand = 0;
|
||||
for (Player p : player.getGame().getPlayers()) {
|
||||
if (p.getCardsIn(ZoneType.Hand).size() > numOppInHand) {
|
||||
@@ -1274,7 +1251,7 @@ public class AiController {
|
||||
if (validCards.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
final int numLandsInPlay = CardLists.count(player.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS);
|
||||
final int numLandsInPlay = CardLists.count(player.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA);
|
||||
final CardCollection landsInHand = CardLists.filter(validCards, CardPredicates.Presets.LANDS);
|
||||
final int numLandsInHand = landsInHand.size();
|
||||
|
||||
@@ -2361,7 +2338,7 @@ public class AiController {
|
||||
return Iterables.getFirst(doubleLife, null);
|
||||
}
|
||||
} else if (mode.equals(ReplacementType.DamageDone)) {
|
||||
List<ReplacementEffect> prevention = filterList(list, CardTraitPredicates.hasParam("Prevention"));
|
||||
List<ReplacementEffect> prevention = filterList(list, CardTraitPredicates.hasParam("Prevent"));
|
||||
|
||||
// TODO when Protection is done as ReplacementEffect do them
|
||||
// before normal prevention
|
||||
|
||||
@@ -487,11 +487,11 @@ public class ComputerUtil {
|
||||
// Discard lands
|
||||
final CardCollection landsInHand = CardLists.getType(typeList, "Land");
|
||||
if (!landsInHand.isEmpty()) {
|
||||
final CardCollection landsInPlay = CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "Land");
|
||||
final int numLandsInPlay = CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA);
|
||||
final CardCollection nonLandsInHand = CardLists.getNotType(ai.getCardsIn(ZoneType.Hand), "Land");
|
||||
final int highestCMC = Math.max(6, Aggregates.max(nonLandsInHand, CardPredicates.Accessors.fnGetCmc));
|
||||
if (landsInPlay.size() >= highestCMC
|
||||
|| (landsInPlay.size() + landsInHand.size() > 6 && landsInHand.size() > 1)) {
|
||||
if (numLandsInPlay >= highestCMC
|
||||
|| (numLandsInPlay + landsInHand.size() > 6 && landsInHand.size() > 1)) {
|
||||
// Don't need more land.
|
||||
return ComputerUtilCard.getWorstLand(landsInHand);
|
||||
}
|
||||
@@ -1190,7 +1190,7 @@ public class ComputerUtil {
|
||||
}
|
||||
|
||||
final Game game = ai.getGame();
|
||||
final CardCollection landsInPlay = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS);
|
||||
final CardCollection landsInPlay = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA);
|
||||
final CardCollection landsInHand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS);
|
||||
final CardCollection nonLandsInHand = CardLists.getNotType(ai.getCardsIn(ZoneType.Hand), "Land");
|
||||
final int highestCMC = Math.max(6, Aggregates.max(nonLandsInHand, CardPredicates.Accessors.fnGetCmc));
|
||||
|
||||
@@ -63,6 +63,7 @@ import forge.item.PaperCard;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.Expressions;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
public class ComputerUtilCard {
|
||||
public static Card getMostExpensivePermanentAI(final CardCollectionView list, final SpellAbility spell, final boolean targeted) {
|
||||
@@ -1816,6 +1817,9 @@ public class ComputerUtilCard {
|
||||
}
|
||||
|
||||
public static boolean hasActiveUndyingOrPersist(final Card c) {
|
||||
if (c.isToken()) {
|
||||
return false;
|
||||
}
|
||||
if (c.hasKeyword(Keyword.UNDYING) && c.getCounters(CounterEnumType.P1P1) == 0) {
|
||||
return true;
|
||||
}
|
||||
@@ -1982,6 +1986,50 @@ public class ComputerUtilCard {
|
||||
return totalCost;
|
||||
}
|
||||
|
||||
public static boolean willUntap(Player ai, Card tapped) {
|
||||
// TODO use AiLogic on trigger in case card loses all abilities
|
||||
// if it's from a static need to also check canUntap
|
||||
for (Card card : ai.getGame().getCardsIn(ZoneType.Battlefield)) {
|
||||
boolean untapsEachTurn = card.hasSVar("UntapsEachTurn");
|
||||
boolean untapsEachOtherTurn = card.hasSVar("UntapsEachOtherPlayerTurn");
|
||||
|
||||
if (untapsEachTurn || untapsEachOtherTurn) {
|
||||
String affected = untapsEachTurn ? card.getSVar("UntapsEachTurn")
|
||||
: card.getSVar("UntapsEachOtherPlayerTurn");
|
||||
|
||||
for (String aff : TextUtil.split(affected, ',')) {
|
||||
if (tapped.isValid(aff, ai, tapped, null)
|
||||
&& (untapsEachTurn || (untapsEachOtherTurn && ai.equals(card.getController())))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isNonDisabledCardInPlay(final Player ai, final String cardName) {
|
||||
for (Card card : ai.getCardsIn(ZoneType.Battlefield)) {
|
||||
if (card.getName().equals(cardName)) {
|
||||
// TODO - Better logic to determine if a permanent is disabled by local effects
|
||||
// currently assuming any permanent enchanted by another player
|
||||
// is disabled and a second copy is necessary
|
||||
// will need actual logic that determines if the enchantment is able
|
||||
// to disable the permanent or it's still functional and a duplicate is unneeded.
|
||||
boolean disabledByEnemy = false;
|
||||
for (Card card2 : card.getEnchantedBy()) {
|
||||
if (card2.getOwner() != ai) {
|
||||
disabledByEnemy = true;
|
||||
}
|
||||
}
|
||||
if (!disabledByEnemy) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine if the AI has an AI:RemoveDeck:All or an AI:RemoveDeck:Random hint specified.
|
||||
// Includes a NPE guard on getRules() which might otherwise be tripped on some cards (e.g. tokens).
|
||||
public static boolean isCardRemAIDeck(final Card card) {
|
||||
|
||||
@@ -11,13 +11,6 @@ import forge.game.keyword.Keyword;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class CreatureEvaluator implements Function<Card, Integer> {
|
||||
protected int getEffectivePower(final Card c) {
|
||||
return c.getNetCombatDamage();
|
||||
}
|
||||
protected int getEffectiveToughness(final Card c) {
|
||||
return c.getNetToughness();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer apply(Card c) {
|
||||
return evaluateCreature(c);
|
||||
@@ -31,8 +24,8 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
||||
if (!c.isToken()) {
|
||||
value += addValue(20, "non-token"); // tokens should be worth less than actual cards
|
||||
}
|
||||
int power = getEffectivePower(c);
|
||||
final int toughness = getEffectiveToughness(c);
|
||||
int power = c.getNetCombatDamage();
|
||||
final int toughness = c.getNetToughness();
|
||||
|
||||
// TODO replace with ReplacementEffect checks
|
||||
if (c.hasKeyword("Prevent all combat damage that would be dealt by CARDNAME.")
|
||||
@@ -45,6 +38,11 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
||||
if (considerPT) {
|
||||
value += addValue(power * 15, "power");
|
||||
value += addValue(toughness * 10, "toughness: " + toughness);
|
||||
|
||||
// because backside is always stronger the potential makes it better than a single faced card
|
||||
if (c.hasKeyword(Keyword.DAYBOUND)) {
|
||||
value += addValue(power * 10, "transforming");
|
||||
}
|
||||
}
|
||||
if (considerCMC) {
|
||||
value += addValue(c.getCMC() * 5, "cmc");
|
||||
@@ -72,8 +70,8 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
||||
if (c.hasKeyword(Keyword.MENACE)) {
|
||||
value += addValue(power * 4, "menace");
|
||||
}
|
||||
if (c.hasStartOfKeyword("CantBeBlockedBy")) {
|
||||
value += addValue(power * 3, "block-restrict");
|
||||
if (c.hasKeyword(Keyword.SKULK)) {
|
||||
value += addValue(power * 3, "skulk");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,19 +104,18 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
||||
value += addValue(c.getKeywordMagnitude(Keyword.AFFLICT) * 5, "afflict");
|
||||
}
|
||||
|
||||
value += addValue(c.getKeywordMagnitude(Keyword.BUSHIDO) * 16, "bushido");
|
||||
value += addValue(c.getAmountOfKeyword(Keyword.FLANKING) * 15, "flanking");
|
||||
value += addValue(c.getAmountOfKeyword(Keyword.EXALTED) * 15, "exalted");
|
||||
value += addValue(c.getKeywordMagnitude(Keyword.ANNIHILATOR) * 50, "eldrazi");
|
||||
value += addValue(c.getKeywordMagnitude(Keyword.ABSORB) * 11, "absorb");
|
||||
|
||||
// Keywords that may produce temporary or permanent buffs over time
|
||||
if (c.hasKeyword(Keyword.PROWESS)) {
|
||||
value += addValue(5, "prowess");
|
||||
}
|
||||
if (c.hasKeyword(Keyword.OUTLAST)) {
|
||||
value += addValue(10, "outlast");
|
||||
}
|
||||
value += addValue(c.getKeywordMagnitude(Keyword.BUSHIDO) * 16, "bushido");
|
||||
value += addValue(c.getAmountOfKeyword(Keyword.FLANKING) * 15, "flanking");
|
||||
value += addValue(c.getAmountOfKeyword(Keyword.EXALTED) * 15, "exalted");
|
||||
value += addValue(c.getAmountOfKeyword(Keyword.MELEE) * 18, "melee");
|
||||
value += addValue(c.getAmountOfKeyword(Keyword.PROWESS) * 5, "prowess");
|
||||
|
||||
// Defensive Keywords
|
||||
if (c.hasKeyword(Keyword.REACH) && !c.hasKeyword(Keyword.FLYING)) {
|
||||
@@ -141,18 +138,41 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
||||
value += addValue(35, "hexproof");
|
||||
} else if (c.hasKeyword(Keyword.SHROUD)) {
|
||||
value += addValue(30, "shroud");
|
||||
} else if (c.hasKeyword(Keyword.WARD)) {
|
||||
value += addValue(10, "ward");
|
||||
}
|
||||
if (c.hasKeyword(Keyword.PROTECTION)) {
|
||||
value += addValue(20, "protection");
|
||||
}
|
||||
|
||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||
if (sa.isAbility()) {
|
||||
value += addValue(evaluateSpellAbility(sa), "sa: " + sa);
|
||||
}
|
||||
}
|
||||
|
||||
// paired creatures are more valuable because they grant a bonus to the other creature
|
||||
if (c.isPaired()) {
|
||||
value += addValue(14, "paired");
|
||||
}
|
||||
|
||||
if (c.hasEncodedCard()) {
|
||||
value += addValue(24, "encoded");
|
||||
}
|
||||
|
||||
if (ComputerUtilCard.hasActiveUndyingOrPersist(c)) {
|
||||
value += addValue(30, "revive");
|
||||
}
|
||||
|
||||
// Bad keywords
|
||||
if (c.hasKeyword(Keyword.DEFENDER) || c.hasKeyword("CARDNAME can't attack.")) {
|
||||
value -= subValue((power * 9) + 40, "defender");
|
||||
} else if (c.getSVar("SacrificeEndCombat").equals("True")) {
|
||||
value -= subValue(40, "sac-end");
|
||||
}
|
||||
if (c.hasKeyword("CARDNAME can't block.")) {
|
||||
if (c.hasKeyword("CARDNAME can't attack or block.")) {
|
||||
value = addValue(50 + (c.getCMC() * 5), "useless"); // reset everything - useless
|
||||
} else if (c.hasKeyword("CARDNAME can't block.")) {
|
||||
value -= subValue(10, "cant-block");
|
||||
} else if (c.hasKeyword("CARDNAME attacks each combat if able.")) {
|
||||
value -= subValue(10, "must-attack");
|
||||
@@ -165,10 +185,18 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
||||
if (c.hasSVar("DestroyWhenDamaged")) {
|
||||
value -= subValue((toughness - 1) * 9, "dies-to-dmg");
|
||||
}
|
||||
|
||||
if (c.hasKeyword("CARDNAME can't attack or block.")) {
|
||||
value = addValue(50 + (c.getCMC() * 5), "useless"); // reset everything - useless
|
||||
if (c.getSVar("Targeting").equals("Dies")) {
|
||||
value -= subValue(25, "dies");
|
||||
}
|
||||
|
||||
if (c.isUntapped()) {
|
||||
value += addValue(1, "untapped");
|
||||
}
|
||||
|
||||
if (!c.getManaAbilities().isEmpty()) {
|
||||
value += addValue(10, "manadork");
|
||||
}
|
||||
|
||||
if (c.hasKeyword("CARDNAME doesn't untap during your untap step.")) {
|
||||
if (c.isTapped()) {
|
||||
value = addValue(50 + (c.getCMC() * 5), "tapped-useless"); // reset everything - useless
|
||||
@@ -185,40 +213,19 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
||||
} else if (c.hasKeyword(Keyword.ECHO) && c.cameUnderControlSinceLastUpkeep()) {
|
||||
value -= subValue(10, "echo-unpaid");
|
||||
}
|
||||
|
||||
if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) {
|
||||
value -= subValue(20, "upkeep-dmg");
|
||||
}
|
||||
if (c.hasKeyword(Keyword.FADING)) {
|
||||
value -= subValue(20, "fading");
|
||||
}
|
||||
if (c.hasKeyword(Keyword.VANISHING)) {
|
||||
value -= subValue(20, "vanishing");
|
||||
}
|
||||
if (c.getSVar("Targeting").equals("Dies")) {
|
||||
value -= subValue(25, "dies");
|
||||
if (c.hasKeyword(Keyword.PHASING)) {
|
||||
value -= subValue(10, "phasing");
|
||||
}
|
||||
|
||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||
if (sa.isAbility()) {
|
||||
value += addValue(evaluateSpellAbility(sa), "sa: " + sa);
|
||||
}
|
||||
}
|
||||
if (!c.getManaAbilities().isEmpty()) {
|
||||
value += addValue(10, "manadork");
|
||||
}
|
||||
|
||||
if (c.isUntapped()) {
|
||||
value += addValue(1, "untapped");
|
||||
}
|
||||
|
||||
// paired creatures are more valuable because they grant a bonus to the other creature
|
||||
if (c.isPaired()) {
|
||||
value += addValue(14, "paired");
|
||||
}
|
||||
|
||||
if (!c.hasEncodedCard()) {
|
||||
value += addValue(24, "encoded");
|
||||
// TODO no longer a KW
|
||||
if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) {
|
||||
value -= subValue(20, "upkeep-dmg");
|
||||
}
|
||||
|
||||
// card-specific evaluation modifier
|
||||
@@ -240,7 +247,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
||||
&& (!sa.hasParam("Defined") || "Self".equals(sa.getParam("Defined")))) {
|
||||
if (sa.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
|
||||
// Electrostatic Pummeler, can be expanded for similar cards
|
||||
int initPower = getEffectivePower(sa.getHostCard());
|
||||
int initPower = sa.getHostCard().getNetPower();
|
||||
int pumpedPower = initPower;
|
||||
int energy = sa.getHostCard().getController().getCounters(CounterEnumType.ENERGY);
|
||||
if (energy > 0) {
|
||||
|
||||
@@ -462,7 +462,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
}
|
||||
|
||||
int landsOTB = CardLists.filter(p.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
|
||||
int landsOTB = CardLists.count(p.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA);
|
||||
|
||||
if (!p.isOpponentOf(player)) {
|
||||
if (landsOTB <= 2) {
|
||||
@@ -611,7 +611,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
hand.removeAll(toReturn);
|
||||
|
||||
CardCollection landsInHand = CardLists.filter(hand, Presets.LANDS);
|
||||
int numLandsInHand = landsInHand.size() - CardLists.filter(toReturn, Presets.LANDS).size();
|
||||
int numLandsInHand = landsInHand.size() - CardLists.count(toReturn, Presets.LANDS);
|
||||
|
||||
// If we're flooding with lands, get rid of the worst land we have
|
||||
if (numLandsInHand > 0 && numLandsInHand > numLandsDesired) {
|
||||
|
||||
@@ -1558,8 +1558,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
final boolean evasive = keyword.equals("Unblockable") || keyword.equals("Fear")
|
||||
|| keyword.equals("Intimidate") || keyword.equals("Shadow")
|
||||
|| keyword.equals("Flying") || keyword.equals("Horsemanship")
|
||||
|| keyword.endsWith("walk") || keyword.startsWith("CantBeBlockedBy")
|
||||
|| keyword.equals("All creatures able to block CARDNAME do so.");
|
||||
|| keyword.endsWith("walk") || keyword.equals("All creatures able to block CARDNAME do so.");
|
||||
// give evasive keywords to creatures that can attack and deal damage
|
||||
|
||||
boolean canBeBlocked = false;
|
||||
|
||||
@@ -122,10 +122,7 @@ public class ControlGainAi extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
|
||||
CardCollection list = new CardCollection();
|
||||
for (Player pl : opponents) {
|
||||
list.addAll(pl.getCardsIn(ZoneType.Battlefield));
|
||||
}
|
||||
CardCollection list = opponents.getCardsIn(ZoneType.Battlefield);
|
||||
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||
|
||||
@@ -325,7 +322,7 @@ public class ControlGainAi extends SpellAbilityAi {
|
||||
} else {
|
||||
return this.canPlayAI(ai, sa);
|
||||
}
|
||||
} // pumpDrawbackAI()
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
|
||||
@@ -124,7 +124,7 @@ public class CounterAi extends SpellAbilityAi {
|
||||
|
||||
if (toPay <= usableManaSources) {
|
||||
// If this is a reusable Resource, feel free to play it most of the time
|
||||
if (!SpellAbilityAi.playReusable(ai,sa)) {
|
||||
if (!SpellAbilityAi.playReusable(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,7 +356,8 @@ public class DamageDealAi extends DamageAiBase {
|
||||
return c.getSVar("Targeting").equals("Dies")
|
||||
|| (ComputerUtilCombat.getEnoughDamageToKill(c, d, source, false, noPrevention) <= d)
|
||||
&& !ComputerUtil.canRegenerate(ai, c)
|
||||
&& !(c.getSVar("SacMe").length() > 0);
|
||||
&& !(c.getSVar("SacMe").length() > 0)
|
||||
&& !ComputerUtilCard.hasActiveUndyingOrPersist(c);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -489,10 +490,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
*/
|
||||
private boolean damageTargetAI(final Player ai, final SpellAbility saMe, final int dmg, final boolean immediately) {
|
||||
final TargetRestrictions tgt = saMe.getTargetRestrictions();
|
||||
if ("Atarka's Command".equals(ComputerUtilAbility.getAbilitySourceName(saMe))) {
|
||||
// playReusable in damageChooseNontargeted wrongly assumes that CharmEffect options are re-usable
|
||||
return shouldTgtP(ai, saMe, dmg, false);
|
||||
}
|
||||
|
||||
if (tgt == null) {
|
||||
return damageChooseNontargeted(ai, saMe, dmg);
|
||||
}
|
||||
@@ -834,6 +832,10 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
}
|
||||
}
|
||||
if ("Atarka's Command".equals(ComputerUtilAbility.getAbilitySourceName(saMe))) {
|
||||
// playReusable wrongly assumes that CharmEffect options are re-usable
|
||||
return positive;
|
||||
}
|
||||
if (!positive && !(saMe instanceof AbilitySub)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return (c.getShieldCount() == 0 && !ComputerUtil.canRegenerate(ai, c));
|
||||
return c.getShieldCount() == 0 && !ComputerUtil.canRegenerate(ai, c);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -440,8 +440,8 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
boolean nonBasicTgt = !tgtLand.isBasicLand();
|
||||
|
||||
// Try not to lose tempo too much and not to mana-screw yourself when considering this logic
|
||||
int numLandsInHand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
|
||||
int numLandsOTB = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
|
||||
int numLandsInHand = CardLists.count(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS_PRODUCING_MANA);
|
||||
int numLandsOTB = CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA);
|
||||
|
||||
// If the opponent skipped a land drop, consider not looking at having the extra land in hand if the profile allows it
|
||||
boolean isHighPriority = highPriorityIfNoLandDrop && oppSkippedLandDrop;
|
||||
|
||||
@@ -93,7 +93,7 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
if (sa.hasParam("AnyNumber")) {
|
||||
if ("DiscardUncastableAndExcess".equals(aiLogic)) {
|
||||
final CardCollectionView inHand = ai.getCardsIn(ZoneType.Hand);
|
||||
final int numLandsOTB = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS).size();
|
||||
final int numLandsOTB = CardLists.count(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS);
|
||||
int numDiscard = 0;
|
||||
int numOppInHand = 0;
|
||||
for (Player p : ai.getGame().getPlayers()) {
|
||||
@@ -143,7 +143,7 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
// some other variables here, like handsize vs. maxHandSize
|
||||
|
||||
return randomReturn;
|
||||
} // discardCanPlayAI()
|
||||
}
|
||||
|
||||
private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
|
||||
final PlayerCollection opps = ai.getOpponents();
|
||||
@@ -164,7 +164,7 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} // discardTargetAI()
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
@@ -198,7 +198,7 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
return true;
|
||||
} // discardTrigger()
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
@@ -209,10 +209,10 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
}
|
||||
// TODO: check for some extra things
|
||||
return true;
|
||||
} // discardCheckDrawbackAI()
|
||||
}
|
||||
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
if ( mode == PlayerActionConfirmMode.Random ) {
|
||||
if (mode == PlayerActionConfirmMode.Random) {
|
||||
// TODO For now AI will always discard Random used currently with: Balduvian Horde and similar cards
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.google.common.base.Predicates;
|
||||
import forge.ai.AiPlayDecision;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
@@ -16,7 +17,6 @@ import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaAtom;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
@@ -65,7 +65,7 @@ public class ManaEffectAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
|
||||
if (ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai && canRampPool(ai, sa.getHostCard())) {
|
||||
if (ph.is(PhaseType.END_OF_TURN) && (ph.getNextTurn() == ai || ComputerUtilCard.willUntap(ai, sa.getHostCard())) && canRampPool(ai, sa.getHostCard())) {
|
||||
return true;
|
||||
}
|
||||
if (!ph.is(PhaseType.MAIN2) || !ComputerUtil.activateForCost(sa, ai)) {
|
||||
@@ -87,7 +87,7 @@ public class ManaEffectAi extends SpellAbilityAi {
|
||||
if (logic.startsWith("ManaRitual")) {
|
||||
return ph.is(PhaseType.MAIN2, ai) || ph.is(PhaseType.MAIN1, ai);
|
||||
} else if ("AtOppEOT".equals(logic)) {
|
||||
return !ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manaBurn) && ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai;
|
||||
return !ai.getManaPool().hasBurn() && ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai;
|
||||
}
|
||||
return super.checkPhaseRestrictions(ai, sa, ph, logic);
|
||||
}
|
||||
@@ -106,7 +106,7 @@ public class ManaEffectAi extends SpellAbilityAi {
|
||||
|
||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
boolean moreManaNextTurn = false;
|
||||
if (ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai && canRampPool(ai, sa.getHostCard())) {
|
||||
if (ph.is(PhaseType.END_OF_TURN) && (ph.getNextTurn() == ai || ComputerUtilCard.willUntap(ai, sa.getHostCard())) && canRampPool(ai, sa.getHostCard())) {
|
||||
moreManaNextTurn = true;
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ public class ManaEffectAi extends SpellAbilityAi {
|
||||
return castableSpells.size() > 0;
|
||||
}
|
||||
|
||||
private boolean canRampPool(Player ai, Card source) {
|
||||
public static boolean canRampPool(Player ai, Card source) {
|
||||
ManaPool mp = ai.getManaPool();
|
||||
Mana test = null;
|
||||
if (mp.isEmpty()) {
|
||||
|
||||
@@ -244,7 +244,7 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
// Only cast if there are X or more mana sources controlled by the AI *or*
|
||||
// if there are X-1 mana sources in play but the AI has an extra land in hand
|
||||
CardCollection m = ComputerUtilMana.getAvailableManaSources(ai, true);
|
||||
int extraMana = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS).size() > 0 ? 1 : 0;
|
||||
int extraMana = CardLists.count(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS) > 0 ? 1 : 0;
|
||||
if (card.getName().equals("Illusions of Grandeur")) {
|
||||
// TODO: this is currently hardcoded for specific Illusions-Donate cost reduction spells, need to make this generic.
|
||||
extraMana += Math.min(3, CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), Predicates.or(CardPredicates.nameEquals("Sapphire Medallion"), CardPredicates.nameEquals("Helm of Awakening"))).size()) * 2; // each cost-reduction spell accounts for {1} in both Illusions and Donate
|
||||
|
||||
@@ -139,7 +139,7 @@ public class PermanentCreatureAi extends PermanentAi {
|
||||
boolean hasETBTrigger = card.hasETBTrigger(true);
|
||||
boolean hasAmbushAI = card.hasSVar("AmbushAI");
|
||||
boolean defOnlyAmbushAI = hasAmbushAI && "BlockOnly".equals(card.getSVar("AmbushAI"));
|
||||
boolean hasFloatMana = ai.getManaPool().totalMana() > 0;
|
||||
boolean loseFloatMana = ai.getManaPool().totalMana() > 0 && !ManaEffectAi.canRampPool(ai, card);
|
||||
boolean willDiscardNow = isOwnEOT && !ai.isUnlimitedHandSize() && ai.getCardsIn(ZoneType.Hand).size() > ai.getMaxHandSize();
|
||||
boolean willDieNow = combat != null && ComputerUtilCombat.lifeInSeriousDanger(ai, combat);
|
||||
boolean wantToCastInMain1 = ph.is(PhaseType.MAIN1, ai) && ComputerUtil.castPermanentInMain1(ai, sa);
|
||||
@@ -176,7 +176,7 @@ public class PermanentCreatureAi extends PermanentAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (hasFloatMana || willDiscardNow || willDieNow) {
|
||||
if (loseFloatMana || 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
|
||||
return true;
|
||||
} else if (isCommander && isMyMain1OrLater) {
|
||||
|
||||
@@ -37,7 +37,7 @@ public class PowerExchangeAi extends SpellAbilityAi {
|
||||
return c.canBeTargetedBy(sa) && c.getController() != ai;
|
||||
}
|
||||
});
|
||||
CardLists.sortByPowerAsc(list);
|
||||
CardLists.sortByPowerDesc(list);
|
||||
c1 = list.isEmpty() ? null : list.get(0);
|
||||
if (sa.hasParam("Defined")) {
|
||||
c2 = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
|
||||
@@ -52,7 +52,7 @@ public class PowerExchangeAi extends SpellAbilityAi {
|
||||
if (c1 == null || c2 == null) {
|
||||
return false;
|
||||
}
|
||||
if (ComputerUtilCard.evaluateCreature(c1) > ComputerUtilCard.evaluateCreature(c2) + 40) {
|
||||
if (sa.isMandatory() || ComputerUtilCard.evaluateCreature(c1) > ComputerUtilCard.evaluateCreature(c2) + 40) {
|
||||
sa.getTargets().add(c1);
|
||||
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean evasive = (keyword.endsWith("Unblockable") || keyword.endsWith("Shadow") || keyword.startsWith("CantBeBlockedBy"));
|
||||
final boolean evasive = keyword.endsWith("Unblockable") || keyword.endsWith("Shadow");
|
||||
// give evasive keywords to creatures that can or do attack
|
||||
if (evasive) {
|
||||
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|
||||
|
||||
@@ -109,7 +109,7 @@ public class RearrangeTopOfLibraryAi extends SpellAbilityAi {
|
||||
|
||||
Player p = pc.getFirst(); // currently always a single target spell
|
||||
Card top = p.getCardsIn(ZoneType.Library).getFirst();
|
||||
int landsOTB = CardLists.filter(p.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
|
||||
int landsOTB = CardLists.count(p.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA);
|
||||
int cmc = top.isSplitCard() ? Math.min(top.getCMC(Card.SplitCMCMode.LeftSplitCMC), top.getCMC(Card.SplitCMCMode.RightSplitCMC))
|
||||
: top.getCMC();
|
||||
int maxCastable = ComputerUtilMana.getAvailableManaEstimate(p, false);
|
||||
|
||||
@@ -24,6 +24,10 @@ public abstract class RevealAiBase extends SpellAbilityAi {
|
||||
opps = Lists.newArrayList(Iterables.filter(opps, PlayerPredicates.isTargetableBy(sa)));
|
||||
|
||||
if (opps.isEmpty()) {
|
||||
if (mandatory && sa.canTarget(ai)) {
|
||||
sa.getTargets().add(ai);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -198,12 +198,13 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
Predicate<Card> findBlockers = CardPredicates.possibleBlockerForAtLeastOne(attackers);
|
||||
List<Card> creatureList = CardLists.filter(tapList, findBlockers);
|
||||
|
||||
// TODO check if own creature would be forced to attack and we want to keep it alive
|
||||
|
||||
if (!attackers.isEmpty() && !creatureList.isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestCreatureAI(creatureList);
|
||||
} else if (sa.getRootAbility().isTrigger() || ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(tapList);
|
||||
}
|
||||
|
||||
} else if (phase.isPlayerTurn(opp)
|
||||
&& phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
// Tap creatures possible blockers before combat during AI's turn.
|
||||
|
||||
@@ -26,6 +26,7 @@ import forge.game.cost.CostTap;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.phase.Untap;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -213,6 +214,7 @@ public class UntapAi extends SpellAbilityAi {
|
||||
|
||||
untapList.remove(choice);
|
||||
list.remove(choice);
|
||||
// TODO ComputerUtilCard.willUntap(ai, choice)
|
||||
sa.getTargets().add(choice);
|
||||
}
|
||||
return true;
|
||||
@@ -321,7 +323,7 @@ public class UntapAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// See if there's anything to untap that is tapped and that doesn't untap during the next untap step by itself
|
||||
CardCollection noAutoUntap = CardLists.filter(untapList, CardPredicates.hasKeyword("CARDNAME doesn't untap during your untap step."));
|
||||
CardCollection noAutoUntap = CardLists.filter(untapList, Predicates.not(Untap.CANUNTAP));
|
||||
if (!noAutoUntap.isEmpty()) {
|
||||
return ComputerUtilCard.getBestAI(noAutoUntap);
|
||||
}
|
||||
|
||||
@@ -86,10 +86,12 @@ public class StaticData {
|
||||
final Map<String, CardRules> variantsCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
final Map<String, CardRules> customizedCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
for (CardEdition e : editions) {
|
||||
if (e.getType() == CardEdition.Type.FUNNY || e.getBorderColor() == CardEdition.BorderColor.SILVER) {
|
||||
for (CardEdition.CardInSet cis : e.getAllCardsInSet()) {
|
||||
funnyCards.add(cis.name);
|
||||
if (!loadNonLegalCards) {
|
||||
for (CardEdition e : editions) {
|
||||
if (e.getType() == CardEdition.Type.FUNNY || e.getBorderColor() == CardEdition.BorderColor.SILVER) {
|
||||
for (CardEdition.CardInSet cis : e.getAllCardsInSet()) {
|
||||
funnyCards.add(cis.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -705,8 +707,6 @@ public class StaticData {
|
||||
return altCandidate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the Art Count for a given <code>PaperCard</code> looking for a candidate in all
|
||||
* available databases.
|
||||
@@ -734,7 +734,6 @@ public class StaticData {
|
||||
public void setMulliganRule(MulliganDefs.MulliganRule rule) {
|
||||
mulliganRule = rule;
|
||||
}
|
||||
|
||||
public MulliganDefs.MulliganRule getMulliganRule() {
|
||||
return mulliganRule;
|
||||
}
|
||||
@@ -754,7 +753,7 @@ public class StaticData {
|
||||
}
|
||||
|
||||
public CardDb.CardArtPreference getCardArtPreference(boolean latestArt, boolean coreExpansionOnly) {
|
||||
if (latestArt){
|
||||
if (latestArt) {
|
||||
return coreExpansionOnly ? CardDb.CardArtPreference.LATEST_ART_CORE_EXPANSIONS_REPRINT_ONLY : CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS;
|
||||
}
|
||||
return coreExpansionOnly ? CardDb.CardArtPreference.ORIGINAL_ART_CORE_EXPANSIONS_REPRINT_ONLY : CardDb.CardArtPreference.ORIGINAL_ART_ALL_EDITIONS;
|
||||
|
||||
@@ -1037,7 +1037,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
}
|
||||
|
||||
public PaperCard createUnsupportedCard(String cardRequest) {
|
||||
|
||||
CardRequest request = CardRequest.fromString(cardRequest);
|
||||
CardEdition cardEdition = CardEdition.UNKNOWN;
|
||||
CardRarity cardRarity = CardRarity.Unknown;
|
||||
@@ -1078,7 +1077,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
}
|
||||
|
||||
return new PaperCard(CardRules.getUnsupportedCardNamed(request.cardName), cardEdition.getCode(), cardRarity);
|
||||
|
||||
}
|
||||
|
||||
private final Editor editor = new Editor();
|
||||
|
||||
@@ -93,7 +93,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
int len = oracleText.length();
|
||||
for (int i = 0; i < len; i++) {
|
||||
char c = oracleText.charAt(i); // This is to avoid needless allocations performed by toCharArray()
|
||||
switch(c) {
|
||||
switch (c) {
|
||||
case('('): isReminder = i > 0; break; // if oracle has only reminder, consider it valid rules (basic and true lands need this)
|
||||
case(')'): isReminder = false; break;
|
||||
case('{'): isSymbol = true; break;
|
||||
@@ -132,7 +132,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
switch(splitType.getAggregationMethod()) {
|
||||
switch (splitType.getAggregationMethod()) {
|
||||
case COMBINE:
|
||||
return mainPart.getName() + " // " + otherPart.getName();
|
||||
default:
|
||||
@@ -149,7 +149,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
|
||||
@Override
|
||||
public CardType getType() {
|
||||
switch(splitType.getAggregationMethod()) {
|
||||
switch (splitType.getAggregationMethod()) {
|
||||
case COMBINE: // no cards currently have different types
|
||||
return CardType.combine(mainPart.getType(), otherPart.getType());
|
||||
default:
|
||||
@@ -159,7 +159,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
|
||||
@Override
|
||||
public ManaCost getManaCost() {
|
||||
switch(splitType.getAggregationMethod()) {
|
||||
switch (splitType.getAggregationMethod()) {
|
||||
case COMBINE:
|
||||
return ManaCost.combine(mainPart.getManaCost(), otherPart.getManaCost());
|
||||
default:
|
||||
@@ -169,7 +169,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
|
||||
@Override
|
||||
public ColorSet getColor() {
|
||||
switch(splitType.getAggregationMethod()) {
|
||||
switch (splitType.getAggregationMethod()) {
|
||||
case COMBINE:
|
||||
return ColorSet.fromMask(mainPart.getColor().getColor() | otherPart.getColor().getColor());
|
||||
default:
|
||||
@@ -186,7 +186,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
}
|
||||
|
||||
public boolean canCastWithAvailable(byte colorCode) {
|
||||
switch(splitType.getAggregationMethod()) {
|
||||
switch (splitType.getAggregationMethod()) {
|
||||
case COMBINE:
|
||||
return canCastFace(mainPart, colorCode) || canCastFace(otherPart, colorCode);
|
||||
default:
|
||||
@@ -202,7 +202,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
|
||||
@Override
|
||||
public String getOracleText() {
|
||||
switch(splitType.getAggregationMethod()) {
|
||||
switch (splitType.getAggregationMethod()) {
|
||||
case COMBINE:
|
||||
return mainPart.getOracleText() + "\r\n\r\n" + otherPart.getOracleText();
|
||||
default:
|
||||
|
||||
@@ -60,7 +60,7 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|
||||
// Calculated fields are below:
|
||||
private transient CardRarity rarity; // rarity is given in ctor when set is assigned
|
||||
// Reference to a new instance of Self, but foiled!
|
||||
private transient PaperCard foiledVersion = null;
|
||||
private transient PaperCard foiledVersion;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@@ -170,7 +170,7 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|
||||
}
|
||||
};
|
||||
|
||||
public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0){
|
||||
public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0) {
|
||||
this(rules0, edition0, rarity0, IPaperCard.DEFAULT_ART_INDEX, false,
|
||||
IPaperCard.NO_COLLECTOR_NUMBER, IPaperCard.NO_ARTIST_NAME);
|
||||
}
|
||||
@@ -186,7 +186,7 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|
||||
artIndex = Math.max(artIndex0, IPaperCard.DEFAULT_ART_INDEX);
|
||||
foil = foil0;
|
||||
rarity = rarity0;
|
||||
artist = artist0 != null ? TextUtil.normalizeText(artist0) : IPaperCard.NO_ARTIST_NAME;
|
||||
artist = TextUtil.normalizeText(artist0);
|
||||
collectorNumber = (collectorNumber0 != null) && (collectorNumber0.length() > 0) ? collectorNumber0 : IPaperCard.NO_COLLECTOR_NUMBER;
|
||||
// If the user changes the language this will make cards sort by the old language until they restart the game.
|
||||
// This is a good tradeoff
|
||||
|
||||
@@ -349,36 +349,7 @@ public class AbilityUtils {
|
||||
cards.addAll(CardLists.getValidCards(candidates, validDefined, hostCard.getController(), hostCard, sa));
|
||||
return cards;
|
||||
} else {
|
||||
CardCollection list = null;
|
||||
if (sa instanceof SpellAbility) {
|
||||
SpellAbility root = ((SpellAbility)sa).getRootAbility();
|
||||
if (defined.startsWith("SacrificedCards")) {
|
||||
list = root.getPaidList("SacrificedCards");
|
||||
} else if (defined.startsWith("Sacrificed")) {
|
||||
list = root.getPaidList("Sacrificed");
|
||||
} else if (defined.startsWith("Revealed")) {
|
||||
list = root.getPaidList("Revealed");
|
||||
} else if (defined.startsWith("DiscardedCards")) {
|
||||
list = root.getPaidList("DiscardedCards");
|
||||
} else if (defined.startsWith("Discarded")) {
|
||||
list = root.getPaidList("Discarded");
|
||||
} else if (defined.startsWith("ExiledCards")) {
|
||||
list = root.getPaidList("ExiledCards");
|
||||
} else if (defined.startsWith("Exiled")) {
|
||||
list = root.getPaidList("Exiled");
|
||||
} else if (defined.startsWith("Milled")) {
|
||||
list = root.getPaidList("Milled");
|
||||
} else if (defined.startsWith("TappedCards")) {
|
||||
list = root.getPaidList("TappedCards");
|
||||
} else if (defined.startsWith("Tapped")) {
|
||||
list = root.getPaidList("Tapped");
|
||||
} else if (defined.startsWith("UntappedCards")) {
|
||||
list = root.getPaidList("UntappedCards");
|
||||
} else if (defined.startsWith("Untapped")) {
|
||||
list = root.getPaidList("Untapped");
|
||||
}
|
||||
}
|
||||
|
||||
CardCollection list = getPaidCards(sa, defined);
|
||||
if (list != null) {
|
||||
cards.addAll(list);
|
||||
}
|
||||
@@ -1004,7 +975,7 @@ public class AbilityUtils {
|
||||
|
||||
final Player player = sa instanceof SpellAbility ? ((SpellAbility)sa).getActivatingPlayer() : card.getController();
|
||||
|
||||
if (defined.equals("Self") || defined.equals("ThisTargetedCard")) {
|
||||
if (defined.equals("Self") || defined.equals("ThisTargetedCard") || getPaidCards(sa, defined) != null) {
|
||||
// do nothing, Self is for Cards, not Players
|
||||
} else if (defined.equals("TargetedOrController")) {
|
||||
players.addAll(getDefinedPlayers(card, "Targeted", sa));
|
||||
@@ -1225,10 +1196,8 @@ public class AbilityUtils {
|
||||
players.add(p);
|
||||
}
|
||||
}
|
||||
else if (defined.equals("ChosenCardController")) {
|
||||
for (final Card chosen : card.getChosenCards()) {
|
||||
players.add(game.getCardState(chosen).getController());
|
||||
}
|
||||
else if (defined.startsWith("ChosenCard")) {
|
||||
addPlayer(Lists.newArrayList(card.getChosenCards()), defined, players);
|
||||
}
|
||||
else if (defined.equals("SourceController")) {
|
||||
players.add(sa.getHostCard().getController());
|
||||
@@ -3864,6 +3833,39 @@ public class AbilityUtils {
|
||||
return someCards;
|
||||
}
|
||||
|
||||
public static CardCollection getPaidCards(CardTraitBase sa, String defined) {
|
||||
CardCollection list = null;
|
||||
if (sa instanceof SpellAbility) {
|
||||
SpellAbility root = ((SpellAbility)sa).getRootAbility();
|
||||
if (defined.startsWith("SacrificedCards")) {
|
||||
list = root.getPaidList("SacrificedCards");
|
||||
} else if (defined.startsWith("Sacrificed")) {
|
||||
list = root.getPaidList("Sacrificed");
|
||||
} else if (defined.startsWith("Revealed")) {
|
||||
list = root.getPaidList("Revealed");
|
||||
} else if (defined.startsWith("DiscardedCards")) {
|
||||
list = root.getPaidList("DiscardedCards");
|
||||
} else if (defined.startsWith("Discarded")) {
|
||||
list = root.getPaidList("Discarded");
|
||||
} else if (defined.startsWith("ExiledCards")) {
|
||||
list = root.getPaidList("ExiledCards");
|
||||
} else if (defined.startsWith("Exiled")) {
|
||||
list = root.getPaidList("Exiled");
|
||||
} else if (defined.startsWith("Milled")) {
|
||||
list = root.getPaidList("Milled");
|
||||
} else if (defined.startsWith("TappedCards")) {
|
||||
list = root.getPaidList("TappedCards");
|
||||
} else if (defined.startsWith("Tapped")) {
|
||||
list = root.getPaidList("Tapped");
|
||||
} else if (defined.startsWith("UntappedCards")) {
|
||||
list = root.getPaidList("UntappedCards");
|
||||
} else if (defined.startsWith("Untapped")) {
|
||||
list = root.getPaidList("Untapped");
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static int getNumberOfTypes(final Card card) {
|
||||
EnumSet<CardType.CoreType> types = EnumSet.noneOf(CardType.CoreType.class);
|
||||
Iterables.addAll(types, card.getType().getCoreTypes());
|
||||
|
||||
@@ -133,6 +133,11 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
boolean existingCounter = sa.hasParam("CounterType") && sa.getParam("CounterType").equals("ExistingCounter");
|
||||
boolean eachExistingCounter = sa.hasParam("EachExistingCounter");
|
||||
|
||||
if (sa.hasParam("Optional") && !pc.confirmAction
|
||||
(sa, null, Localizer.getInstance().getMessage("lblDoYouWantPutCounter"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<GameEntity> tgtObjects = Lists.newArrayList();
|
||||
int divrem = 0;
|
||||
if (sa.hasParam("Bolster")) {
|
||||
@@ -198,11 +203,6 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("Optional")
|
||||
&& !pc.confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantPutCounter"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
int counterRemain = counterAmount;
|
||||
if (sa.hasParam("DividedRandomly")) {
|
||||
CardCollection targets = new CardCollection();
|
||||
|
||||
@@ -98,8 +98,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
private CardState currentState;
|
||||
private CardStateName currentStateName = CardStateName.Original;
|
||||
|
||||
private Zone castFrom = null;
|
||||
private SpellAbility castSA = null;
|
||||
private Zone castFrom;
|
||||
private SpellAbility castSA;
|
||||
|
||||
private CardDamageHistory damageHistory = new CardDamageHistory();
|
||||
// Hidden keywords won't be displayed on the card
|
||||
@@ -119,18 +119,18 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
private Card encoding, cloneOrigin, haunting, effectSource, pairedWith, meldedWith;
|
||||
private Card mergedTo;
|
||||
|
||||
private SpellAbility effectSourceAbility = null;
|
||||
private SpellAbility effectSourceAbility;
|
||||
|
||||
private GameEntity entityAttachedTo = null;
|
||||
private GameEntity entityAttachedTo;
|
||||
|
||||
private GameEntity mustAttackEntity = null;
|
||||
private GameEntity mustAttackEntityThisTurn = null;
|
||||
private GameEntity mustAttackEntity;
|
||||
private GameEntity mustAttackEntityThisTurn;
|
||||
|
||||
private final Map<StaticAbility, CardPlayOption> mayPlay = Maps.newHashMap();
|
||||
|
||||
// changes by AF animate and continuous static effects
|
||||
|
||||
protected CardChangedType changedTypeByText = null; // Layer 3 by Text Change
|
||||
protected CardChangedType changedTypeByText; // Layer 3 by Text Change
|
||||
// x=timestamp y=StaticAbility id
|
||||
private final Table<Long, Long, CardChangedType> changedCardTypesByText = TreeBasedTable.create(); // Layer 3
|
||||
private final Table<Long, Long, CardChangedType> changedCardTypesCharacterDefining = TreeBasedTable.create(); // Layer 4 CDA
|
||||
@@ -193,22 +193,22 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
private boolean sickness = true; // summoning sickness
|
||||
private boolean token = false;
|
||||
private boolean tokenCard = false;
|
||||
private Card copiedPermanent = null;
|
||||
private Card copiedPermanent;
|
||||
private boolean copiedSpell = false;
|
||||
|
||||
private boolean canCounter = true;
|
||||
|
||||
private boolean unearthed;
|
||||
|
||||
private boolean monstrous = false;
|
||||
private boolean monstrous;
|
||||
|
||||
private boolean renowned = false;
|
||||
private boolean renowned;
|
||||
|
||||
private boolean manifested = false;
|
||||
private boolean manifested;
|
||||
|
||||
private boolean foretold = false;
|
||||
private boolean foretoldThisTurn = false;
|
||||
private boolean foretoldByEffect = false;
|
||||
private boolean foretold;
|
||||
private boolean foretoldThisTurn;
|
||||
private boolean foretoldByEffect;
|
||||
|
||||
private int timesCrewedThisTurn = 0;
|
||||
|
||||
@@ -253,21 +253,21 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
private String oracleText = "";
|
||||
|
||||
private int damage;
|
||||
private boolean hasBeenDealtDeathtouchDamage = false;
|
||||
private boolean hasBeenDealtDeathtouchDamage;
|
||||
|
||||
// regeneration
|
||||
private FCollection<Card> shields = new FCollection<>();
|
||||
private int regeneratedThisTurn = 0;
|
||||
private int regeneratedThisTurn;
|
||||
|
||||
private int turnInZone;
|
||||
// the player that under which control it enters
|
||||
private Player turnInController = null;
|
||||
private Player turnInController;
|
||||
|
||||
private Map<String, Integer> xManaCostPaidByColor;
|
||||
|
||||
private Player owner = null;
|
||||
private Player controller = null;
|
||||
private long controllerTimestamp = 0;
|
||||
private Player owner;
|
||||
private Player controller;
|
||||
private long controllerTimestamp;
|
||||
private NavigableMap<Long, Player> tempControllers = Maps.newTreeMap();
|
||||
|
||||
private String originalText = "", text = "";
|
||||
@@ -283,8 +283,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
private String chosenMode = "";
|
||||
private String currentRoom = null;
|
||||
|
||||
private Card exiledWith = null;
|
||||
private Player exiledBy = null;
|
||||
private Card exiledWith;
|
||||
private Player exiledBy;
|
||||
|
||||
private Map<Long, Player> goad = Maps.newTreeMap();
|
||||
|
||||
@@ -298,11 +298,11 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
private final List<Object[]> staticCommandList = Lists.newArrayList();
|
||||
|
||||
// Zone-changing spells should store card's zone here
|
||||
private Zone currentZone = null;
|
||||
private Zone currentZone;
|
||||
|
||||
// LKI copies of cards are allowed to store the LKI about the zone the card was known to be in last.
|
||||
// For all cards except LKI copies this should always be null.
|
||||
private Zone savedLastKnownZone = null;
|
||||
private Zone savedLastKnownZone;
|
||||
// LKI copies of cards store CMC separately to avoid shenanigans with the game state visualization
|
||||
// breaking when the LKI object is changed to a different card state.
|
||||
private int lkiCMC = -1;
|
||||
@@ -314,7 +314,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|
||||
private SpellAbility[] basicLandAbilities = new SpellAbility[MagicColor.WUBRG.length];
|
||||
|
||||
private int planeswalkerAbilityActivated = 0;
|
||||
private int planeswalkerAbilityActivated;
|
||||
|
||||
private final ActivationTable numberTurnActivations = new ActivationTable();
|
||||
private final ActivationTable numberGameActivations = new ActivationTable();
|
||||
@@ -326,7 +326,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
private final Table<SpellAbility, StaticAbility, List<String>> chosenModesTurnStatic = HashBasedTable.create();
|
||||
private final Table<SpellAbility, StaticAbility, List<String>> chosenModesGameStatic = HashBasedTable.create();
|
||||
|
||||
private CombatLki combatLKI = null;
|
||||
private CombatLki combatLKI;
|
||||
|
||||
// Enumeration for CMC request types
|
||||
public enum SplitCMCMode {
|
||||
@@ -3921,11 +3921,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
updatePTforView();
|
||||
}
|
||||
|
||||
|
||||
public final void addNewPT(final Integer power, final Integer toughness, final long timestamp, final long staticId) {
|
||||
addNewPT(power, toughness, timestamp, staticId, false);
|
||||
}
|
||||
|
||||
public final void addNewPT(final Integer power, final Integer toughness, final long timestamp, final long staticId, final boolean cda) {
|
||||
(cda ? newPTCharacterDefining : newPT).put(timestamp, staticId, Pair.of(power, toughness));
|
||||
updatePTforView();
|
||||
@@ -6135,7 +6133,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
public int getCMC() {
|
||||
return getCMC(SplitCMCMode.CurrentSideCMC);
|
||||
}
|
||||
|
||||
public int getCMC(SplitCMCMode mode) {
|
||||
if (isToken() && getCopiedPermanent() == null) {
|
||||
return 0;
|
||||
|
||||
@@ -12,8 +12,8 @@ public class CardCloneStates extends ForwardingMap<CardStateName, CardState> {
|
||||
|
||||
private Map<CardStateName, CardState> dataMap = Maps.newEnumMap(CardStateName.class);
|
||||
|
||||
private Card origin = null;
|
||||
private CardTraitBase ctb = null;
|
||||
private Card origin;
|
||||
private CardTraitBase ctb;
|
||||
|
||||
public CardCloneStates(Card origin, CardTraitBase sa) {
|
||||
super();
|
||||
|
||||
@@ -27,7 +27,7 @@ import forge.card.ColorSet;
|
||||
* @author Forge
|
||||
* @version $Id$
|
||||
*/
|
||||
public class CardColor {
|
||||
public class CardColor {
|
||||
private final byte colorMask;
|
||||
public final byte getColorMask() {
|
||||
return colorMask;
|
||||
|
||||
@@ -529,7 +529,7 @@ public class CardFactory {
|
||||
}
|
||||
|
||||
public static void copySpellAbility(SpellAbility from, SpellAbility to, final Card host, final Player p, final boolean lki) {
|
||||
if (from.getTargetRestrictions() != null) {
|
||||
if (from.usesTargeting()) {
|
||||
to.setTargetRestrictions(from.getTargetRestrictions());
|
||||
}
|
||||
to.setDescription(from.getOriginalDescription());
|
||||
|
||||
@@ -81,7 +81,7 @@ public class CardState extends GameObject implements IHasSVars {
|
||||
private final CardStateView view;
|
||||
private final Card card;
|
||||
|
||||
private ReplacementEffect loyaltyRep = null;
|
||||
private ReplacementEffect loyaltyRep;
|
||||
|
||||
public CardState(Card card, CardStateName name) {
|
||||
this(card.getView().createAlternateState(name), card);
|
||||
|
||||
@@ -91,8 +91,7 @@ public final class CardUtil {
|
||||
kw = kw.substring(7);
|
||||
}
|
||||
|
||||
return !kw.startsWith("Protection") && !kw.startsWith("CantBeBlockedBy")
|
||||
&& !NON_STACKING_LIST.contains(kw);
|
||||
return !kw.startsWith("Protection") && !NON_STACKING_LIST.contains(kw);
|
||||
}
|
||||
|
||||
public static String getShortColorsString(final Iterable<String> colors) {
|
||||
|
||||
@@ -148,35 +148,37 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
private int lifeStartedThisTurnWith = startingLife;
|
||||
private final Map<Card, Integer> assignedDamage = Maps.newHashMap();
|
||||
private final Map<Card, Integer> assignedCombatDamage = Maps.newHashMap();
|
||||
private int spellsCastThisTurn = 0;
|
||||
private int spellsCastThisGame = 0;
|
||||
private int spellsCastLastTurn = 0;
|
||||
private int landsPlayedThisTurn = 0;
|
||||
private int landsPlayedLastTurn = 0;
|
||||
private int investigatedThisTurn = 0;
|
||||
private int surveilThisTurn = 0;
|
||||
private int cycledThisTurn = 0;
|
||||
private int equippedThisTurn = 0;
|
||||
private int lifeLostThisTurn = 0;
|
||||
private int lifeLostLastTurn = 0;
|
||||
private int lifeGainedThisTurn = 0;
|
||||
private int lifeGainedTimesThisTurn = 0;
|
||||
private int lifeGainedByTeamThisTurn = 0;
|
||||
private int spellsCastThisTurn;
|
||||
private int spellsCastThisGame;
|
||||
private int spellsCastLastTurn;
|
||||
private int landsPlayedThisTurn;
|
||||
private int landsPlayedLastTurn;
|
||||
private int investigatedThisTurn;
|
||||
private int surveilThisTurn;
|
||||
private int cycledThisTurn;
|
||||
private int equippedThisTurn;
|
||||
private int lifeLostThisTurn;
|
||||
private int lifeLostLastTurn;
|
||||
private int lifeGainedThisTurn;
|
||||
private int lifeGainedTimesThisTurn;
|
||||
private int lifeGainedByTeamThisTurn;
|
||||
private int numPowerSurgeLands;
|
||||
private int numLibrarySearchedOwn = 0; //The number of times this player has searched his library
|
||||
private int numLibrarySearchedOwn; //The number of times this player has searched his library
|
||||
private int numDrawnThisTurn;
|
||||
private int numDrawnThisDrawStep;
|
||||
private int numRollsThisTurn;
|
||||
private int numDiscardedThisTurn;
|
||||
private int numTokenCreatedThisTurn;
|
||||
private int numForetoldThisTurn;
|
||||
private int numCardsInHandStartedThisTurnWith;
|
||||
private int attackersDeclaredThisTurn;
|
||||
private int venturedThisTurn;
|
||||
private int maxHandSize = 7;
|
||||
private int startingHandSize = 7;
|
||||
private boolean unlimitedHandSize = false;
|
||||
private Card lastDrawnCard = null;
|
||||
private Card lastDrawnCard;
|
||||
private String namedCard = "";
|
||||
private String namedCard2 = "";
|
||||
private int numDrawnThisTurn = 0;
|
||||
private int numDrawnThisDrawStep = 0;
|
||||
private int numRollsThisTurn = 0;
|
||||
private int numDiscardedThisTurn = 0;
|
||||
private int numTokenCreatedThisTurn = 0;
|
||||
private int numForetoldThisTurn = 0;
|
||||
private int numCardsInHandStartedThisTurnWith = 0;
|
||||
|
||||
private int simultaneousDamage = 0;
|
||||
|
||||
@@ -200,13 +202,11 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
|
||||
private Table<Long, Long, KeywordsChange> changedKeywords = TreeBasedTable.create();
|
||||
private ManaPool manaPool = new ManaPool(this);
|
||||
private GameEntity mustAttackEntity = null;
|
||||
private GameEntity mustAttackEntityThisTurn = null;
|
||||
private GameEntity mustAttackEntity;
|
||||
private GameEntity mustAttackEntityThisTurn;
|
||||
private CardCollection creatureAttackedThisTurn = new CardCollection();
|
||||
private boolean activateLoyaltyAbilityThisTurn = false;
|
||||
private boolean tappedLandForManaThisTurn = false;
|
||||
private int attackersDeclaredThisTurn = 0;
|
||||
private int venturedThisTurn = 0;
|
||||
private List<Card> completedDungeons = new ArrayList<>();
|
||||
|
||||
private final Map<ZoneType, PlayerZone> zones = Maps.newEnumMap(ZoneType.class);
|
||||
@@ -236,9 +236,9 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
// The SA currently being paid for
|
||||
private Deque<SpellAbility> paidForStack = new ArrayDeque<>();
|
||||
|
||||
private Card monarchEffect = null;
|
||||
private Card blessingEffect = null;
|
||||
private Card keywordEffect = null;
|
||||
private Card monarchEffect;
|
||||
private Card blessingEffect;
|
||||
private Card keywordEffect;
|
||||
|
||||
private Map<Long, Integer> additionalVotes = Maps.newHashMap();
|
||||
private Map<Long, Integer> additionalOptionalVotes = Maps.newHashMap();
|
||||
|
||||
@@ -39,7 +39,7 @@ public final class AbilitySub extends SpellAbility implements java.io.Serializab
|
||||
/** Constant <code>serialVersionUID=4650634415821733134L</code>. */
|
||||
private static final long serialVersionUID = 4650634415821733134L;
|
||||
|
||||
private SpellAbility parent = null;
|
||||
private SpellAbility parent;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
|
||||
@@ -110,20 +110,20 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
// choices for constructor isPermanent argument
|
||||
private String originalDescription = "", description = "";
|
||||
private String originalStackDescription = "", stackDescription = "";
|
||||
private ManaCost multiKickerManaCost = null;
|
||||
private Player activatingPlayer = null;
|
||||
private Player targetingPlayer = null;
|
||||
private Pair<Long, Player> controlledByPlayer = null;
|
||||
private ManaCostBeingPaid manaCostBeingPaid = null;
|
||||
private ManaCost multiKickerManaCost;
|
||||
private Player activatingPlayer;
|
||||
private Player targetingPlayer;
|
||||
private Pair<Long, Player> controlledByPlayer;
|
||||
private ManaCostBeingPaid manaCostBeingPaid;
|
||||
private boolean spentPhyrexian = false;
|
||||
|
||||
private SpellAbility grantorOriginal = null;
|
||||
private StaticAbility grantorStatic = null;
|
||||
private SpellAbility grantorOriginal;
|
||||
private StaticAbility grantorStatic;
|
||||
|
||||
private CardCollection splicedCards = null;
|
||||
|
||||
private boolean basicSpell = true;
|
||||
private Trigger triggerObj = null;
|
||||
private Trigger triggerObj;
|
||||
private boolean optionalTrigger = false;
|
||||
private ReplacementEffect replacementEffect = null;
|
||||
private int sourceTrigger = -1;
|
||||
@@ -141,7 +141,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
private Cost payCosts;
|
||||
private SpellAbilityRestriction restrictions = new SpellAbilityRestriction();
|
||||
private SpellAbilityCondition conditions = new SpellAbilityCondition();
|
||||
private AbilitySub subAbility = null;
|
||||
private AbilitySub subAbility;
|
||||
|
||||
private Map<String, SpellAbility> additionalAbilities = Maps.newHashMap();
|
||||
private Map<String, List<AbilitySub>> additionalAbilityLists = Maps.newHashMap();
|
||||
@@ -160,10 +160,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
|
||||
private List<AbilitySub> chosenList = null;
|
||||
private CardCollection tappedForConvoke = new CardCollection();
|
||||
private Card sacrificedAsOffering = null;
|
||||
private Card sacrificedAsEmerge = null;
|
||||
private Card sacrificedAsOffering;
|
||||
private Card sacrificedAsEmerge;
|
||||
|
||||
private AbilityManaPart manaPart = null;
|
||||
private AbilityManaPart manaPart;
|
||||
|
||||
private boolean undoable;
|
||||
|
||||
@@ -171,36 +171,35 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
private boolean mayChooseNewTargets = false;
|
||||
|
||||
private EnumSet<OptionalCost> optionalCosts = EnumSet.noneOf(OptionalCost.class);
|
||||
private TargetRestrictions targetRestrictions = null;
|
||||
private TargetRestrictions targetRestrictions;
|
||||
private TargetChoices targetChosen = new TargetChoices();
|
||||
|
||||
private Integer dividedValue = null;
|
||||
|
||||
private SpellAbilityView view;
|
||||
|
||||
private StaticAbility mayPlay = null;
|
||||
private StaticAbility mayPlay;
|
||||
|
||||
private CardCollection lastStateBattlefield = null;
|
||||
private CardCollection lastStateGraveyard = null;
|
||||
|
||||
private CardCollection rollbackEffects = new CardCollection();
|
||||
|
||||
private CardDamageMap damageMap = null;
|
||||
private CardDamageMap preventMap = null;
|
||||
private GameEntityCounterTable counterTable = null;
|
||||
private CardZoneTable changeZoneTable = null;
|
||||
private CardDamageMap damageMap;
|
||||
private CardDamageMap preventMap;
|
||||
private GameEntityCounterTable counterTable;
|
||||
private CardZoneTable changeZoneTable;
|
||||
|
||||
public CardCollection getLastStateBattlefield() {
|
||||
return lastStateBattlefield;
|
||||
}
|
||||
|
||||
public void setLastStateBattlefield(final CardCollectionView lastStateBattlefield) {
|
||||
this.lastStateBattlefield = new CardCollection(lastStateBattlefield);
|
||||
}
|
||||
|
||||
public CardCollection getLastStateGraveyard() {
|
||||
return lastStateGraveyard;
|
||||
}
|
||||
|
||||
public void setLastStateGraveyard(final CardCollectionView lastStateGraveyard) {
|
||||
this.lastStateGraveyard = new CardCollection(lastStateGraveyard);
|
||||
}
|
||||
@@ -1876,7 +1875,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
final List<GameObject> targets = Lists.newArrayList();
|
||||
SpellAbility child = getParent();
|
||||
while (child != null) {
|
||||
if (child.getTargetRestrictions() != null) {
|
||||
if (child.usesTargeting()) {
|
||||
Iterables.addAll(targets, child.getTargets());
|
||||
}
|
||||
child = child.getParent();
|
||||
|
||||
@@ -326,7 +326,7 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
|
||||
}
|
||||
|
||||
while (compare != null && sub != null) {
|
||||
TargetChoices choices = compare.getTargetRestrictions() != null ? compare.getTargets() : null;
|
||||
TargetChoices choices = compare.usesTargeting() ? compare.getTargets() : null;
|
||||
|
||||
if (choices != null && !choices.equals(sub.getTargetChoices())) {
|
||||
return false;
|
||||
|
||||
@@ -45,7 +45,7 @@ import forge.util.collect.FCollection;
|
||||
*/
|
||||
public class TargetChoices extends ForwardingList<GameObject> implements Cloneable {
|
||||
|
||||
private final FCollection<GameObject> targets = new FCollection<GameObject>();
|
||||
private final FCollection<GameObject> targets = new FCollection<>();
|
||||
|
||||
private final Map<GameObject, Integer> dividedMap = Maps.newHashMap();
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ public abstract class Trigger extends TriggerReplacementBase {
|
||||
|
||||
private Set<PhaseType> validPhases;
|
||||
|
||||
private SpellAbility spawningAbility = null;
|
||||
private SpellAbility spawningAbility;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
|
||||
@@ -79,7 +79,7 @@ public class TriggerAbilityTriggered extends Trigger {
|
||||
if (hasParam("ValidCause")) {
|
||||
boolean match = false;
|
||||
for (Card cause : causes) {
|
||||
if(matchesValidParam("ValidCause", cause)) {
|
||||
if (matchesValidParam("ValidCause", cause)) {
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1000,6 +1000,12 @@ public final class CMatchUI
|
||||
public void showPromptMessage(final PlayerView playerView, final String message) {
|
||||
cPrompt.setMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showCardPromptMessage(PlayerView playerView, String message, CardView card) {
|
||||
cPrompt.setMessage(message, card);
|
||||
}
|
||||
|
||||
// no override for now
|
||||
public void showPromptMessage(final PlayerView playerView, final String message, final CardView card ) {
|
||||
cPrompt.setMessage(message,card);
|
||||
|
||||
@@ -339,7 +339,7 @@ public class Forge implements ApplicationListener {
|
||||
|
||||
protected void afterDbLoaded() {
|
||||
//init here to fix crash if the assets are missing
|
||||
transitionTexture = new Texture(GuiBase.isAndroid() ? Gdx.files.internal("fallback_skin").child("transition.png") : Gdx.files.classpath("fallback_skin").child("transition.png"));
|
||||
transitionTexture = new Texture(Gdx.files.classpath("fallback_skin").child("transition.png"));
|
||||
|
||||
|
||||
destroyThis = false; //Allow back()
|
||||
|
||||
@@ -38,7 +38,6 @@ import forge.toolbox.FLabel;
|
||||
import forge.toolbox.GuiChoose;
|
||||
import forge.util.Callback;
|
||||
import forge.util.ItemPool;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.Utils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
|
||||
|
||||
@@ -200,7 +200,6 @@ public class PlayerStatisticScene extends UIScene {
|
||||
back.getLabel().setText(Forge.getLocalizer().getMessage("lblBack"));
|
||||
ScrollPane scrollPane = ui.findActor("enemies");
|
||||
scrollPane.setActor(enemiesGroup);
|
||||
enemiesGroup.setFillParent(true);
|
||||
this.init = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public class SaveLoadScene extends UIScene {
|
||||
|
||||
|
||||
private TextButton addSaveSlot(String name, int i) {
|
||||
layout.add(Controls.newLabel(name)).align(Align.left).pad(4, 10, 4, 15);
|
||||
layout.add(Controls.newLabel(name)).align(Align.left).pad(2, 5, 2, 10);
|
||||
TextButton button = Controls.newTextButton("...");
|
||||
button.addListener(new ClickListener() {
|
||||
@Override
|
||||
@@ -213,7 +213,6 @@ public class SaveLoadScene extends UIScene {
|
||||
super.resLoaded();
|
||||
if (!this.init) {
|
||||
layout = new Table();
|
||||
layout.setFillParent(true);
|
||||
stage.addActor(layout);
|
||||
dialog = Controls.newDialog(Forge.getLocalizer().getMessage("lblSave"));
|
||||
textInput = Controls.newTextField("");
|
||||
@@ -257,9 +256,8 @@ public class SaveLoadScene extends UIScene {
|
||||
previewImage = ui.findActor("preview");
|
||||
previewBorder = ui.findActor("preview_border");
|
||||
header = Controls.newLabel(Forge.getLocalizer().getMessage("lblSave"));
|
||||
header.setHeight(header.getHeight() * 2);
|
||||
header.setAlignment(Align.center);
|
||||
layout.add(header).pad(2).colspan(4).align(Align.center).expand();
|
||||
layout.add(header).pad(2).colspan(4).align(Align.center).expandX();
|
||||
layout.row();
|
||||
autoSave = addSaveSlot(Forge.getLocalizer().getMessage("lblAutoSave"), WorldSave.AUTO_SAVE_SLOT);
|
||||
quickSave = addSaveSlot(Forge.getLocalizer().getMessage("lblQuickSave"), WorldSave.QUICK_SAVE_SLOT);
|
||||
|
||||
@@ -74,8 +74,8 @@ public class AssetsDownloader {
|
||||
}
|
||||
return;
|
||||
}
|
||||
SOptionPane.showMessageDialog("Could not download update. " +
|
||||
"Press OK to proceed without update.", "Update Failed");
|
||||
SOptionPane.showOptionDialog("Could not download update. " +
|
||||
"Press OK to proceed without update.", "Update Failed", null, ImmutableList.of("Ok"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,9 +112,12 @@ public class AssetsDownloader {
|
||||
else {
|
||||
message += "You cannot start the app since you haven't previously downloaded these files.";
|
||||
}
|
||||
SOptionPane.showMessageDialog(message, "No Internet Connection");
|
||||
if (!canIgnoreDownload) {
|
||||
Forge.exitAnimation(false); //exit if can't ignore download
|
||||
switch (SOptionPane.showOptionDialog(message, "No Internet Connection", null, ImmutableList.of("Ok"))) {
|
||||
default: {
|
||||
if (!canIgnoreDownload) {
|
||||
Forge.exitAnimation(false); //exit if can't ignore download
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ public class FSkin {
|
||||
{
|
||||
if (!dir.exists() || !dir.isDirectory()) {
|
||||
//if skins directory doesn't exist, point to internal assets/skin directory instead for the sake of the splash screen
|
||||
preferredDir = GuiBase.isAndroid() ? Gdx.files.internal("fallback_skin") : Gdx.files.classpath("fallback_skin");
|
||||
preferredDir = Gdx.files.classpath("fallback_skin");
|
||||
}
|
||||
else {
|
||||
if (splashScreen != null) {
|
||||
|
||||
@@ -97,9 +97,7 @@ public class SplashScreen extends FContainer {
|
||||
private float progress = 0;
|
||||
private boolean finished, openAdventure;
|
||||
//for transition image only...
|
||||
TextureRegion transition_bg = new TextureRegion(new Texture(GuiBase.isAndroid()
|
||||
? Gdx.files.internal("fallback_skin").child("title_bg_lq.png")
|
||||
: Gdx.files.classpath("fallback_skin").child("title_bg_lq.png")));
|
||||
TextureRegion transition_bg = new TextureRegion(new Texture(Gdx.files.classpath("fallback_skin").child("title_bg_lq.png")));
|
||||
|
||||
public void drawBackground(Graphics g) {
|
||||
float percentage = progress / DURATION;
|
||||
|
||||
@@ -12,6 +12,7 @@ import forge.deck.Deck;
|
||||
import forge.game.player.Player;
|
||||
import forge.item.IPaperCard;
|
||||
import forge.screens.TransitionScreen;
|
||||
import forge.util.collect.FCollection;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
@@ -157,13 +158,18 @@ public class MatchController extends AbstractGuiGame {
|
||||
public void openView(final TrackableCollection<PlayerView> myPlayers) {
|
||||
final boolean noHumans = !hasLocalPlayers();
|
||||
|
||||
final FCollectionView<PlayerView> allPlayers = getGameView().getPlayers();
|
||||
FCollectionView<PlayerView> players = getGameView().getPlayers();
|
||||
if (players.size() == 2 && myPlayers != null && myPlayers.size() == 1 && myPlayers.get(0).equals(players.get(1))) {
|
||||
players = new FCollection<>(new PlayerView[]{players.get(1), players.get(0)});
|
||||
}
|
||||
final List<VPlayerPanel> playerPanels = new ArrayList<>();
|
||||
for (final PlayerView p : allPlayers) {
|
||||
boolean init = false;
|
||||
for (final PlayerView p : players) {
|
||||
final boolean isLocal = isLocalPlayer(p);
|
||||
final VPlayerPanel playerPanel = new VPlayerPanel(p, isLocal || noHumans, allPlayers.size());
|
||||
if (isLocal && !playerPanels.isEmpty()) {
|
||||
final VPlayerPanel playerPanel = new VPlayerPanel(p, isLocal || noHumans, players.size());
|
||||
if (isLocal && !init) {
|
||||
playerPanels.add(0, playerPanel); //ensure local player always first among player panels
|
||||
init = true;
|
||||
}
|
||||
else {
|
||||
playerPanels.add(playerPanel);
|
||||
@@ -203,7 +209,7 @@ public class MatchController extends AbstractGuiGame {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showPromptMessage(final PlayerView player, final String message, final CardView card) {
|
||||
public void showCardPromptMessage(final PlayerView player, final String message, final CardView card) {
|
||||
view.getPrompt(player).setMessage(message, card);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import forge.gui.FThreads;
|
||||
import forge.gui.interfaces.IButton;
|
||||
import forge.model.FModel;
|
||||
import forge.screens.LoadingOverlay;
|
||||
import forge.screens.home.HomeScreen;
|
||||
import forge.toolbox.FEvent;
|
||||
import forge.toolbox.FEvent.FEventHandler;
|
||||
import forge.toolbox.FLabel;
|
||||
|
||||
@@ -112,7 +112,7 @@ public class FCardPanel extends FDisplayObject {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!animate || MatchController.instance.isGameFast() || MatchController.instance.getGameView().isMatchOver()) {
|
||||
if (!animate || MatchController.instance.isGameFast() || (MatchController.instance.getGameView() != null && MatchController.instance.getGameView().isMatchOver())) {
|
||||
//don't animate if game is fast or match is over
|
||||
rotateTransform(g, x, y, w, h, edgeOffset, false);
|
||||
card.updateNeedsTapAnimation(false);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#Add one announcement per line
|
||||
Get in the discord if you aren't yet. https://discord.gg/3v9JCVr
|
||||
All new Alchemy: Innistrad cards (Y22) have been implemented in Forge. Happy brewing!
|
||||
Support for rebalanced Arena cards (separate from the original implementations) is implemented.
|
||||
It's now possible to choose to play Constructed matches in the best of 1, 3, and 5 formats.
|
||||
Kamigawa: Neon Dynasty (NEO) and Kamigawa: Neon Dynasty Commander (NEC) are fully implemented.
|
||||
Mobile Forge and Mobile backports now integrate Adventure Mode, an overworld adventure in the spirit of Shandalar.
|
||||
*** Android 7 & 8 support is now deprecated. Support will be dropped in an upcoming release. ***
|
||||
|
||||
@@ -2,6 +2,6 @@ Name:Swallow Whole
|
||||
ManaCost:W
|
||||
Types:Sorcery
|
||||
A:SP$ ChangeZone | Cost$ W tapXType<1/Creature> | ValidTgts$ Creature.tapped | TgtPrompt$ Select target tapped creature | Origin$ Battlefield | Destination$ Exile | SubAbility$ DBPutCounter | SpellDescription$ As an additional cost to cast this spell, tap an untapped creature you control. Exile target tapped creature.
|
||||
SVar:DBPutCounter:DB$ PutCounter | Defined$ Tapped | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a +1/+1 counter on the creature tapped to cast this spell.
|
||||
SVar:DBPutCounter:DB$ PutCounter | Defined$ Tapped | CounterType$ P1P1 | SpellDescription$ Put a +1/+1 counter on the creature tapped to cast this spell.
|
||||
DeckHas:Ability$Counters
|
||||
Oracle:As an additional cost to cast this spell, tap an untapped creature you control.\nExile target tapped creature. Put a +1/+1 counter on the creature tapped to pay this spell's additional cost.
|
||||
|
||||
@@ -34,3 +34,6 @@ ScryfallCode=PPRO
|
||||
2035 U Saw it Coming @Anato Finnstark
|
||||
2036 M Kaya the Inexorable @Jason A. Engle
|
||||
2037 R Faceless Haven @Pablo Mendoza
|
||||
2038 M Grand Master of Flowers
|
||||
2039 R Adult Gold Dragon
|
||||
2040 U Krydle of Baldur's Gate
|
||||
|
||||
@@ -4,4 +4,4 @@ Order:108
|
||||
Subtype:Custom
|
||||
Type:Casual
|
||||
Rarities:L, C
|
||||
Banned:Arcum's Astrolabe; Atog; Bonder's Ornament; Chatterstorm; Cloud of Faeries; Cloudpost; Cranial Plating; Daze; Expedition Map; Empty the Warrens; Fall from Favor; Frantic Search; Gitaxian Probe; Grapeshot; Gush; High Tide; Hymn to Tourach; Invigorate; Mystic Sanctuary; Peregrine Drake; Prophetic Prism; Sinkhole; Sojourner's Companion; Temporal Fissure; Treasure Cruise
|
||||
Banned:Arcum's Astrolabe; Atog; Bonder's Ornament; Chatterstorm; Cloud of Faeries; Cloudpost; Cranial Plating; Daze; Disciple of the Vault; Empty the Warrens; Fall from Favor; Frantic Search; Galvanic Relay; Gitaxian Probe; Grapeshot; Gush; High Tide; Hymn to Tourach; Invigorate; Mystic Sanctuary; Peregrine Drake; Prophetic Prism; Sinkhole; Sojourner's Companion; Temporal Fissure; Treasure Cruise
|
||||
|
||||
@@ -4,4 +4,4 @@ Order:103
|
||||
Subtype:Modern
|
||||
Type:Sanctioned
|
||||
Sets:8ED, MRD, DST, 5DN, CHK, BOK, SOK, 9ED, RAV, GPT, DIS, CSP, TSP, TSB, TSR, PLC, FUT, 10E, LRW, EVE, SHM, MOR, ALA, CFX, ARB, M10, ZEN, WWK, ROE, M11, SOM, MBS, NPH, M12, ISD, DKA, AVR, M13, RTR, GTC, DGM, M14, THS, BNG, JOU, M15, KTK, FRF, DTK, MMA, MM2, MM3, ORI, BFZ, OGW, SOI, EMN, KLD, AER, AKH, W16, W17, HOU, XLN, RIX, DOM, M19, G18, GRN, RNA, WAR, MH1, M20, ELD, THB, IKO, M21, ZNR, KHM, STX, MH2, AFR, MID, VOW, NEO
|
||||
Banned:Ancient Den; Arcum's Astrolabe; Birthing Pod; Blazing Shoal; Bridge from Below; Chrome Mox; Cloudpost; Dark Depths; Deathrite Shaman; Dig Through Time; Dread Return; Eye of Ugin; Faithless Looting; Field of the Dead; Gitaxian Probe; Glimpse of Nature; Golgari Grave-Troll; Great Furnace; Green Sun's Zenith; Hogaak, Arisen Necropolis; Hypergenesis; Krark-Clan Ironworks; Mental Misstep; Mox Opal; Mycosynth Lattice; Mystic Sanctuary; Oko, Thief of Crowns; Once Upon A Time; Ponder; Preordain; Punishing Fire; Rite of Flame; Seat of the Synod; Second Sunrise; Seething Song; Sensei's Divining Top; Simian Spirit Guide; Skullclamp; Splinter Twin; Summer Bloom; Tibalt's Trickery; Treasure Cruise; Tree of Tales; Umezawa's Jitte; Uro, Titan of Nature's Wrath; Vault of Whispers
|
||||
Banned:Ancient Den; Arcum's Astrolabe; Birthing Pod; Blazing Shoal; Bridge from Below; Chrome Mox; Cloudpost; Dark Depths; Deathrite Shaman; Dig Through Time; Dread Return; Eye of Ugin; Faithless Looting; Field of the Dead; Gitaxian Probe; Glimpse of Nature; Golgari Grave-Troll; Great Furnace; Green Sun's Zenith; Hogaak, Arisen Necropolis; Hypergenesis; Krark-Clan Ironworks; Lurrus of the Dream-Den; Mental Misstep; Mox Opal; Mycosynth Lattice; Mystic Sanctuary; Oko, Thief of Crowns; Once Upon A Time; Ponder; Preordain; Punishing Fire; Rite of Flame; Seat of the Synod; Second Sunrise; Seething Song; Sensei's Divining Top; Simian Spirit Guide; Skullclamp; Splinter Twin; Summer Bloom; Tibalt's Trickery; Treasure Cruise; Tree of Tales; Umezawa's Jitte; Uro, Titan of Nature's Wrath; Vault of Whispers
|
||||
|
||||
@@ -4,4 +4,4 @@ Order:102
|
||||
Subtype:Pioneer
|
||||
Type:Sanctioned
|
||||
Sets:RTR, GTC, DGM, M14, THS, BNG, JOU, M15, KTK, FRF, DTK, ORI, BFZ, OGW, SOI, EMN, KLD, AER, AKH, HOU, XLN, RIX, DOM, M19, G18, GRN, RNA, WAR, M20, ELD, THB, IKO, M21, ZNR, KHM, STX, AFR, MID, VOW, NEO
|
||||
Banned:Balustrade Spy; Bloodstained Mire; Felidar Guardian; Field of the Dead; Flooded Strand; Inverter of Truth; Kethis, the Hidden Hand; Leyline of Abundance; Nexus of Fate; Oko, Thief of Crowns; Once Upon a Time; Polluted Delta; Smuggler's Copter; Teferi, Time Raveler; Undercity Informer; Underworld Breach; Uro, Titan of Nature's Wrath; Veil of Summer; Walking Ballista; Wilderness Reclamation; Windswept Heath; Wooded Foothills
|
||||
Banned:Balustrade Spy; Bloodstained Mire; Felidar Guardian; Field of the Dead; Flooded Strand; Inverter of Truth; Kethis, the Hidden Hand; Leyline of Abundance; Lurrus of the Dream-Den; Nexus of Fate; Oko, Thief of Crowns; Once Upon a Time; Polluted Delta; Smuggler's Copter; Teferi, Time Raveler; Undercity Informer; Underworld Breach; Uro, Titan of Nature's Wrath; Veil of Summer; Walking Ballista; Wilderness Reclamation; Windswept Heath; Wooded Foothills
|
||||
|
||||
@@ -2690,9 +2690,9 @@ lblUseFormatFilter=Wähle ein Format für die Deckliste
|
||||
lblIgnoreBnR=Importiere auch gebannte und eingeschränkte Karten
|
||||
ttIgnoreBnR=Wenn aktiviert, erden auch gebannte oder eingeschränkte Karten ins Deck importiert.
|
||||
nlIgnoreBnR=Warnung: Das Deck kann bei eingeschalteter Deckkonkonformität nicht spielbar sein.
|
||||
lblUseSmartCardArt=Enable Smart Card Art Selection
|
||||
ttUseSmartCardArtNoDeck=If enabled, the art of cards will be automatically chosen to match up with other cards in the Decklist.
|
||||
ttUseSmartCardArtWithDeck=If enabled, the art of cards will be automatically chosen to match up with other cards in the Decklist, and in current Deck.
|
||||
lblUseSmartCardArt=Aktiviere intelligente Kartenbildwahl
|
||||
ttUseSmartCardArtNoDeck=Wenn aktiviert, wird das Kartenbild automatisch passend zu anderen Karten in der Deckliste gewählt.
|
||||
ttUseSmartCardArtWithDeck=Wenn aktiviert, wird das Kartenbild automatisch passend zu anderen Karten in der Deckliste und dem aktuellen Deck gewählt.
|
||||
lblExtraOptions=Zeige Optionen
|
||||
lblHideOptions=Verstecke Optionen
|
||||
lblCardPreview=Karten-Vorschau
|
||||
@@ -2875,6 +2875,6 @@ lblAbort=Abbrechen
|
||||
lblNameYourSaveFile=Nennen Sie Ihre neue Save-Datei
|
||||
lblEdit=Bearbeiten
|
||||
lblWinProper=Sieg
|
||||
lblLossProper=Verlust
|
||||
lblWinLossRatio=Verlustquote gewinnen
|
||||
lblLossProper=Niederlage
|
||||
lblWinLossRatio=Sieg/Niederlage-Quote
|
||||
lblHeal=Heilen
|
||||
@@ -6,6 +6,10 @@ Budget Modern Metagame | https://downloads.cardforge.org/decks/budgetmodernmetag
|
||||
Building on a Budget | https://downloads.cardforge.org/decks/buildingonabudget.zip
|
||||
Card Preview | https://downloads.cardforge.org/decks/cardpreview.zip
|
||||
Community Cup | https://downloads.cardforge.org/decks/communitycup.zip
|
||||
Current Alchemy Metagame | https://downloads.cardforge.org/decks/currentalchemymetagame.zip
|
||||
Current Bo1 Alchemy Metagame | https://downloads.cardforge.org/decks/currentbo1alchemymetagame.zip
|
||||
Current Bo1 Historic Metagame | https://downloads.cardforge.org/decks/currentbo1historicmetagame.zip
|
||||
Current Bo1 Standard Metagame | https://downloads.cardforge.org/decks/currentbo1standardmetagame.zip
|
||||
Current Historic Metagame | https://downloads.cardforge.org/decks/currenthistoricmetagame.zip
|
||||
Current Legacy Metagame | https://downloads.cardforge.org/decks/currentlegacymetagame.zip
|
||||
Current Modern Metagame | https://downloads.cardforge.org/decks/currentmodernmetagame.zip
|
||||
|
||||
17
forge-gui/res/puzzle/PS_NEO1.pzl
Normal file
17
forge-gui/res/puzzle/PS_NEO1.pzl
Normal file
@@ -0,0 +1,17 @@
|
||||
[metadata]
|
||||
Name:Possibility Storm - Kamigawa: Neon Dynasty #01
|
||||
URL:https://i2.wp.com/www.possibilitystorm.com/wp-content/uploads/2022/01/latest-3-scaled.jpg?ssl=1
|
||||
Goal:Win
|
||||
Turns:1
|
||||
Difficulty:Uncommon
|
||||
Description:Win this turn. The top card of your library is Bloodthirsty Adversary. Assume any other cards you could access are irrelevant to the solution. Good luck!
|
||||
[state]
|
||||
humanlife=20
|
||||
ailife=11
|
||||
turn=1
|
||||
activeplayer=human
|
||||
activephase=MAIN1
|
||||
humanhand=Hero's Downfall;The Meathook Massacre;Kappa Tech-Wrecker
|
||||
humanlibrary=Bloodthirsty Adversary;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt
|
||||
humanbattlefield=Bookwurm;Atsushi, the Blazing Sky;Hidetsugu, Devouring Chaos;Swamp;Swamp;Swamp;Swamp;Mountain;Mountain;Mountain;Forest
|
||||
aibattlefield=Angel of Destiny;Henrika Domnathi|Transformed;Vengeful Reaper
|
||||
17
forge-gui/res/puzzle/PS_NEO2.pzl
Normal file
17
forge-gui/res/puzzle/PS_NEO2.pzl
Normal file
@@ -0,0 +1,17 @@
|
||||
[metadata]
|
||||
Name:Possibility Storm - Kamigawa: Neon Dynasty #02
|
||||
URL:https://i0.wp.com/www.possibilitystorm.com/wp-content/uploads/2022/02/latest-scaled.jpg?ssl=1
|
||||
Goal:Win
|
||||
Turns:1
|
||||
Difficulty:Mythic
|
||||
Description:Win this turn. Start in your first main phase (Saga has resolved). Assume opponent has no mana.
|
||||
[state]
|
||||
humanlife=4
|
||||
ailife=8
|
||||
turn=1
|
||||
activeplayer=human
|
||||
activephase=MAIN1
|
||||
humanhand=Jugan Defends the Temple;Satsuki, the Living Lore;The Kami War;Fall of the Impostor;Croaking Counterpart
|
||||
humanbattlefield=Battle for Bretagard|Counters:LORE=2;Tuktuk Rubblefort;Overgrown Farmland;Overgrown Farmland;Overgrown Farmland;Overgrown Farmland;Vineglimmer Snarl;Vineglimmer Snarl;Vineglimmer Snarl;The World Tree
|
||||
aibattlefield=Flamescroll Celebrant;Colossal Skyturtle;Weaver of Harmony;Greater Tanuki
|
||||
humanprecast=Battle for Bretagard:TrigToken1;Battle for Bretagard:TrigToken2
|
||||
@@ -123,10 +123,10 @@ public abstract class InputBase implements java.io.Serializable, Input {
|
||||
controller.getGui().showPromptMessage(getOwner(), message);
|
||||
}
|
||||
protected final void showMessage(final String message, final SpellAbilityView sav) {
|
||||
controller.getGui().showPromptMessage(getOwner(), message, sav.getHostCard());
|
||||
controller.getGui().showCardPromptMessage(getOwner(), message, sav.getHostCard());
|
||||
}
|
||||
protected final void showMessage(final String message, final CardView card) {
|
||||
controller.getGui().showPromptMessage(getOwner(), message, card);
|
||||
controller.getGui().showCardPromptMessage(getOwner(), message, card);
|
||||
}
|
||||
|
||||
protected String getTurnPhasePriorityMessage(final Game game) {
|
||||
|
||||
@@ -5,7 +5,6 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectStreamClass;
|
||||
import java.io.StreamCorruptedException;
|
||||
|
||||
import io.netty.handler.codec.serialization.ClassResolver;
|
||||
|
||||
@@ -23,16 +22,19 @@ public class CObjectInputStream extends ObjectInputStream {
|
||||
if (type < 0) {
|
||||
throw new EOFException();
|
||||
} else {
|
||||
switch(type) {
|
||||
case 0:
|
||||
return super.readClassDescriptor();
|
||||
case 1:
|
||||
String className = readUTF();
|
||||
Class<?> clazz = classResolver.resolve(className);
|
||||
return ObjectStreamClass.lookupAny(clazz);
|
||||
default:
|
||||
throw new StreamCorruptedException("Unexpected class descriptor type: " + type);
|
||||
ObjectStreamClass resultClassDescriptor = super.readClassDescriptor();
|
||||
Class localClass;
|
||||
try {
|
||||
localClass = Class.forName(resultClassDescriptor.getName());
|
||||
} catch (ClassNotFoundException e) {
|
||||
System.err.println("[Class Not Found Exception]\nNo local class for " + resultClassDescriptor.getName());
|
||||
return resultClassDescriptor;
|
||||
}
|
||||
ObjectStreamClass localClassDescriptor = ObjectStreamClass.lookupAny(localClass);
|
||||
if (localClassDescriptor != null && type == 1) {
|
||||
resultClassDescriptor = localClassDescriptor; // Use local class descriptor for deserialization by default
|
||||
}
|
||||
return resultClassDescriptor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,21 +32,22 @@ public enum ProtocolMethod {
|
||||
// Server -> Client
|
||||
setGameView (Mode.SERVER, Void.TYPE, GameView.class),
|
||||
openView (Mode.SERVER, Void.TYPE, TrackableCollection/*PlayerView*/.class),
|
||||
afterGameEnd (Mode.SERVER),
|
||||
showCombat (Mode.SERVER),
|
||||
afterGameEnd (Mode.SERVER, Void.TYPE),
|
||||
showCombat (Mode.SERVER, Void.TYPE),
|
||||
showPromptMessage (Mode.SERVER, Void.TYPE, PlayerView.class, String.class),
|
||||
showCardPromptMessage (Mode.SERVER, Void.TYPE, PlayerView.class, String.class, CardView.class),
|
||||
updateButtons (Mode.SERVER, Void.TYPE, PlayerView.class, String.class, String.class, Boolean.TYPE, Boolean.TYPE, Boolean.TYPE),
|
||||
flashIncorrectAction(Mode.SERVER),
|
||||
alertUser (Mode.SERVER),
|
||||
flashIncorrectAction(Mode.SERVER, Void.TYPE),
|
||||
alertUser (Mode.SERVER, Void.TYPE),
|
||||
updatePhase (Mode.SERVER, Void.TYPE, Boolean.TYPE),
|
||||
updateTurn (Mode.SERVER, Void.TYPE, PlayerView.class),
|
||||
updatePlayerControl (Mode.SERVER),
|
||||
enableOverlay (Mode.SERVER),
|
||||
disableOverlay (Mode.SERVER),
|
||||
finishGame (Mode.SERVER),
|
||||
updatePlayerControl (Mode.SERVER, Void.TYPE),
|
||||
enableOverlay (Mode.SERVER, Void.TYPE),
|
||||
disableOverlay (Mode.SERVER, Void.TYPE),
|
||||
finishGame (Mode.SERVER, Void.TYPE),
|
||||
showManaPool (Mode.SERVER, Void.TYPE, PlayerView.class),
|
||||
hideManaPool (Mode.SERVER, Void.TYPE, PlayerView.class),
|
||||
updateStack (Mode.SERVER),
|
||||
updateStack (Mode.SERVER, Void.TYPE),
|
||||
updateZones (Mode.SERVER, Void.TYPE, Iterable/*PlayerZoneUpdate*/.class),
|
||||
tempShowZones (Mode.SERVER, Iterable/*PlayerZoneUpdate*/.class, PlayerView.class, Iterable/*PlayerZoneUpdate*/.class),
|
||||
hideZones (Mode.SERVER, Void.TYPE, PlayerView.class, Iterable/*PlayerZoneUpdate*/.class),
|
||||
@@ -71,11 +72,11 @@ public enum ProtocolMethod {
|
||||
manipulateCardList (Mode.SERVER, List.class, String.class, Iterable.class, Iterable.class, Boolean.TYPE, Boolean.TYPE, Boolean.TYPE),
|
||||
setCard (Mode.SERVER, Void.TYPE, CardView.class),
|
||||
setSelectables (Mode.SERVER, Void.TYPE, Iterable/*CardView*/.class),
|
||||
clearSelectables (Mode.SERVER),
|
||||
refreshField (Mode.SERVER),
|
||||
clearSelectables (Mode.SERVER, Void.TYPE),
|
||||
refreshField (Mode.SERVER, Void.TYPE),
|
||||
// TODO case "setPlayerAvatar":
|
||||
openZones (Mode.SERVER, PlayerZoneUpdates.class, PlayerView.class, Collection/*ZoneType*/.class, Map/*PlayerView,Object*/.class),
|
||||
restoreOldZones (Mode.SERVER, Void.TYPE, PlayerView.class, PlayerZoneUpdates.class),
|
||||
restoreOldZones (Mode.SERVER, Void.TYPE, PlayerView.class, Iterable/*PlayerZoneUpdates*/.class),
|
||||
isUiSetToSkipPhase (Mode.SERVER, Boolean.TYPE, PlayerView.class, PhaseType.class),
|
||||
setRememberedActions(Mode.SERVER, Void.TYPE),
|
||||
nextRememberedAction(Mode.SERVER, Void.TYPE),
|
||||
@@ -85,18 +86,18 @@ public enum ProtocolMethod {
|
||||
// which client and server wait for one another's response and block
|
||||
// the threads that're supposed to give that response
|
||||
useMana (Mode.CLIENT, Void.TYPE, Byte.TYPE),
|
||||
undoLastAction (Mode.CLIENT, Void.TYPE, Boolean.TYPE),
|
||||
undoLastAction (Mode.CLIENT, Void.TYPE),
|
||||
selectPlayer (Mode.CLIENT, Void.TYPE, PlayerView.class, ITriggerEvent.class),
|
||||
selectCard (Mode.CLIENT, Void.TYPE, CardView.class, List.class, ITriggerEvent.class),
|
||||
selectButtonOk (Mode.CLIENT),
|
||||
selectButtonCancel (Mode.CLIENT),
|
||||
selectButtonOk (Mode.CLIENT, Void.TYPE),
|
||||
selectButtonCancel (Mode.CLIENT, Void.TYPE),
|
||||
selectAbility (Mode.CLIENT, Void.TYPE, SpellAbilityView.class),
|
||||
passPriorityUntilEndOfTurn(Mode.CLIENT),
|
||||
passPriority (Mode.CLIENT),
|
||||
passPriorityUntilEndOfTurn(Mode.CLIENT, Void.TYPE),
|
||||
passPriority (Mode.CLIENT, Void.TYPE),
|
||||
nextGameDecision (Mode.CLIENT, Void.TYPE, NextGameDecision.class),
|
||||
getActivateDescription (Mode.CLIENT, String.class, CardView.class),
|
||||
concede (Mode.CLIENT),
|
||||
alphaStrike (Mode.CLIENT),
|
||||
concede (Mode.CLIENT, Void.TYPE),
|
||||
alphaStrike (Mode.CLIENT, Void.TYPE),
|
||||
reorderHand (Mode.CLIENT, Void.TYPE, CardView.class, Integer.TYPE);
|
||||
|
||||
private enum Mode {
|
||||
@@ -161,13 +162,17 @@ public enum ProtocolMethod {
|
||||
if(!GuiBase.hasPropertyConfig())
|
||||
return; //if the experimental network option is enabled, then check the args, else let the default decoder handle it
|
||||
|
||||
for (int iArg = 0; iArg < args.length; iArg++) {
|
||||
final Object arg = args[iArg];
|
||||
final Class<?> type = this.args[iArg];
|
||||
if (!ReflectionUtil.isInstance(arg, type)) {
|
||||
//throw new InternalError(String.format("Protocol method %s: illegal argument (%d) of type %s, %s expected", name(), iArg, arg.getClass().getName(), type.getName()));
|
||||
System.err.println(String.format("InternalError: Protocol method %s: illegal argument (%d) of type %s, %s expected (ProtocolMethod.java)", name(), iArg, arg.getClass().getName(), type.getName()));
|
||||
try {
|
||||
for (int iArg = 0; iArg < args.length; iArg++) {
|
||||
final Object arg = args[iArg];
|
||||
final Class<?> type = this.args[iArg];
|
||||
if (!ReflectionUtil.isInstance(arg, type)) {
|
||||
//throw new InternalError(String.format("Protocol method %s: illegal argument (%d) of type %s, %s expected", name(), iArg, arg.getClass().getName(), type.getName()));
|
||||
System.err.println(String.format("InternalError: Protocol method %s: illegal argument (%d) of type %s, %s expected (ProtocolMethod.java)", name(), iArg, arg.getClass().getName(), type.getName()));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,9 +76,9 @@ public class NetGuiGame extends AbstractGuiGame {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showPromptMessage(final PlayerView playerView, final String message, final CardView card) {
|
||||
public void showCardPromptMessage(final PlayerView playerView, final String message, final CardView card) {
|
||||
updateGameView();
|
||||
send(ProtocolMethod.showPromptMessage, playerView, message);
|
||||
send(ProtocolMethod.showCardPromptMessage, playerView, message, card);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.gui.GuiBase;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
@@ -304,8 +305,9 @@ public class QuestWinLoseController {
|
||||
|
||||
if (altReward > 0) {
|
||||
credGameplay += altReward;
|
||||
sb.append(TextUtil.concatNoSpace("Alternate win condition: <u>",
|
||||
winConditionName, "</u>! Bonus: ", String.valueOf(altReward), " credits.\n"));
|
||||
sb.append(GuiBase.getInterface().isLibgdxPort()
|
||||
? TextUtil.concatNoSpace("Alternate win condition: ", winConditionName, "! Bonus: ", String.valueOf(altReward), " credits.\n")
|
||||
: TextUtil.concatNoSpace("Alternate win condition: <u>", winConditionName, "</u>! Bonus: ", String.valueOf(altReward), " credits.\n"));
|
||||
}
|
||||
}
|
||||
// Mulligan to zero
|
||||
|
||||
@@ -39,7 +39,7 @@ public interface IGuiGame {
|
||||
void afterGameEnd();
|
||||
void showCombat();
|
||||
void showPromptMessage(PlayerView playerView, String message);
|
||||
void showPromptMessage(PlayerView playerView, String message, CardView card);
|
||||
void showCardPromptMessage(PlayerView playerView, String message, CardView card);
|
||||
void updateButtons(PlayerView owner, boolean okEnabled, boolean cancelEnabled, boolean focusOk);
|
||||
void updateButtons(PlayerView owner, String label1, String label2, boolean enable1, boolean enable2, boolean focus1);
|
||||
void flashIncorrectAction();
|
||||
|
||||
@@ -87,7 +87,7 @@ public class TargetSelection {
|
||||
final int maxTargets = numTargets != null ? numTargets.intValue() : ability.getMaxTargets();
|
||||
//final int maxTotalCMC = tgt.getMaxTotalCMC(ability.getHostCard(), ability);
|
||||
final int numTargeted = ability.getTargets().size();
|
||||
final boolean isSingleZone = ability.getTargetRestrictions().isSingleZone();
|
||||
final boolean isSingleZone = getTgt().isSingleZone();
|
||||
|
||||
final boolean hasEnoughTargets = minTargets == 0 || numTargeted >= minTargets;
|
||||
final boolean hasAllTargets = numTargeted == maxTargets && maxTargets > 0;
|
||||
@@ -219,7 +219,7 @@ public class TargetSelection {
|
||||
for (final Card inZone : choices) {
|
||||
Zone zz = game.getZoneOf(inZone);
|
||||
CardView cardView = CardView.get(inZone);
|
||||
if (this.ability.getTargetRestrictions() != null && this.ability.getTargetRestrictions().isWithSameCreatureType()) {
|
||||
if (getTgt() != null && getTgt().isWithSameCreatureType()) {
|
||||
Card firstTgt = this.ability.getTargetCard();
|
||||
if (firstTgt != null && !firstTgt.sharesCreatureTypeWith(inZone)) {
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user