Merge remote-tracking branch 'core-developers/master'
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.33-SNAPSHOT</version>
|
||||
<version>1.6.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-ai</artifactId>
|
||||
|
||||
@@ -91,9 +91,6 @@ public class ComputerUtil {
|
||||
}
|
||||
}
|
||||
|
||||
source.setCastSA(sa);
|
||||
sa.setLastStateBattlefield(game.getLastStateBattlefield());
|
||||
sa.setLastStateGraveyard(game.getLastStateGraveyard());
|
||||
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
||||
}
|
||||
|
||||
@@ -219,9 +216,6 @@ public class ComputerUtil {
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
if (sa.isSpell() && !source.isCopiedSpell()) {
|
||||
source.setCastSA(sa);
|
||||
sa.setLastStateBattlefield(game.getLastStateBattlefield());
|
||||
sa.setLastStateGraveyard(game.getLastStateGraveyard());
|
||||
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
||||
}
|
||||
|
||||
@@ -246,9 +240,6 @@ public class ComputerUtil {
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
if (sa.isSpell() && !source.isCopiedSpell()) {
|
||||
source.setCastSA(sa);
|
||||
sa.setLastStateBattlefield(game.getLastStateBattlefield());
|
||||
sa.setLastStateGraveyard(game.getLastStateGraveyard());
|
||||
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
||||
}
|
||||
|
||||
@@ -267,9 +258,6 @@ public class ComputerUtil {
|
||||
|
||||
final Card source = newSA.getHostCard();
|
||||
if (newSA.isSpell() && !source.isCopiedSpell()) {
|
||||
source.setCastSA(newSA);
|
||||
sa.setLastStateBattlefield(game.getLastStateBattlefield());
|
||||
sa.setLastStateGraveyard(game.getLastStateGraveyard());
|
||||
newSA.setHostCard(game.getAction().moveToStack(source, sa));
|
||||
|
||||
if (newSA.getApi() == ApiType.Charm && !newSA.isWrapper()) {
|
||||
@@ -290,9 +278,6 @@ public class ComputerUtil {
|
||||
if (ComputerUtilCost.canPayCost(sa, ai)) {
|
||||
final Card source = sa.getHostCard();
|
||||
if (sa.isSpell() && !source.isCopiedSpell()) {
|
||||
source.setCastSA(sa);
|
||||
sa.setLastStateBattlefield(game.getLastStateBattlefield());
|
||||
sa.setLastStateGraveyard(game.getLastStateGraveyard());
|
||||
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
||||
}
|
||||
|
||||
@@ -2338,7 +2323,7 @@ public class ComputerUtil {
|
||||
return chosen;
|
||||
}
|
||||
|
||||
public static Object vote(Player ai, List<Object> options, SpellAbility sa, Multimap<Object, Player> votes) {
|
||||
public static Object vote(Player ai, List<Object> options, SpellAbility sa, Multimap<Object, Player> votes, Player forPlayer) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Player controller = source.getController();
|
||||
final Game game = controller.getGame();
|
||||
|
||||
@@ -1169,7 +1169,7 @@ public class ComputerUtilMana {
|
||||
cost.increaseShard(shardToGrow, manaToAdd);
|
||||
|
||||
if (!test) {
|
||||
card.setXManaCostPaid(manaToAdd / cost.getXcounter());
|
||||
sa.setXManaCostPaid(manaToAdd / cost.getXcounter());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -981,14 +981,27 @@ public abstract class GameState {
|
||||
spellDef = spellDef.substring(0, spellDef.indexOf("->")).trim();
|
||||
}
|
||||
|
||||
PaperCard pc = StaticData.instance().getCommonCards().getCard(spellDef);
|
||||
Card c = null;
|
||||
|
||||
if (pc == null) {
|
||||
System.err.println("ERROR: Could not find a card with name " + spellDef + " to precast!");
|
||||
return;
|
||||
if (StringUtils.isNumeric(spellDef)) {
|
||||
// Precast from a specific host
|
||||
c = idToCard.get(Integer.parseInt(spellDef));
|
||||
if (c == null) {
|
||||
System.err.println("ERROR: Could not find a card with ID " + spellDef + " to precast!");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Precast from a card by name
|
||||
PaperCard pc = StaticData.instance().getCommonCards().getCard(spellDef);
|
||||
|
||||
if (pc == null) {
|
||||
System.err.println("ERROR: Could not find a card with name " + spellDef + " to precast!");
|
||||
return;
|
||||
}
|
||||
|
||||
c = Card.fromPaperCard(pc, activator);
|
||||
}
|
||||
|
||||
Card c = Card.fromPaperCard(pc, activator);
|
||||
SpellAbility sa = null;
|
||||
|
||||
if (!scriptID.isEmpty()) {
|
||||
|
||||
@@ -513,8 +513,8 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes) {
|
||||
return ComputerUtil.vote(player, options, sa, votes);
|
||||
public Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer) {
|
||||
return ComputerUtil.vote(player, options, sa, votes, forPlayer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1295,6 +1295,26 @@ public class SpecialCardAi {
|
||||
}
|
||||
}
|
||||
|
||||
// Timmerian Fiends
|
||||
public static class TimmerianFiends {
|
||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||
final Card targeted = sa.getParentTargetingCard().getTargetCard();
|
||||
if (targeted == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targeted.isCreature()) {
|
||||
if (ComputerUtil.aiLifeInDanger(ai, true, 0)) {
|
||||
return true; // do it, hoping to save a valuable potential blocker etc.
|
||||
}
|
||||
return ComputerUtilCard.evaluateCreature(targeted) >= 200; // might need tweaking
|
||||
} else {
|
||||
// TODO: this currently compares purely by CMC. To be somehow improved, especially for stuff like the Power Nine etc.
|
||||
return ComputerUtilCard.evaluatePermanentList(new CardCollection(targeted)) >= 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Volrath's Shapeshifter
|
||||
public static class VolrathsShapeshifter {
|
||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
@@ -24,6 +19,11 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class MillAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
@@ -196,6 +196,10 @@ public class MillAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
if ("TimmerianFiends".equals(sa.getParam("AILogic"))) {
|
||||
return SpecialCardAi.TimmerianFiends.consider(player, sa);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,12 @@ public class VoteAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
||||
if (params.containsKey("Voter")) {
|
||||
Player p = (Player)params.get("Voter");
|
||||
if (p.isOpponentOf(player)) {
|
||||
return min;
|
||||
}
|
||||
}
|
||||
if (sa.getActivatingPlayer().isOpponentOf(player)) {
|
||||
return min;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.33-SNAPSHOT</version>
|
||||
<version>1.6.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-core</artifactId>
|
||||
|
||||
@@ -6,6 +6,8 @@ import forge.util.ImageUtil;
|
||||
import forge.util.TextUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -113,7 +115,17 @@ public final class ImageKeys {
|
||||
}
|
||||
//try fullborder...
|
||||
if (filename.contains(".full")) {
|
||||
file = findFile(dir, TextUtil.fastReplace(filename, ".full", ".fullborder"));
|
||||
String fullborderFile = TextUtil.fastReplace(filename, ".full", ".fullborder");
|
||||
file = findFile(dir, fullborderFile);
|
||||
if (file != null) { return file; }
|
||||
// if there's a 1st art variant try without it for .fullborder images
|
||||
file = findFile(dir, TextUtil.fastReplace(fullborderFile, "1.fullborder", ".fullborder"));
|
||||
if (file != null) { return file; }
|
||||
// if there's an art variant try without it for .full images
|
||||
file = findFile(dir, filename.replaceAll("[0-9].full]",".full"));
|
||||
if (file != null) { return file; }
|
||||
// if there's a 1st art variant try with it for .full images
|
||||
file = findFile(dir, filename.replaceAll("[0-9]*.full", "1.full"));
|
||||
if (file != null) { return file; }
|
||||
}
|
||||
//if an image, like phenomenon or planes is missing .full in their filenames but you have an existing images that have .full/.fullborder
|
||||
|
||||
@@ -54,11 +54,11 @@ public class StaticData {
|
||||
|
||||
private static StaticData lastInstance = null;
|
||||
|
||||
public StaticData(CardStorageReader cardReader, String editionFolder, String blockDataFolder) {
|
||||
this(cardReader, null, editionFolder, blockDataFolder);
|
||||
public StaticData(CardStorageReader cardReader, String editionFolder, String blockDataFolder, boolean enableUnknownCards) {
|
||||
this(cardReader, null, editionFolder, blockDataFolder, enableUnknownCards);
|
||||
}
|
||||
|
||||
public StaticData(CardStorageReader cardReader, CardStorageReader tokenReader, String editionFolder, String blockDataFolder) {
|
||||
public StaticData(CardStorageReader cardReader, CardStorageReader tokenReader, String editionFolder, String blockDataFolder, boolean enableUnknownCards) {
|
||||
this.cardReader = cardReader;
|
||||
this.tokenReader = tokenReader;
|
||||
this.editions = new CardEdition.Collection(new CardEdition.Reader(new File(editionFolder)));
|
||||
@@ -84,8 +84,8 @@ public class StaticData {
|
||||
variantCards = new CardDb(variantsCards, editions);
|
||||
|
||||
//must initialize after establish field values for the sake of card image logic
|
||||
commonCards.initialize(false, false);
|
||||
variantCards.initialize(false, false);
|
||||
commonCards.initialize(false, false, enableUnknownCards);
|
||||
variantCards.initialize(false, false, enableUnknownCards);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -215,7 +215,7 @@ public class StaticData {
|
||||
public Predicate<PaperCard> getStandardPredicate() { return standardPredicate; }
|
||||
|
||||
public Predicate<PaperCard> getPioneerPredicate() { return pioneerPredicate; }
|
||||
|
||||
|
||||
public Predicate<PaperCard> getModernPredicate() { return modernPredicate; }
|
||||
|
||||
public Predicate<PaperCard> getCommanderPredicate() { return commanderPredicate; }
|
||||
|
||||
@@ -165,7 +165,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
reIndex();
|
||||
}
|
||||
|
||||
public void initialize(boolean logMissingPerEdition, boolean logMissingSummary) {
|
||||
public void initialize(boolean logMissingPerEdition, boolean logMissingSummary, boolean enableUnknownCards) {
|
||||
Set<String> allMissingCards = new LinkedHashSet<>();
|
||||
List<String> missingCards = new ArrayList<>();
|
||||
CardEdition upcomingSet = null;
|
||||
@@ -218,7 +218,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
if (!contains(cr.getName())) {
|
||||
if (upcomingSet != null) {
|
||||
addCard(new PaperCard(cr, upcomingSet.getCode(), CardRarity.Unknown, 1));
|
||||
} else {
|
||||
} else if(enableUnknownCards) {
|
||||
System.err.println("The card " + cr.getName() + " was not assigned to any set. Adding it to UNKNOWN set... to fix see res/editions/ folder. ");
|
||||
addCard(new PaperCard(cr, CardEdition.UNKNOWN.getCode(), CardRarity.Special, 1));
|
||||
}
|
||||
@@ -312,17 +312,21 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
return tryGetCard(request);
|
||||
}
|
||||
|
||||
public int getCardCollectorNumber(String cardName, String reqEdition) {
|
||||
public String getCardCollectorNumber(String cardName, String reqEdition, int artIndex) {
|
||||
cardName = getName(cardName);
|
||||
CardEdition edition = editions.get(reqEdition);
|
||||
if (edition == null)
|
||||
return -1;
|
||||
return null;
|
||||
int numMatches = 0;
|
||||
for (CardInSet card : edition.getCards()) {
|
||||
if (card.name.equalsIgnoreCase(cardName)) {
|
||||
return card.collectorNumber;
|
||||
numMatches += 1;
|
||||
if (numMatches == artIndex) {
|
||||
return card.collectorNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
return null;
|
||||
}
|
||||
|
||||
private PaperCard tryGetCard(CardRequest request) {
|
||||
|
||||
@@ -38,6 +38,8 @@ import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
/**
|
||||
@@ -75,10 +77,10 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
|
||||
public static class CardInSet {
|
||||
public final CardRarity rarity;
|
||||
public final int collectorNumber;
|
||||
public final String collectorNumber;
|
||||
public final String name;
|
||||
|
||||
public CardInSet(final String name, final int collectorNumber, final CardRarity rarity) {
|
||||
public CardInSet(final String name, final String collectorNumber, final CardRarity rarity) {
|
||||
this.name = name;
|
||||
this.collectorNumber = collectorNumber;
|
||||
this.rarity = rarity;
|
||||
@@ -86,7 +88,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (collectorNumber != -1) {
|
||||
if (collectorNumber != null) {
|
||||
sb.append(collectorNumber);
|
||||
sb.append(' ');
|
||||
}
|
||||
@@ -190,6 +192,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
public boolean getSmallSetOverride() { return smallSetOverride; }
|
||||
public String getBoosterMustContain() { return boosterMustContain; }
|
||||
public CardInSet[] getCards() { return cards; }
|
||||
public boolean isModern() { return getDate().after(parseDate("2003-07-27")); } //8ED and above are modern except some promo cards and others
|
||||
|
||||
public Map<String, Integer> getTokens() { return tokenNormalized; }
|
||||
|
||||
@@ -266,24 +269,33 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
Map<String, Integer> tokenNormalized = new HashMap<>();
|
||||
List<CardEdition.CardInSet> processedCards = new ArrayList<>();
|
||||
if (contents.containsKey("cards")) {
|
||||
final Pattern pattern = Pattern.compile(
|
||||
/*
|
||||
The following pattern will match the WAR Japanese art entries,
|
||||
it should also match the Un-set and older alternate art cards
|
||||
like Merseine from FEM (should the editions files ever be updated)
|
||||
*/
|
||||
//"(^(?<cnum>[0-9]+.?) )?((?<rarity>[SCURML]) )?(?<name>.*)$"
|
||||
/* Ideally we'd use the named group above, but Android 6 and
|
||||
earlier don't appear to support named groups.
|
||||
So, untill support for those devices is officially dropped,
|
||||
we'll have to suffice with numbered groups.
|
||||
We are looking for:
|
||||
* cnum - grouping #2
|
||||
* rarity - grouping #4
|
||||
* name - grouping #5
|
||||
*/
|
||||
"(^([0-9]+.?) )?(([SCURML]) )?(.*)$"
|
||||
);
|
||||
for(String line : contents.get("cards")) {
|
||||
if (StringUtils.isBlank(line))
|
||||
continue;
|
||||
|
||||
// Optional collector number at the start.
|
||||
String[] split = line.split(" ", 2);
|
||||
int collectorNumber = -1;
|
||||
if (split.length >= 2 && StringUtils.isNumeric(split[0])) {
|
||||
collectorNumber = Integer.parseInt(split[0]);
|
||||
line = split[1];
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
if (matcher.matches()) {
|
||||
String collectorNumber = matcher.group(2);
|
||||
CardRarity r = CardRarity.smartValueOf(matcher.group(4));
|
||||
String cardName = matcher.group(5);
|
||||
CardInSet cis = new CardInSet(cardName, collectorNumber, r);
|
||||
processedCards.add(cis);
|
||||
}
|
||||
|
||||
// You may omit rarity for early development
|
||||
CardRarity r = CardRarity.smartValueOf(line.substring(0, 1));
|
||||
boolean hadRarity = r != CardRarity.Unknown && line.charAt(1) == ' ';
|
||||
String cardName = hadRarity ? line.substring(2) : line;
|
||||
CardInSet cis = new CardInSet(cardName, collectorNumber, r);
|
||||
processedCards.add(cis);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -222,7 +222,12 @@ public final class CardRules implements ICardCharacteristics {
|
||||
|
||||
public boolean canBeBrawlCommander() {
|
||||
CardType type = mainPart.getType();
|
||||
return (type.isLegendary() && type.isCreature()) || type.isPlaneswalker();
|
||||
return type.isLegendary() && (type.isCreature() || type.isPlaneswalker());
|
||||
}
|
||||
|
||||
public boolean canBeTinyLeadersCommander() {
|
||||
CardType type = mainPart.getType();
|
||||
return type.isLegendary() && (type.isCreature() || type.isPlaneswalker());
|
||||
}
|
||||
|
||||
public String getMeldWith() {
|
||||
|
||||
@@ -594,8 +594,10 @@ public final class CardRulesPredicates {
|
||||
public static final Predicate<CardRules> IS_VANGUARD = CardRulesPredicates.coreType(true, CardType.CoreType.Vanguard);
|
||||
public static final Predicate<CardRules> IS_CONSPIRACY = CardRulesPredicates.coreType(true, CardType.CoreType.Conspiracy);
|
||||
public static final Predicate<CardRules> IS_NON_LAND = CardRulesPredicates.coreType(false, CardType.CoreType.Land);
|
||||
public static final Predicate<CardRules> CAN_BE_BRAWL_COMMANDER = Predicates.or(Presets.IS_PLANESWALKER,
|
||||
Predicates.and(Presets.IS_CREATURE, Presets.IS_LEGENDARY));
|
||||
public static final Predicate<CardRules> CAN_BE_BRAWL_COMMANDER = Predicates.and(Presets.IS_LEGENDARY,
|
||||
Predicates.or(Presets.IS_CREATURE, Presets.IS_PLANESWALKER));
|
||||
public static final Predicate<CardRules> CAN_BE_TINY_LEADERS_COMMANDER = Predicates.and(Presets.IS_LEGENDARY,
|
||||
Predicates.or(Presets.IS_CREATURE, Presets.IS_PLANESWALKER));
|
||||
|
||||
/** The Constant IS_NON_CREATURE_SPELL. **/
|
||||
public static final Predicate<CardRules> IS_NON_CREATURE_SPELL = com.google.common.base.Predicates
|
||||
|
||||
@@ -463,6 +463,9 @@ public enum DeckFormat {
|
||||
if (this.equals(DeckFormat.Brawl)) {
|
||||
return rules.canBeBrawlCommander();
|
||||
}
|
||||
if (this.equals(DeckFormat.TinyLeaders)) {
|
||||
return rules.canBeTinyLeadersCommander();
|
||||
}
|
||||
return rules.canBeCommander();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import forge.card.CardRarity;
|
||||
import forge.card.CardRules;
|
||||
import forge.card.CardType.CoreType;
|
||||
import forge.card.MagicColor;
|
||||
import forge.util.PredicateCard;
|
||||
import forge.util.PredicateString;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@@ -60,6 +61,8 @@ public interface IPaperCard extends InventoryItem {
|
||||
return new PredicateNames(what);
|
||||
}
|
||||
|
||||
public static PredicateCards cards(final List<PaperCard> what) { return new PredicateCards(what); }
|
||||
|
||||
private static final class PredicateColor implements Predicate<PaperCard> {
|
||||
|
||||
private final byte operand;
|
||||
@@ -161,6 +164,25 @@ public interface IPaperCard extends InventoryItem {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PredicateCards extends PredicateCard<PaperCard> {
|
||||
private final List<PaperCard> operand;
|
||||
|
||||
@Override
|
||||
public boolean apply(final PaperCard card) {
|
||||
for (final PaperCard element : this.operand) {
|
||||
if (this.op(card, element)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private PredicateCards(final List<PaperCard> operand) {
|
||||
super(StringOp.EQUALS);
|
||||
this.operand = operand;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-built predicates are stored here to allow their re-usage and
|
||||
* easier access from code.
|
||||
|
||||
@@ -566,12 +566,8 @@ public class BoosterGenerator {
|
||||
toAdd = IPaperCard.Predicates.printedInSets(sets);
|
||||
} else if (operator.startsWith("fromSheet(") && invert) {
|
||||
String sheetName = StringUtils.strip(operator.substring(9), "()\" ");
|
||||
Iterable<PaperCard> src = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
|
||||
List<String> cardNames = Lists.newArrayList();
|
||||
for (PaperCard card : src) {
|
||||
cardNames.add(card.getName());
|
||||
}
|
||||
toAdd = IPaperCard.Predicates.names(Lists.newArrayList(cardNames));
|
||||
Iterable<PaperCard> cards = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
|
||||
toAdd = IPaperCard.Predicates.cards(Lists.newArrayList(cards));
|
||||
}
|
||||
|
||||
if (toAdd == null) {
|
||||
|
||||
83
forge-core/src/main/java/forge/util/PredicateCard.java
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2020 Jamin W. Collins
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.util;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.item.PaperCard;
|
||||
|
||||
/**
|
||||
* Special predicate class to perform string operations.
|
||||
*
|
||||
* @param <T>
|
||||
* the generic type
|
||||
*/
|
||||
public abstract class PredicateCard<T> implements Predicate<T> {
|
||||
/** Possible operators for string operands. */
|
||||
public enum StringOp {
|
||||
/** The EQUALS. */
|
||||
EQUALS,
|
||||
}
|
||||
|
||||
/** The operator. */
|
||||
private final StringOp operator;
|
||||
|
||||
/**
|
||||
* Op.
|
||||
*
|
||||
* @param op1
|
||||
* the op1
|
||||
* @param op2
|
||||
* the op2
|
||||
* @return true, if successful
|
||||
*/
|
||||
protected final boolean op(final PaperCard op1, final PaperCard op2) {
|
||||
switch (this.getOperator()) {
|
||||
case EQUALS:
|
||||
return op1.equals(op2);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new predicate string.
|
||||
*
|
||||
* @param operator
|
||||
* the operator
|
||||
*/
|
||||
public PredicateCard(final StringOp operator) {
|
||||
this.operator = operator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the operator
|
||||
*/
|
||||
public StringOp getOperator() {
|
||||
return operator;
|
||||
}
|
||||
|
||||
public static PredicateCard<PaperCard> equals(final PaperCard what) {
|
||||
return new PredicateCard<PaperCard>(StringOp.EQUALS) {
|
||||
@Override
|
||||
public boolean apply(PaperCard subject) {
|
||||
return op(subject, what);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import forge.item.PaperCard;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -17,6 +19,22 @@ import java.util.Map.Entry;
|
||||
*/
|
||||
public class TextUtil {
|
||||
|
||||
static ImmutableSortedMap<Integer,String> romanMap = ImmutableSortedMap.<Integer,String>naturalOrder()
|
||||
.put(1000, "M").put(900, "CM")
|
||||
.put(500, "D").put(400, "CD")
|
||||
.put(100, "C").put(90, "XC")
|
||||
.put(50, "L").put(40, "XL")
|
||||
.put(10, "X").put(9, "IX")
|
||||
.put(5, "V").put(4, "IV").put(1, "I").build();
|
||||
|
||||
public final static String toRoman(int number) {
|
||||
if (number <= 0) {
|
||||
return "";
|
||||
}
|
||||
int l = romanMap.floorKey(number);
|
||||
return romanMap.get(l) + toRoman(number-l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely converts an object to a String.
|
||||
*
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.33-SNAPSHOT</version>
|
||||
<version>1.6.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-game</artifactId>
|
||||
|
||||
@@ -914,4 +914,17 @@ public class Game {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Player getControlVote() {
|
||||
Player result = null;
|
||||
long maxValue = 0;
|
||||
for (Player p : getPlayers()) {
|
||||
Long v = p.getHighestControlVote();
|
||||
if (v != null && v > maxValue) {
|
||||
maxValue = v;
|
||||
result = p;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,11 +157,6 @@ public class GameAction {
|
||||
c.removeSVar("EndOfTurnLeavePlay");
|
||||
}
|
||||
|
||||
// Clean up temporary variables such as Sunburst value or announced PayX value
|
||||
if (!(zoneTo.is(ZoneType.Stack) || zoneTo.is(ZoneType.Battlefield))) {
|
||||
c.clearTemporaryVars();
|
||||
}
|
||||
|
||||
if (fromBattlefield && !toBattlefield) {
|
||||
c.getController().setRevolt(true);
|
||||
}
|
||||
@@ -173,7 +168,7 @@ public class GameAction {
|
||||
|
||||
// if to Battlefield and it is caused by an replacement effect,
|
||||
// try to get previous LKI if able
|
||||
if (zoneTo.is(ZoneType.Battlefield)) {
|
||||
if (toBattlefield) {
|
||||
if (cause != null && cause.isReplacementAbility()) {
|
||||
ReplacementEffect re = cause.getReplacementEffect();
|
||||
if (ReplacementType.Moved.equals(re.getMode())) {
|
||||
@@ -244,6 +239,12 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up temporary variables such as Sunburst value or announced PayX value
|
||||
if (!(zoneTo.is(ZoneType.Stack) || zoneTo.is(ZoneType.Battlefield))) {
|
||||
copied.clearTemporaryVars();
|
||||
}
|
||||
|
||||
|
||||
if (!suppress) {
|
||||
if (zoneFrom == null) {
|
||||
copied.getOwner().addInboundToken(copied);
|
||||
@@ -547,6 +548,13 @@ public class GameAction {
|
||||
c.setCastSA(null);
|
||||
} else if (zoneTo.is(ZoneType.Stack)) {
|
||||
c.setCastFrom(zoneFrom.getZoneType());
|
||||
if (cause != null && cause.isSpell() && c.equals(cause.getHostCard()) && !c.isCopiedSpell()) {
|
||||
cause.setLastStateBattlefield(game.getLastStateBattlefield());
|
||||
cause.setLastStateGraveyard(game.getLastStateGraveyard());
|
||||
c.setCastSA(cause);
|
||||
} else {
|
||||
c.setCastSA(null);
|
||||
}
|
||||
} else if (!(zoneTo.is(ZoneType.Battlefield) && zoneFrom.is(ZoneType.Stack))) {
|
||||
c.setCastFrom(null);
|
||||
c.setCastSA(null);
|
||||
@@ -977,7 +985,7 @@ public class GameAction {
|
||||
for (final Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
||||
if (c.isCreature()) {
|
||||
// Rule 704.5f - Put into grave (no regeneration) for toughness <= 0
|
||||
if (c.getNetToughness() <= 0) {
|
||||
if (c.getLethal() <= 0) {
|
||||
if (noRegCreats == null) {
|
||||
noRegCreats = new CardCollection();
|
||||
}
|
||||
@@ -985,7 +993,7 @@ public class GameAction {
|
||||
checkAgain = true;
|
||||
} else if (c.hasKeyword("CARDNAME can't be destroyed by lethal damage unless lethal damage dealt by a single source is marked on it.")) {
|
||||
for (final Integer dmg : c.getReceivedDamageFromThisTurn().values()) {
|
||||
if (c.getNetToughness() <= dmg.intValue()) {
|
||||
if (c.getLethal() <= dmg.intValue()) {
|
||||
if (desCreats == null) {
|
||||
desCreats = new CardCollection();
|
||||
}
|
||||
@@ -997,7 +1005,7 @@ public class GameAction {
|
||||
}
|
||||
// Rule 704.5g - Destroy due to lethal damage
|
||||
// Rule 704.5h - Destroy due to deathtouch
|
||||
else if (c.getNetToughness() <= c.getDamage() || c.hasBeenDealtDeathtouchDamage()) {
|
||||
else if (c.getLethal() <= c.getDamage() || c.hasBeenDealtDeathtouchDamage()) {
|
||||
if (desCreats == null) {
|
||||
desCreats = new CardCollection();
|
||||
}
|
||||
|
||||
@@ -22,19 +22,28 @@ import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.card.mana.ManaCostParser;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.CardPlayOption.PayManaCost;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerController;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.replacement.ReplacementHandler;
|
||||
import forge.game.replacement.ReplacementLayer;
|
||||
import forge.game.spellability.*;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerHandler;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Lang;
|
||||
import forge.util.TextUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@@ -210,23 +219,34 @@ public final class GameActionUtil {
|
||||
}
|
||||
}
|
||||
|
||||
if (!sa.isBasicSpell()) {
|
||||
return alternatives;
|
||||
}
|
||||
|
||||
// below are for some special cases of activated abilities
|
||||
if (sa.isCycling() && activator.hasKeyword("CyclingForZero")) {
|
||||
// set the cost to this directly to buypass non mana cost
|
||||
final SpellAbility newSA = sa.copyWithDefinedCost("Discard<1/CARDNAME>");
|
||||
newSA.setActivatingPlayer(activator);
|
||||
newSA.setBasicSpell(false);
|
||||
newSA.getMapParams().put("CostDesc", ManaCostParser.parse("0"));
|
||||
// makes new SpellDescription
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(newSA.getCostDescription());
|
||||
sb.append(newSA.getParam("SpellDescription"));
|
||||
newSA.setDescription(sb.toString());
|
||||
|
||||
alternatives.add(newSA);
|
||||
for (final KeywordInterface inst : source.getKeywords()) {
|
||||
// need to find the correct Keyword from which this Ability is from
|
||||
if (!inst.getAbilities().contains(sa)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// set the cost to this directly to buypass non mana cost
|
||||
final SpellAbility newSA = sa.copyWithDefinedCost("Discard<1/CARDNAME>");
|
||||
newSA.setActivatingPlayer(activator);
|
||||
newSA.getMapParams().put("CostDesc", ManaCostParser.parse("0"));
|
||||
|
||||
// need to build a new Keyword to get better Reminder Text
|
||||
String data[] = inst.getOriginal().split(":");
|
||||
data[1] = "0";
|
||||
KeywordInterface newKi = Keyword.getInstance(StringUtils.join(data, ":"));
|
||||
|
||||
// makes new SpellDescription
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(newSA.getCostDescription());
|
||||
sb.append("(").append(newKi.getReminderText()).append(")");
|
||||
newSA.setDescription(sb.toString());
|
||||
|
||||
alternatives.add(newSA);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("Equip") && activator.hasKeyword("EquipInstantSpeed")) {
|
||||
@@ -363,10 +383,11 @@ public final class GameActionUtil {
|
||||
}
|
||||
SpellAbility result = null;
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = host.getGame();
|
||||
final Player activator = sa.getActivatingPlayer();
|
||||
final PlayerController pc = activator.getController();
|
||||
|
||||
host.getGame().getAction().checkStaticAbilities(false);
|
||||
game.getAction().checkStaticAbilities(false);
|
||||
|
||||
boolean reset = false;
|
||||
|
||||
@@ -429,7 +450,60 @@ public final class GameActionUtil {
|
||||
int v = pc.chooseNumberForKeywordCost(sa, cost, ki, str, Integer.MAX_VALUE);
|
||||
|
||||
if (v > 0) {
|
||||
host.addReplacementEffect(CardFactoryUtil.makeEtbCounter("etbCounter:P1P1:" + v, host, false));
|
||||
|
||||
final Card eff = new Card(game.nextCardId(), game);
|
||||
eff.setTimestamp(game.getNextTimestamp());
|
||||
eff.setName(c.getName() + "'s Effect");
|
||||
eff.addType("Effect");
|
||||
eff.setToken(true); // Set token to true, so when leaving play it gets nuked
|
||||
eff.setOwner(activator);
|
||||
|
||||
eff.setImageKey(c.getImageKey());
|
||||
eff.setColor(MagicColor.COLORLESS);
|
||||
eff.setImmutable(true);
|
||||
// try to get the SpellAbility from the mana ability
|
||||
//eff.setEffectSource((SpellAbility)null);
|
||||
|
||||
eff.addRemembered(host);
|
||||
|
||||
String abStr = "DB$ PutCounter | Defined$ ReplacedCard | CounterType$ P1P1 | ETB$ True | CounterNum$ " + v;
|
||||
|
||||
SpellAbility saAb = AbilityFactory.getAbility(abStr, c);
|
||||
|
||||
CardFactoryUtil.setupETBReplacementAbility(saAb);
|
||||
|
||||
String desc = "It enters the battlefield with ";
|
||||
desc += Lang.nounWithNumeral(v, CounterType.P1P1.getName() + " counter");
|
||||
desc += " on it.";
|
||||
|
||||
String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered | Destination$ Battlefield | Description$ " + desc;
|
||||
|
||||
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);
|
||||
re.setLayer(ReplacementLayer.Other);
|
||||
re.setOverridingAbility(saAb);
|
||||
|
||||
eff.addReplacementEffect(re);
|
||||
|
||||
// Forgot Trigger
|
||||
String trig = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Stack | Destination$ Any | TriggerZones$ Command | Static$ True";
|
||||
String forgetEffect = "DB$ Pump | ForgetObjects$ TriggeredCard";
|
||||
String exileEffect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile"
|
||||
+ " | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0";
|
||||
|
||||
SpellAbility saForget = AbilityFactory.getAbility(forgetEffect, eff);
|
||||
AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(exileEffect, eff);
|
||||
saForget.setSubAbility(saExile);
|
||||
|
||||
final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, eff, true);
|
||||
parsedTrigger.setOverridingAbility(saForget);
|
||||
eff.addTrigger(parsedTrigger);
|
||||
eff.updateStateForView();
|
||||
|
||||
// TODO: Add targeting to the effect so it knows who it's dealing with
|
||||
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
|
||||
game.getAction().moveTo(ZoneType.Command, eff, null);
|
||||
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
|
||||
|
||||
if (result == null) {
|
||||
result = sa.copy();
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ import java.util.Map.Entry;
|
||||
public class GameFormat implements Comparable<GameFormat> {
|
||||
private final String name;
|
||||
public enum FormatType {Sanctioned, Casual, Historic, Digital, Custom}
|
||||
public enum FormatSubType {Block, Standard, Extended, Pioneer, Modern, Legacy, Vintage, Commander, Planechase, Videogame, MTGO, Custom}
|
||||
public enum FormatSubType {Block, Standard, Extended, Pioneer, Modern, Legacy, Vintage, Commander, Planechase, Videogame, MTGO, Arena, Custom}
|
||||
|
||||
// contains allowed sets, when empty allows all sets
|
||||
private FormatType formatType;
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@@ -37,7 +37,7 @@ import com.google.common.collect.Maps;
|
||||
* <p>
|
||||
* StaticEffect class.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id$
|
||||
*/
|
||||
@@ -72,7 +72,7 @@ public class StaticEffect {
|
||||
|
||||
/**
|
||||
* setTimestamp TODO Write javadoc for this method.
|
||||
*
|
||||
*
|
||||
* @param t
|
||||
* a long
|
||||
*/
|
||||
@@ -82,7 +82,7 @@ public class StaticEffect {
|
||||
|
||||
/**
|
||||
* getTimestamp. TODO Write javadoc for this method.
|
||||
*
|
||||
*
|
||||
* @return a long
|
||||
*/
|
||||
public final long getTimestamp() {
|
||||
@@ -93,7 +93,7 @@ public class StaticEffect {
|
||||
* <p>
|
||||
* Getter for the field <code>source</code>.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @return a {@link forge.game.card.Card} object.
|
||||
*/
|
||||
public final Card getSource() {
|
||||
@@ -104,7 +104,7 @@ public class StaticEffect {
|
||||
* <p>
|
||||
* Getter for the field <code>affectedCards</code>.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @return a {@link forge.CardList} object.
|
||||
*/
|
||||
public final CardCollectionView getAffectedCards() {
|
||||
@@ -115,7 +115,7 @@ public class StaticEffect {
|
||||
* <p>
|
||||
* Setter for the field <code>affectedCards</code>.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @param list
|
||||
* a {@link forge.CardList} object.
|
||||
*/
|
||||
@@ -125,7 +125,7 @@ public class StaticEffect {
|
||||
|
||||
/**
|
||||
* Gets the affected players.
|
||||
*
|
||||
*
|
||||
* @return the affected players
|
||||
*/
|
||||
public final List<Player> getAffectedPlayers() {
|
||||
@@ -134,7 +134,7 @@ public class StaticEffect {
|
||||
|
||||
/**
|
||||
* Sets the affected players.
|
||||
*
|
||||
*
|
||||
* @param list
|
||||
* the new affected players
|
||||
*/
|
||||
@@ -144,7 +144,7 @@ public class StaticEffect {
|
||||
|
||||
/**
|
||||
* setParams. TODO Write javadoc for this method.
|
||||
*
|
||||
*
|
||||
* @param params
|
||||
* a HashMap
|
||||
*/
|
||||
@@ -154,7 +154,7 @@ public class StaticEffect {
|
||||
|
||||
/**
|
||||
* Gets the params.
|
||||
*
|
||||
*
|
||||
* @return the params
|
||||
*/
|
||||
public final Map<String, String> getParams() {
|
||||
@@ -171,13 +171,12 @@ public class StaticEffect {
|
||||
|
||||
/**
|
||||
* Undo everything that was changed by this effect.
|
||||
*
|
||||
*
|
||||
* @return a {@link CardCollectionView} of all affected cards.
|
||||
*/
|
||||
final CardCollectionView remove() {
|
||||
final CardCollectionView affectedCards = getAffectedCards();
|
||||
final List<Player> affectedPlayers = getAffectedPlayers();
|
||||
//final Map<String, String> params = getParams();
|
||||
|
||||
String changeColorWordsTo = null;
|
||||
|
||||
@@ -245,6 +244,10 @@ public class StaticEffect {
|
||||
|
||||
p.removeMaxLandPlays(getTimestamp());
|
||||
p.removeMaxLandPlaysInfinite(getTimestamp());
|
||||
|
||||
p.removeControlVote(getTimestamp());
|
||||
p.removeAdditionalVote(getTimestamp());
|
||||
p.removeAdditionalOptionalVote(getTimestamp());
|
||||
}
|
||||
|
||||
// modify the affected card
|
||||
|
||||
@@ -25,6 +25,8 @@ import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.*;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Expressions;
|
||||
import forge.util.TextUtil;
|
||||
@@ -1573,11 +1575,51 @@ public class AbilityUtils {
|
||||
|
||||
// special logic for xPaid in SpellAbility
|
||||
if (sq[0].contains("xPaid")) {
|
||||
// ETB effects of cloned cards have xPaid = 0
|
||||
if (sa.hasParam("ETB") && sa.getOriginalHost() != null) {
|
||||
SpellAbility root = sa.getRootAbility();
|
||||
|
||||
// 107.3i If an object gains an ability, the value of X within that ability is the value defined by that ability,
|
||||
// or 0 if that ability doesn’t define a value of X. This is an exception to rule 107.3h. This may occur with ability-adding effects, text-changing effects, or copy effects.
|
||||
if (root.getXManaCostPaid() != null) {
|
||||
return CardFactoryUtil.doXMath(root.getXManaCostPaid(), expr, c);
|
||||
}
|
||||
|
||||
// If the chosen creature has X in its mana cost, that X is considered to be 0.
|
||||
// The value of X in Altered Ego’s last ability will be whatever value was chosen for X while casting Altered Ego.
|
||||
if (sa.getOriginalHost() != null || !sa.getHostCard().equals(c)) {
|
||||
return 0;
|
||||
}
|
||||
return CardFactoryUtil.doXMath(c.getXManaCostPaid(), expr, c);
|
||||
|
||||
if (root.isTrigger()) {
|
||||
Trigger t = root.getTrigger();
|
||||
if (t == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 107.3k If an object’s enters-the-battlefield triggered ability or replacement effect refers to X,
|
||||
// and the spell that became that object as it resolved had a value of X chosen for any of its costs,
|
||||
// the value of X for that ability is the same as the value of X for that spell, although the value of X for that permanent is 0.
|
||||
if (TriggerType.ChangesZone.equals(t.getMode())
|
||||
&& ZoneType.Battlefield.name().equals(t.getParam("Destination"))) {
|
||||
return CardFactoryUtil.doXMath(c.getXManaCostPaid(), expr, c);
|
||||
} else if (TriggerType.SpellCast.equals(t.getMode())) {
|
||||
// Cast Trigger like Hydroid Krasis
|
||||
return CardFactoryUtil.doXMath(c.getXManaCostPaid(), expr, c);
|
||||
} else if (TriggerType.Cycled.equals(t.getMode())) {
|
||||
SpellAbility cycleSA = (SpellAbility) sa.getTriggeringObject(AbilityKey.Cause);
|
||||
if (cycleSA == null) {
|
||||
return 0;
|
||||
}
|
||||
return CardFactoryUtil.doXMath(cycleSA.getXManaCostPaid(), expr, c);
|
||||
}
|
||||
}
|
||||
|
||||
if (root.isReplacementAbility()) {
|
||||
if (sa.hasParam("ETB")) {
|
||||
return CardFactoryUtil.doXMath(c.getXManaCostPaid(), expr, c);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Count$Kicked.<numHB>.<numNotHB>
|
||||
|
||||
@@ -112,9 +112,8 @@ public abstract class SpellAbilityEffect {
|
||||
sb.append(" ");
|
||||
sb.append(TextUtil.enclosedParen(TextUtil.concatNoSpace(svar,"=",String.valueOf(amount))));
|
||||
} else{
|
||||
if (sa.getPayCosts() != null && sa.getPayCosts().getCostMana() != null &&
|
||||
sa.getPayCosts().getCostMana().getAmountOfX() > 0) {
|
||||
int amount = sa.getHostCard().getXManaCostPaid();
|
||||
if (sa.costHasManaX()) {
|
||||
int amount = sa.getXManaCostPaid() == null ? 0 : sa.getXManaCostPaid();
|
||||
sb.append(" ");
|
||||
sb.append(TextUtil.enclosedParen(TextUtil.concatNoSpace("X","=",String.valueOf(amount))));
|
||||
}
|
||||
|
||||
@@ -67,8 +67,9 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
|
||||
|
||||
SpellAbility chosenSA = null;
|
||||
if (sa.hasParam("AtRandom")) {
|
||||
int idxChosen = MyRandom.getRandom().nextInt(abilities.size());
|
||||
chosenSA = abilities.get(idxChosen);
|
||||
if (!abilities.isEmpty()) {
|
||||
chosenSA = abilities.get(MyRandom.getRandom().nextInt(abilities.size()));
|
||||
}
|
||||
} else {
|
||||
chosenSA = p.getController().chooseSingleSpellForEffect(abilities, sa, Localizer.getInstance().getMessage("lblChooseOne"),
|
||||
ImmutableMap.of());
|
||||
@@ -90,7 +91,7 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
|
||||
if (fallback != null) {
|
||||
p.getGame().fireEvent(new GameEventCardModeChosen(p, host.getName(), fallback.getDescription(), sa.hasParam("ShowChoice")));
|
||||
AbilityUtils.resolve(fallback);
|
||||
} else {
|
||||
} else if (!sa.hasParam("AtRandom")) {
|
||||
System.err.println("Warning: all Unless costs were unpayable for " + host.getName() +", but it had no FallbackAbility defined. Doing nothing (this is most likely incorrect behavior).");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@ public class CountersNoteEffect extends SpellAbilityEffect {
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
for (Card c : getDefinedCardsOrTargeted(sa)) {
|
||||
if (mode.equals(MODE_STORE)) {
|
||||
noteCounters(c, source);
|
||||
noteCounters(c, c);
|
||||
} else if (mode.equals(MODE_LOAD)) {
|
||||
loadCounters(c, source, p, table);
|
||||
loadCounters(c, c, p, table);
|
||||
}
|
||||
}
|
||||
table.triggerCountersPutAll(game);
|
||||
|
||||
@@ -78,7 +78,9 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
final List<Card> targetCards = SpellAbilityEffect.getTargetCards(spellAbility);
|
||||
for(int i = 0; i < targetCards.size(); i++) {
|
||||
Card targetCard = targetCards.get(i);
|
||||
stringBuilder.append(targetCard).append(" (").append(spellAbility.getTargetRestrictions().getDividedMap().get(targetCard)).append(" counter)");
|
||||
stringBuilder.append(targetCard);
|
||||
if (spellAbility.getTargetRestrictions().getDividedMap().get(targetCard) != null) // fix null counter stack description
|
||||
stringBuilder.append(" (").append(spellAbility.getTargetRestrictions().getDividedMap().get(targetCard)).append(" counter)");
|
||||
|
||||
if(i == targetCards.size() - 2) {
|
||||
stringBuilder.append(" and ");
|
||||
@@ -104,7 +106,6 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
stringBuilder.append(".");
|
||||
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
@@ -147,8 +148,30 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("Bolster")) {
|
||||
CardCollection creatsYouCtrl = CardLists.filter(activator.getCardsIn(ZoneType.Battlefield), Presets.CREATURES);
|
||||
CardCollection leastToughness = new CardCollection(Aggregates.listWithMin(creatsYouCtrl, CardPredicates.Accessors.fnGetDefense));
|
||||
tgtCards.addAll(pc.chooseCardsForEffect(leastToughness, sa, Localizer.getInstance().getMessage("lblChooseACreatureWithLeastToughness"), 1, 1, false));
|
||||
tgtCards.addAll(activator.getController().chooseCardsForEffect(leastToughness, sa, Localizer.getInstance().getMessage("lblChooseACreatureWithLeastToughness"), 1, 1, false));
|
||||
tgtObjects.addAll(tgtCards);
|
||||
} else if (sa.hasParam("Choices")) {
|
||||
ZoneType choiceZone = ZoneType.Battlefield;
|
||||
if (sa.hasParam("ChoiceZone")) {
|
||||
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
|
||||
}
|
||||
Player chooser = activator;
|
||||
if (sa.hasParam("Chooser")) {
|
||||
List<Player> choosers = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Chooser"), sa);
|
||||
if (choosers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
chooser = choosers.get(0);
|
||||
}
|
||||
|
||||
CardCollection choices = new CardCollection(game.getCardsIn(choiceZone));
|
||||
|
||||
int n = sa.hasParam("ChoiceAmount") ? Integer.parseInt(sa.getParam("ChoiceAmount")) : 1;
|
||||
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, card, sa);
|
||||
|
||||
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChooseaCard") + " ";
|
||||
tgtObjects.addAll(new CardCollection(chooser.getController().chooseCardsForEffect(choices, sa, title, n, n, sa.hasParam("ChoiceOptional"))));
|
||||
} else {
|
||||
tgtObjects.addAll(getDefinedOrTargeted(sa, "Defined"));
|
||||
}
|
||||
|
||||
@@ -30,7 +30,12 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
// when damageStackDescription is called, just build exactly what is happening
|
||||
final StringBuilder stringBuilder = new StringBuilder();
|
||||
final String damage = spellAbility.getParam("NumDmg");
|
||||
final int dmg = AbilityUtils.calculateAmount(spellAbility.getHostCard(), damage, spellAbility);
|
||||
int dmg;
|
||||
try { // try-catch to fix Volcano Hellion Crash
|
||||
dmg = AbilityUtils.calculateAmount(spellAbility.getHostCard(), damage, spellAbility);
|
||||
} catch (NullPointerException e) {
|
||||
dmg = 0;
|
||||
}
|
||||
|
||||
List<GameObject> targets = SpellAbilityEffect.getTargets(spellAbility);
|
||||
if (targets.isEmpty()) {
|
||||
@@ -53,7 +58,8 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
stringBuilder.append("divided evenly (rounded down) to\n");
|
||||
} else if (spellAbility.hasParam("DividedAsYouChoose")) {
|
||||
stringBuilder.append("divided to\n");
|
||||
}
|
||||
} else
|
||||
stringBuilder.append("to ");
|
||||
|
||||
final List<Card> targetCards = SpellAbilityEffect.getTargetCards(spellAbility);
|
||||
final List<Player> players = SpellAbilityEffect.getTargetPlayers(spellAbility);
|
||||
@@ -63,7 +69,9 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
// target cards
|
||||
for (int i = 0; i < targetCards.size(); i++) {
|
||||
Card targetCard = targetCards.get(i);
|
||||
stringBuilder.append(targetCard).append(" (").append(spellAbility.getTargetRestrictions().getDividedMap().get(targetCard)).append(" damage)");
|
||||
stringBuilder.append(targetCard);
|
||||
if (spellAbility.getTargetRestrictions().getDividedMap().get(targetCard) != null) //fix null damage stack description
|
||||
stringBuilder.append(" (").append(spellAbility.getTargetRestrictions().getDividedMap().get(targetCard)).append(" damage)");
|
||||
|
||||
if (i == targetCount - 2) {
|
||||
stringBuilder.append(" and ");
|
||||
@@ -74,9 +82,10 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
|
||||
// target players
|
||||
for (int i = 0; i < players.size(); i++) {
|
||||
|
||||
Player targetPlayer = players.get(i);
|
||||
stringBuilder.append(targetPlayer).append(" (").append(spellAbility.getTargetRestrictions().getDividedMap().get(targetPlayer)).append(" damage)");
|
||||
stringBuilder.append(targetPlayer);
|
||||
if (spellAbility.getTargetRestrictions().getDividedMap().get(targetPlayer) != null) //fix null damage stack description
|
||||
stringBuilder.append(" (").append(spellAbility.getTargetRestrictions().getDividedMap().get(targetPlayer)).append(" damage)");
|
||||
|
||||
if (i == players.size() - 2) {
|
||||
stringBuilder.append(" and ");
|
||||
|
||||
@@ -70,7 +70,7 @@ public class DebuffEffect extends SpellAbilityEffect {
|
||||
for (final Card tgtC : getTargetCards(sa)) {
|
||||
final List<String> addedKW = Lists.newArrayList();
|
||||
final List<String> removedKW = Lists.newArrayList();
|
||||
if (tgtC.isInPlay() && tgtC.canBeTargetedBy(sa)) {
|
||||
if (tgtC.isInPlay() && (!sa.usesTargeting() || tgtC.canBeTargetedBy(sa))) {
|
||||
if (sa.hasParam("AllSuffixKeywords")) {
|
||||
String suffix = sa.getParam("AllSuffixKeywords");
|
||||
for (final KeywordInterface kw : tgtC.getKeywords()) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -9,9 +10,9 @@ import forge.game.ability.AbilityKey;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
@@ -19,10 +20,7 @@ import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
@@ -67,33 +65,29 @@ public class VoteEffect extends SpellAbilityEffect {
|
||||
return;
|
||||
}
|
||||
|
||||
// starting with the activator
|
||||
int pSize = tgtPlayers.size();
|
||||
Player activator = sa.getActivatingPlayer();
|
||||
while (tgtPlayers.contains(activator) && !activator.equals(Iterables.getFirst(tgtPlayers, null))) {
|
||||
tgtPlayers.add(pSize - 1, tgtPlayers.remove(0));
|
||||
|
||||
// starting with the activator
|
||||
int aidx = tgtPlayers.indexOf(activator);
|
||||
if (aidx != -1) {
|
||||
Collections.rotate(tgtPlayers, -aidx);
|
||||
}
|
||||
|
||||
ListMultimap<Object, Player> votes = ArrayListMultimap.create();
|
||||
|
||||
Player voter = null;
|
||||
|
||||
PlayerCollection voters = game.getPlayers().filter(PlayerPredicates.hasKeyword("You choose how each player votes this turn."));
|
||||
|
||||
if (voters.size() > 1) {
|
||||
List<Card> illusions = CardLists.filter(voters.getCardsIn(ZoneType.Command), CardPredicates.nameEquals("Illusion of Choice Effect"));
|
||||
voter = Collections.max(illusions, CardPredicates.compareByTimestamp()).getController();
|
||||
} else if (voters.size() == 1) {
|
||||
voter = voters.get(0);
|
||||
}
|
||||
Player voter = game.getControlVote();
|
||||
|
||||
for (final Player p : tgtPlayers) {
|
||||
int voteAmount = p.getKeywords().getAmount("You get an additional vote.") + 1;
|
||||
int optionalVotes = p.getKeywords().getAmount("You may vote an additional time.");
|
||||
voteAmount += p.getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblHowManyAdditionalVotesDoYouWant"), 0, optionalVotes);
|
||||
int voteAmount = p.getAdditionalVotesAmount() + 1;
|
||||
int optionalVotes = p.getAdditionalOptionalVotesAmount();
|
||||
Player realVoter = voter == null ? p : voter;
|
||||
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("Voter", realVoter);
|
||||
voteAmount += p.getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblHowManyAdditionalVotesDoYouWant"), 0, optionalVotes, params);
|
||||
|
||||
for (int i = 0; i < voteAmount; i++) {
|
||||
Object result = realVoter.getController().vote(sa, host + Localizer.getInstance().getMessage("lblVote") + ":", voteType, votes);
|
||||
Object result = realVoter.getController().vote(sa, host + Localizer.getInstance().getMessage("lblVote") + ":", voteType, votes, p);
|
||||
|
||||
votes.put(result, p);
|
||||
host.getGame().getAction().nofityOfValue(sa, p, result + "\r\n" + Localizer.getInstance().getMessage("lblCurrentVote") + ":" + votes, p);
|
||||
@@ -104,34 +98,49 @@ public class VoteEffect extends SpellAbilityEffect {
|
||||
runParams.put(AbilityKey.AllVotes, votes);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.Vote, runParams, false);
|
||||
|
||||
List<String> subAbs = Lists.newArrayList();
|
||||
final List<Object> mostVotes = getMostVotes(votes);
|
||||
if (sa.hasParam("Tied") && mostVotes.size() > 1) {
|
||||
subAbs.add(sa.getParam("Tied"));
|
||||
} else if (sa.hasParam("VoteSubAbility")) {
|
||||
for (final Object o : mostVotes) {
|
||||
host.addRemembered(o);
|
||||
}
|
||||
subAbs.add(sa.getParam("VoteSubAbility"));
|
||||
} else {
|
||||
for (Object type : mostVotes) {
|
||||
subAbs.add(sa.getParam("Vote" + type.toString()));
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("StoreVoteNum")) {
|
||||
for (final Object type : voteType) {
|
||||
host.setSVar("VoteNum" + type, "Number$" + votes.get(type).size());
|
||||
}
|
||||
} else {
|
||||
for (final String subAb : subAbs) {
|
||||
final SpellAbility action = AbilityFactory.getAbility(host.getSVar(subAb), host);
|
||||
if (sa.hasParam("EachVote")) {
|
||||
for (Map.Entry<Object, Collection<Player>> e : votes.asMap().entrySet()) {
|
||||
final SpellAbility action = AbilityFactory.getAbility(host, sa.getParam("Vote" + e.getKey().toString()));
|
||||
|
||||
action.setActivatingPlayer(sa.getActivatingPlayer());
|
||||
((AbilitySub) action).setParent(sa);
|
||||
AbilityUtils.resolve(action);
|
||||
|
||||
for (Player p : e.getValue()) {
|
||||
host.addRemembered(p);
|
||||
AbilityUtils.resolve(action);
|
||||
host.removeRemembered(p);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
List<String> subAbs = Lists.newArrayList();
|
||||
final List<Object> mostVotes = getMostVotes(votes);
|
||||
if (sa.hasParam("Tied") && mostVotes.size() > 1) {
|
||||
subAbs.add(sa.getParam("Tied"));
|
||||
} else if (sa.hasParam("VoteSubAbility")) {
|
||||
for (final Object o : mostVotes) {
|
||||
host.addRemembered(o);
|
||||
}
|
||||
subAbs.add(sa.getParam("VoteSubAbility"));
|
||||
} else {
|
||||
for (Object type : mostVotes) {
|
||||
subAbs.add(sa.getParam("Vote" + type.toString()));
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("StoreVoteNum")) {
|
||||
for (final Object type : voteType) {
|
||||
host.setSVar("VoteNum" + type, "Number$" + votes.get(type).size());
|
||||
}
|
||||
} else {
|
||||
for (final String subAb : subAbs) {
|
||||
final SpellAbility action = AbilityFactory.getAbility(host, subAb);
|
||||
action.setActivatingPlayer(sa.getActivatingPlayer());
|
||||
((AbilitySub) action).setParent(sa);
|
||||
AbilityUtils.resolve(action);
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("VoteSubAbility")) {
|
||||
host.clearRemembered();
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("VoteSubAbility")) {
|
||||
host.clearRemembered();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -129,6 +129,8 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
|
||||
private final Multimap<Long, Keyword> cantHaveKeywords = MultimapBuilder.hashKeys().enumSetValues(Keyword.class).build();
|
||||
|
||||
private final Map<CounterType, Long> counterTypeTimestamps = Maps.newEnumMap(CounterType.class);
|
||||
|
||||
private final Map<Long, Integer> canBlockAdditional = Maps.newTreeMap();
|
||||
private final Set<Long> canBlockAny = Sets.newHashSet();
|
||||
|
||||
@@ -220,7 +222,6 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
|
||||
private int turnInZone;
|
||||
|
||||
private int xManaCostPaid = 0;
|
||||
private Map<String, Integer> xManaCostPaidByColor;
|
||||
|
||||
private int sunburstValue = 0;
|
||||
@@ -1087,10 +1088,11 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
}
|
||||
|
||||
public final int getXManaCostPaid() {
|
||||
return xManaCostPaid;
|
||||
}
|
||||
public final void setXManaCostPaid(final int n) {
|
||||
xManaCostPaid = n;
|
||||
if (getCastSA() != null) {
|
||||
Integer paid = getCastSA().getXManaCostPaid();
|
||||
return paid == null ? 0 : paid;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public final Map<String, Integer> getXManaCostPaidByColor() {
|
||||
@@ -1305,12 +1307,44 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
getController().addCounterToPermThisTurn(counterType, addAmount);
|
||||
view.updateCounters(this);
|
||||
}
|
||||
if (newValue <= 0) {
|
||||
removeCounterTimestamp(counterType);
|
||||
} else {
|
||||
addCounterTimestamp(counterType);
|
||||
}
|
||||
if (table != null) {
|
||||
table.put(this, counterType, addAmount);
|
||||
}
|
||||
return addAmount;
|
||||
}
|
||||
|
||||
public boolean addCounterTimestamp(CounterType counterType) {
|
||||
return addCounterTimestamp(counterType, true);
|
||||
}
|
||||
public boolean addCounterTimestamp(CounterType counterType, boolean updateView) {
|
||||
if (!counterType.isKeywordCounter()) {
|
||||
return false;
|
||||
}
|
||||
removeCounterTimestamp(counterType);
|
||||
|
||||
long timestamp = game.getNextTimestamp();
|
||||
counterTypeTimestamps.put(counterType, timestamp);
|
||||
addChangedCardKeywords(ImmutableList.of(counterType.getKeyword().toString()), null, false, false, timestamp, updateView);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean removeCounterTimestamp(CounterType counterType) {
|
||||
return removeCounterTimestamp(counterType, true);
|
||||
}
|
||||
|
||||
public boolean removeCounterTimestamp(CounterType counterType, boolean updateView) {
|
||||
Long old = counterTypeTimestamps.remove(counterType);
|
||||
if (old != null) {
|
||||
removeChangedCardKeywords(old, updateView);
|
||||
}
|
||||
return old != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* addCountersAddedBy.
|
||||
@@ -1358,6 +1392,10 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
setCounters(counterName, newValue);
|
||||
view.updateCounters(this);
|
||||
|
||||
if (newValue <= 0) {
|
||||
this.removeCounterTimestamp(counterName);
|
||||
}
|
||||
|
||||
//fire card stats changed event if p/t bonuses or loyalty changed from subtracted counters
|
||||
if (powerBonusBefore != getPowerBonusFromCounters() || toughnessBonusBefore != getToughnessBonusFromCounters() || loyaltyBefore != getCurrentLoyalty()) {
|
||||
getGame().fireEvent(new GameEventCardStatsChanged(this));
|
||||
@@ -1380,8 +1418,23 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
|
||||
@Override
|
||||
public final void setCounters(final Map<CounterType, Integer> allCounters) {
|
||||
boolean changed = false;
|
||||
for (CounterType ct : counters.keySet()) {
|
||||
if (removeCounterTimestamp(ct, false)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
counters = allCounters;
|
||||
view.updateCounters(this);
|
||||
|
||||
for (CounterType ct : counters.keySet()) {
|
||||
if (addCounterTimestamp(ct, false)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
updateKeywords();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1389,6 +1442,16 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
if (counters.isEmpty()) { return; }
|
||||
counters.clear();
|
||||
view.updateCounters(this);
|
||||
|
||||
boolean changed = false;
|
||||
for (CounterType ct : counterTypeTimestamps.keySet()) {
|
||||
if (removeCounterTimestamp(ct, false)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
updateKeywords();
|
||||
}
|
||||
}
|
||||
|
||||
public final String getSVar(final String var) {
|
||||
@@ -3938,7 +4001,9 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
keywordsGrantedByTextChanges.add(newKw);
|
||||
}
|
||||
}
|
||||
addChangedCardKeywordsInternal(addKeywords, removeKeywords, false, false, timestamp, true);
|
||||
if (!addKeywords.isEmpty() || !removeKeywords.isEmpty()) {
|
||||
addChangedCardKeywordsInternal(addKeywords, removeKeywords, false, false, timestamp, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateKeywordsOnRemoveChangedText(final KeywordsChange k) {
|
||||
@@ -4729,9 +4794,21 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
return false;
|
||||
}
|
||||
|
||||
// this is the amount of damage a creature needs to receive before it dies
|
||||
public final int getLethal() {
|
||||
if (getAmountOfKeyword("Lethal damage dealt to CARDNAME is determined by its power rather than its toughness.") % 2 !=0) {
|
||||
return getNetPower(); }
|
||||
else {
|
||||
return getNetToughness(); }
|
||||
}
|
||||
|
||||
// this is the minimal damage a trampling creature has to assign to a blocker
|
||||
public final int getLethalDamage() {
|
||||
return getNetToughness() - getDamage() - getTotalAssignedDamage();
|
||||
if (getAmountOfKeyword("Lethal damage dealt to CARDNAME is determined by its power rather than its toughness.") % 2 !=0) {
|
||||
return getNetPower() - getDamage() - getTotalAssignedDamage();
|
||||
}
|
||||
else {
|
||||
return getNetToughness() - getDamage() - getTotalAssignedDamage();}
|
||||
}
|
||||
|
||||
public final int getDamage() {
|
||||
@@ -6273,10 +6350,13 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
|
||||
public final void addGoad(Long timestamp, final Player p) {
|
||||
goad.put(timestamp, p);
|
||||
updateAbilityTextForView();
|
||||
}
|
||||
|
||||
public final void removeGoad(Long timestamp) {
|
||||
goad.remove(timestamp);
|
||||
if (goad.remove(timestamp) != null) {
|
||||
updateAbilityTextForView();
|
||||
}
|
||||
}
|
||||
|
||||
public final boolean isGoaded() {
|
||||
@@ -6353,6 +6433,9 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
removeSVar("PayX"); // Temporary AI X announcement variable
|
||||
removeSVar("IsCastFromPlayEffect"); // Temporary SVar indicating that the spell is cast indirectly via AF Play
|
||||
setSunburstValue(0); // Sunburst
|
||||
setXManaCostPaidByColor(null);
|
||||
setKickerMagnitude(0);
|
||||
setPseudoMultiKickerMagnitude(0);
|
||||
}
|
||||
|
||||
public final int getFinalChapterNr() {
|
||||
|
||||
@@ -185,18 +185,12 @@ public class CardFactory {
|
||||
c.setCopiedSpell(true);
|
||||
|
||||
if (bCopyDetails) {
|
||||
c.setXManaCostPaid(original.getXManaCostPaid());
|
||||
c.setXManaCostPaidByColor(original.getXManaCostPaidByColor());
|
||||
c.setKickerMagnitude(original.getKickerMagnitude());
|
||||
|
||||
// Rule 706.10 : Madness is copied
|
||||
if (original.isInZone(ZoneType.Stack)) {
|
||||
c.setMadness(original.isMadness());
|
||||
|
||||
final SpellAbilityStackInstance si = controller.getGame().getStack().getInstanceFromSpellAbility(sa);
|
||||
if (si != null) {
|
||||
c.setXManaCostPaid(si.getXManaPaid());
|
||||
}
|
||||
}
|
||||
|
||||
for (OptionalCost cost : original.getOptionalCostsPaid()) {
|
||||
@@ -875,6 +869,13 @@ public class CardFactory {
|
||||
}
|
||||
}
|
||||
|
||||
for (final Trigger trigger : state.getTriggers()) {
|
||||
final SpellAbility newSa = trigger.getOverridingAbility();
|
||||
if (newSa != null && newSa.getOriginalHost() == null) {
|
||||
newSa.setOriginalHost(in);
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("GainTextOf") && originalState != null) {
|
||||
state.setSetCode(originalState.getSetCode());
|
||||
state.setRarity(originalState.getRarity());
|
||||
|
||||
@@ -20,7 +20,6 @@ package forge.game.card;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
@@ -3010,24 +3009,43 @@ public class CardFactoryUtil {
|
||||
|
||||
inst.addTrigger(parsedTrigger);
|
||||
} else if (keyword.startsWith("Saga")) {
|
||||
// Saga there doesn't need Max value anymore?
|
||||
final String[] k = keyword.split(":");
|
||||
final String[] abs = k[2].split(",");
|
||||
final List<String> abs = Arrays.asList(k[2].split(","));
|
||||
if (abs.size() != Integer.valueOf(k[1])) {
|
||||
throw new RuntimeException("Saga max differ from Ability amount");
|
||||
}
|
||||
|
||||
int i = 1;
|
||||
for (String ab : abs) {
|
||||
SpellAbility sa = AbilityFactory.getAbility(card, ab);
|
||||
sa.setChapter(i);
|
||||
int idx = 0;
|
||||
int skipId = 0;
|
||||
for(String ab : abs) {
|
||||
idx += 1;
|
||||
if (idx <= skipId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO better logic for Roman numbers
|
||||
// In the Description try to merge Chapter trigger with the Same Effect
|
||||
String trigStr = "Mode$ CounterAdded | ValidCard$ Card.Self | TriggerZones$ Battlefield"
|
||||
+ "| CounterType$ LORE | CounterAmount$ EQ" + i
|
||||
+ "| TriggerDescription$ " + Strings.repeat("I", i) + " - " + sa.getDescription();
|
||||
final Trigger t = TriggerHandler.parseTrigger(trigStr, card, intrinsic);
|
||||
t.setOverridingAbility(sa);
|
||||
inst.addTrigger(t);
|
||||
++i;
|
||||
skipId = idx + abs.subList(idx - 1, abs.size()).lastIndexOf(ab);
|
||||
StringBuilder desc = new StringBuilder();
|
||||
for (int i = idx; i <= skipId; i++) {
|
||||
if (i != idx) {
|
||||
desc.append(", ");
|
||||
}
|
||||
desc.append(TextUtil.toRoman(i));
|
||||
}
|
||||
|
||||
for (int i = idx; i <= skipId; i++) {
|
||||
SpellAbility sa = AbilityFactory.getAbility(card, ab);
|
||||
sa.setChapter(i);
|
||||
|
||||
StringBuilder trigStr = new StringBuilder("Mode$ CounterAdded | ValidCard$ Card.Self | TriggerZones$ Battlefield");
|
||||
trigStr.append("| CounterType$ LORE | CounterAmount$ EQ").append(i);
|
||||
if (i != idx) {
|
||||
trigStr.append(" | Secondary$ True");
|
||||
}
|
||||
trigStr.append("| TriggerDescription$ ").append(desc).append(" — ").append(sa.getDescription());
|
||||
final Trigger t = TriggerHandler.parseTrigger(trigStr.toString(), card, intrinsic);
|
||||
t.setOverridingAbility(sa);
|
||||
inst.addTrigger(t);
|
||||
}
|
||||
}
|
||||
} else if (keyword.equals("Soulbond")) {
|
||||
// Setup ETB trigger for card with Soulbond keyword
|
||||
|
||||
@@ -1518,27 +1518,38 @@ public class CardProperty {
|
||||
} else if (property.startsWith("blockedByThisTurn")) {
|
||||
return !card.getBlockedByThisTurn().isEmpty();
|
||||
} else if (property.startsWith("blockedValidThisTurn ")) {
|
||||
if (card.getBlockedThisTurn() == null) {
|
||||
CardCollectionView blocked = card.getBlockedThisTurn();
|
||||
if (blocked == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String valid = property.split(" ")[1];
|
||||
for(Card c : card.getBlockedThisTurn()) {
|
||||
for(Card c : blocked) {
|
||||
if (c.isValid(valid, card.getController(), source, spellAbility)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for(Card c : AbilityUtils.getDefinedCards(source, valid, spellAbility)) {
|
||||
if (blocked.contains(c)) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return false;
|
||||
} else if (property.startsWith("blockedByValidThisTurn ")) {
|
||||
if (card.getBlockedByThisTurn() == null) {
|
||||
CardCollectionView blocked = card.getBlockedByThisTurn();
|
||||
if (blocked == null) {
|
||||
return false;
|
||||
}
|
||||
String valid = property.split(" ")[1];
|
||||
for(Card c : card.getBlockedByThisTurn()) {
|
||||
for(Card c : blocked) {
|
||||
if (c.isValid(valid, card.getController(), source, spellAbility)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for(Card c : AbilityUtils.getDefinedCards(source, valid, spellAbility)) {
|
||||
if (blocked.contains(c)) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return false;
|
||||
} else if (property.startsWith("blockedBySourceThisTurn")) {
|
||||
return source.getBlockedByThisTurn().contains(card);
|
||||
@@ -1779,4 +1790,4 @@ public class CardProperty {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ package forge.game.card;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import forge.game.keyword.Keyword;
|
||||
|
||||
/**
|
||||
* The class Counters.
|
||||
*
|
||||
@@ -109,6 +111,8 @@ public enum CounterType {
|
||||
|
||||
FLOOD("FLOOD", 0, 203, 255),
|
||||
|
||||
FORESHADOW("FRSHD",144,99, 207),
|
||||
|
||||
FUNGUS("FNGUS", 121, 219, 151),
|
||||
|
||||
FURY("FURY", 255, 120, 89),
|
||||
@@ -309,7 +313,23 @@ public enum CounterType {
|
||||
|
||||
EXPERIENCE("EXP"),
|
||||
|
||||
POISON("POISN");
|
||||
POISON("POISN"),
|
||||
|
||||
// Keyword Counters
|
||||
|
||||
FLYING("Flying"),
|
||||
FIRSTSTRIKE("First Strike"),
|
||||
DOUBLESTRIKE("Double Strike"),
|
||||
DEATHTOUCH("Deathtouch"),
|
||||
HEXPROOF("Hexproof"),
|
||||
INDESTRUCTIBLE("Indestructible"),
|
||||
LIFELINK("Lifelink"),
|
||||
MENACE("Menace"),
|
||||
REACH("Reach"),
|
||||
TRAMPLE("Trample"),
|
||||
VIGILANCE("Vigilance")
|
||||
|
||||
;
|
||||
|
||||
private String name, counterOnCardDisplayName;
|
||||
private int red, green, blue;
|
||||
@@ -365,6 +385,39 @@ public enum CounterType {
|
||||
return Enum.valueOf(CounterType.class, replacedName);
|
||||
}
|
||||
|
||||
public boolean isKeywordCounter() {
|
||||
return this.getKeyword() != null;
|
||||
}
|
||||
|
||||
public Keyword getKeyword() {
|
||||
switch (this) {
|
||||
case FLYING:
|
||||
return Keyword.FLYING;
|
||||
case FIRSTSTRIKE:
|
||||
return Keyword.FIRST_STRIKE;
|
||||
case DOUBLESTRIKE:
|
||||
return Keyword.DOUBLE_STRIKE;
|
||||
case DEATHTOUCH:
|
||||
return Keyword.DEATHTOUCH;
|
||||
case HEXPROOF:
|
||||
return Keyword.HEXPROOF;
|
||||
case INDESTRUCTIBLE:
|
||||
return Keyword.INDESTRUCTIBLE;
|
||||
case LIFELINK:
|
||||
return Keyword.LIFELINK;
|
||||
case MENACE:
|
||||
return Keyword.MENACE;
|
||||
case REACH:
|
||||
return Keyword.REACH;
|
||||
case TRAMPLE:
|
||||
return Keyword.TRAMPLE;
|
||||
case VIGILANCE:
|
||||
return Keyword.VIGILANCE;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static final ImmutableList<CounterType> values = ImmutableList.copyOf(values());
|
||||
|
||||
}
|
||||
|
||||
@@ -882,6 +882,10 @@ public class Combat {
|
||||
return true; // is blocking something at the moment
|
||||
}
|
||||
|
||||
if (!blocker.isLKI()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CombatLki lki = lkiCache.get(blocker);
|
||||
return null != lki && !lki.isAttacker; // was blocking something anyway
|
||||
}
|
||||
@@ -892,7 +896,11 @@ public class Combat {
|
||||
if (blockers != null && blockers.contains(blocker)) {
|
||||
return true; // is blocking the attacker's band at the moment
|
||||
}
|
||||
|
||||
|
||||
if (!blocker.isLKI()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CombatLki lki = lkiCache.get(blocker);
|
||||
return null != lki && !lki.isAttacker && lki.relatedBands.contains(ab); // was blocking that very band
|
||||
}
|
||||
|
||||
@@ -122,8 +122,10 @@ public abstract class CostPartWithList extends CostPart {
|
||||
|
||||
// always returns true, made this to inline with return
|
||||
public boolean executePayment(SpellAbility ability, CardCollectionView targetCards) {
|
||||
if (canPayListAtOnce()) { // This is used by reveal. Without it when opponent would reveal hand, you'll get N message boxes.
|
||||
lkiList.addAll(targetCards);
|
||||
if (canPayListAtOnce()) { // This is used by reveal. Without it when opponent would reveal hand, you'll get N message boxes.
|
||||
for (Card c: targetCards) {
|
||||
lkiList.add(CardUtil.getLKICopy(c));
|
||||
}
|
||||
cardList.addAll(doListPayment(ability, targetCards));
|
||||
handleChangeZoneTrigger(ability);
|
||||
return true;
|
||||
|
||||
@@ -127,7 +127,7 @@ public enum Keyword {
|
||||
SHROUD("Shroud", SimpleKeyword.class, true, "This can't be the target of spells or abilities."),
|
||||
SKULK("Skulk", SimpleKeyword.class, true, "This creature can't be blocked by creatures with greater power."),
|
||||
SCAVENGE("Scavenge", KeywordWithCost.class, false, "%s, Exile this card from your graveyard: Put a number of +1/+1 counters equal to this card's power on target creature. Scavenge only as a sorcery."),
|
||||
SOULBOND("Souldbond", SimpleKeyword.class, true, "You may pair this creature with another unpaired creature when either enters the battlefield. They remain paired for as long as you control both of them"),
|
||||
SOULBOND("Soulbond", SimpleKeyword.class, true, "You may pair this creature with another unpaired creature when either enters the battlefield. They remain paired for as long as you control both of them."),
|
||||
SOULSHIFT("Soulshift", KeywordWithAmount.class, false, "When this creature dies, you may return target Spirit card with converted mana cost %d or less from your graveyard to your hand."),
|
||||
SPECTACLE("Spectacle", KeywordWithCost.class, false, "You may cast this spell for its spectacle cost rather than its mana cost if an opponent lost life this turn."),
|
||||
SPLICE("Splice", KeywordWithCostAndType.class, false, "As you cast an %2$s spell, you may reveal this card from your hand and pay its splice cost. If you do, add this card's effects to that spell."),
|
||||
|
||||
@@ -254,7 +254,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
|
||||
}
|
||||
}
|
||||
if (mana.addsCounters(sa)) {
|
||||
mana.getManaAbility().createETBCounters(host);
|
||||
mana.getManaAbility().createETBCounters(host, this.owner);
|
||||
}
|
||||
if (mana.triggersWhenSpent()) {
|
||||
mana.getManaAbility().addTriggersWhenSpent(sa, host);
|
||||
|
||||
@@ -55,6 +55,11 @@ public enum PhaseType {
|
||||
return result;
|
||||
}
|
||||
|
||||
public final boolean isCombatPhase() {
|
||||
return ((ALL_PHASES.indexOf(this) >= ALL_PHASES.indexOf(COMBAT_BEGIN))
|
||||
&& (ALL_PHASES.indexOf(this) <= ALL_PHASES.indexOf(COMBAT_END)));
|
||||
}
|
||||
|
||||
public final boolean isAfter(final PhaseType phase) {
|
||||
return ALL_PHASES.indexOf(this) > ALL_PHASES.indexOf(phase);
|
||||
}
|
||||
|
||||
@@ -162,6 +162,10 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
private Card blessingEffect = null;
|
||||
private Card keywordEffect = null;
|
||||
|
||||
private Map<Long, Integer> additionalVotes = Maps.newHashMap();
|
||||
private Map<Long, Integer> additionalOptionalVotes = Maps.newHashMap();
|
||||
private SortedSet<Long> controlVotes = Sets.newTreeSet();
|
||||
|
||||
private final AchievementTracker achievementTracker = new AchievementTracker();
|
||||
private final PlayerView view;
|
||||
|
||||
@@ -2521,6 +2525,10 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
|
||||
public boolean isSkippingCombat() {
|
||||
if (hasLost()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasKeyword("Skip your next combat phase.")) {
|
||||
return true;
|
||||
}
|
||||
@@ -3067,4 +3075,86 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
this.updateZoneForView(com);
|
||||
return keywordEffect;
|
||||
}
|
||||
|
||||
public void addAdditionalVote(long timestamp, int value) {
|
||||
additionalVotes.put(timestamp, value);
|
||||
getView().updateAdditionalVote(this);
|
||||
getGame().fireEvent(new GameEventPlayerStatsChanged(this, false));
|
||||
}
|
||||
|
||||
public void removeAdditionalVote(long timestamp) {
|
||||
if (additionalVotes.remove(timestamp) != null) {
|
||||
getView().updateAdditionalVote(this);
|
||||
getGame().fireEvent(new GameEventPlayerStatsChanged(this, false));
|
||||
}
|
||||
}
|
||||
|
||||
public int getAdditionalVotesAmount() {
|
||||
int value = 0;
|
||||
for (Integer i : additionalVotes.values()) {
|
||||
value += i;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public void addAdditionalOptionalVote(long timestamp, int value) {
|
||||
additionalOptionalVotes.put(timestamp, value);
|
||||
getView().updateOptionalAdditionalVote(this);
|
||||
getGame().fireEvent(new GameEventPlayerStatsChanged(this, false));
|
||||
}
|
||||
public void removeAdditionalOptionalVote(long timestamp) {
|
||||
if (additionalOptionalVotes.remove(timestamp) != null) {
|
||||
getView().updateOptionalAdditionalVote(this);
|
||||
getGame().fireEvent(new GameEventPlayerStatsChanged(this, false));
|
||||
}
|
||||
}
|
||||
|
||||
public int getAdditionalOptionalVotesAmount() {
|
||||
int value = 0;
|
||||
for (Integer i : additionalOptionalVotes.values()) {
|
||||
value += i;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean addControlVote(long timestamp) {
|
||||
if (controlVotes.add(timestamp)) {
|
||||
updateControlVote();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public boolean removeControlVote(long timestamp) {
|
||||
if (controlVotes.remove(timestamp)) {
|
||||
updateControlVote();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void updateControlVote() {
|
||||
// need to update all players because it can't know
|
||||
Player control = getGame().getControlVote();
|
||||
for (Player pl : getGame().getPlayers()) {
|
||||
pl.getView().updateControlVote(pl.equals(control));
|
||||
getGame().fireEvent(new GameEventPlayerStatsChanged(pl, false));
|
||||
}
|
||||
}
|
||||
|
||||
public Set<Long> getControlVote() {
|
||||
return controlVotes;
|
||||
}
|
||||
|
||||
public void setControlVote(Set<Long> value) {
|
||||
controlVotes.clear();
|
||||
controlVotes.addAll(value);
|
||||
updateControlVote();
|
||||
}
|
||||
|
||||
public Long getHighestControlVote() {
|
||||
if (controlVotes.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return controlVotes.last();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ public abstract class PlayerController {
|
||||
return chooseSomeType(kindOfType, sa, validTypes, invalidTypes, false);
|
||||
}
|
||||
|
||||
public abstract Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes);
|
||||
public abstract Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer);
|
||||
public abstract boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question);
|
||||
|
||||
public abstract CardCollectionView getCardsToMulligan(Player firstPlayer);
|
||||
|
||||
@@ -271,6 +271,27 @@ public class PlayerView extends GameEntityView {
|
||||
set(TrackableProperty.NumDrawnThisTurn, p.getNumDrawnThisTurn());
|
||||
}
|
||||
|
||||
public int getAdditionalVote() {
|
||||
return get(TrackableProperty.AdditionalVote);
|
||||
}
|
||||
public void updateAdditionalVote(Player p) {
|
||||
set(TrackableProperty.AdditionalVote, p.getAdditionalVotesAmount());
|
||||
}
|
||||
|
||||
public int getOptionalAdditionalVote() {
|
||||
return get(TrackableProperty.OptionalAdditionalVote);
|
||||
}
|
||||
public void updateOptionalAdditionalVote(Player p) {
|
||||
set(TrackableProperty.OptionalAdditionalVote, p.getAdditionalOptionalVotesAmount());
|
||||
}
|
||||
|
||||
public boolean getControlVote() {
|
||||
return get(TrackableProperty.ControlVotes);
|
||||
}
|
||||
public void updateControlVote(boolean val) {
|
||||
set(TrackableProperty.ControlVotes, val);
|
||||
}
|
||||
|
||||
public ImmutableMultiset<String> getKeywords() {
|
||||
return get(TrackableProperty.Keywords);
|
||||
}
|
||||
@@ -497,6 +518,19 @@ public class PlayerView extends GameEntityView {
|
||||
details.add(Localizer.getInstance().getMessage("lblCardDrawnThisTurnHas", String.valueOf(getNumDrawnThisTurn())));
|
||||
details.add(Localizer.getInstance().getMessage("lblDamagepreventionHas", String.valueOf(getPreventNextDamage())));
|
||||
|
||||
int v = getAdditionalVote();
|
||||
if (v > 0) {
|
||||
details.add(Localizer.getInstance().getMessage("lblAdditionalVotes", String.valueOf(v)));
|
||||
}
|
||||
v = getOptionalAdditionalVote();
|
||||
if (v > 0) {
|
||||
details.add(Localizer.getInstance().getMessage("lblOptionalAdditionalVotes", String.valueOf(v)));
|
||||
}
|
||||
|
||||
if (getControlVote()) {
|
||||
details.add(Localizer.getInstance().getMessage("lblControlsVote"));
|
||||
}
|
||||
|
||||
if (getIsExtraTurn()) {
|
||||
details.add(Localizer.getInstance().getMessage("lblIsExtraTurn"));
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@@ -19,9 +19,11 @@ package forge.game.spellability;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaAtom;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.card.Card;
|
||||
@@ -34,6 +36,8 @@ import forge.game.replacement.*;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerHandler;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Lang;
|
||||
import forge.util.TextUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@@ -46,7 +50,7 @@ import java.util.regex.Pattern;
|
||||
* <p>
|
||||
* Abstract AbilityMana class.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id$
|
||||
*/
|
||||
@@ -78,7 +82,7 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
* <p>
|
||||
* Constructor for AbilityMana.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @param sourceCard
|
||||
* a {@link forge.game.card.Card} object.
|
||||
*/
|
||||
@@ -111,7 +115,7 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
* <p>
|
||||
* produceMana.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @param produced
|
||||
* a {@link java.lang.String} object.
|
||||
* @param player
|
||||
@@ -169,7 +173,7 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
* cannotCounterPaidWith.
|
||||
* </p>
|
||||
* @param saBeingPaid
|
||||
*
|
||||
*
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
public boolean cannotCounterPaidWith(SpellAbility saBeingPaid) {
|
||||
@@ -186,7 +190,7 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
* addKeywords.
|
||||
* </p>
|
||||
* @param saBeingPaid
|
||||
*
|
||||
*
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
public boolean addKeywords(SpellAbility saBeingPaid) {
|
||||
@@ -205,7 +209,7 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
* <p>
|
||||
* getKeywords.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
public String getKeywords() {
|
||||
@@ -217,7 +221,7 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
* addsCounters.
|
||||
* </p>
|
||||
* @param saBeingPaid
|
||||
*
|
||||
*
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
public boolean addsCounters(SpellAbility saBeingPaid) {
|
||||
@@ -227,10 +231,26 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
/**
|
||||
* createETBCounters
|
||||
*/
|
||||
public void createETBCounters(Card c) {
|
||||
public void createETBCounters(Card c, Player controller) {
|
||||
String[] parse = this.addsCounters.split("_");
|
||||
// Convert random SVars if there are other cards with this effect
|
||||
if (c.isValid(parse[0], c.getController(), c, null)) {
|
||||
final Game game = this.sourceCard.getGame();
|
||||
final Card eff = new Card(game.nextCardId(), game);
|
||||
eff.setTimestamp(game.getNextTimestamp());
|
||||
eff.setName(sourceCard.getName() + "'s Effect");
|
||||
eff.addType("Effect");
|
||||
eff.setToken(true); // Set token to true, so when leaving play it gets nuked
|
||||
eff.setOwner(controller);
|
||||
|
||||
eff.setImageKey(sourceCard.getImageKey());
|
||||
eff.setColor(MagicColor.COLORLESS);
|
||||
eff.setImmutable(true);
|
||||
// try to get the SpellAbility from the mana ability
|
||||
//eff.setEffectSource((SpellAbility)null);
|
||||
|
||||
eff.addRemembered(c);
|
||||
|
||||
String abStr = "DB$ PutCounter | Defined$ ReplacedCard | CounterType$ " + parse[1]
|
||||
+ " | ETB$ True | CounterNum$ " + parse[2];
|
||||
|
||||
@@ -240,15 +260,37 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
}
|
||||
CardFactoryUtil.setupETBReplacementAbility(sa);
|
||||
|
||||
String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield "
|
||||
+ " | Secondary$ True | Description$ CARDNAME"
|
||||
+ " enters the battlefield with " + CounterType.valueOf(parse[1]).getName() + " counters.";
|
||||
String desc = "It enters the battlefield with ";
|
||||
desc += Lang.nounWithNumeral(parse[2], CounterType.valueOf(parse[1]).getName() + " counter");
|
||||
desc += " on it.";
|
||||
|
||||
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, c, false);
|
||||
String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered | Destination$ Battlefield | Description$ " + desc;
|
||||
|
||||
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);
|
||||
re.setLayer(ReplacementLayer.Other);
|
||||
re.setOverridingAbility(sa);
|
||||
|
||||
c.addReplacementEffect(re);
|
||||
eff.addReplacementEffect(re);
|
||||
|
||||
// Forgot Trigger
|
||||
String trig = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Stack | Destination$ Any | TriggerZones$ Command | Static$ True";
|
||||
String forgetEffect = "DB$ Pump | ForgetObjects$ TriggeredCard";
|
||||
String exileEffect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile"
|
||||
+ " | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0";
|
||||
|
||||
SpellAbility saForget = AbilityFactory.getAbility(forgetEffect, eff);
|
||||
AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(exileEffect, eff);
|
||||
saForget.setSubAbility(saExile);
|
||||
|
||||
final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, eff, true);
|
||||
parsedTrigger.setOverridingAbility(saForget);
|
||||
eff.addTrigger(parsedTrigger);
|
||||
eff.updateStateForView();
|
||||
|
||||
// TODO: Add targeting to the effect so it knows who it's dealing with
|
||||
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
|
||||
game.getAction().moveTo(ZoneType.Command, eff, null);
|
||||
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +311,7 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
* <p>
|
||||
* getManaRestrictions.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
public String getManaRestrictions() {
|
||||
@@ -280,7 +322,7 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
* <p>
|
||||
* meetsManaRestrictions.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @return a boolean.
|
||||
@@ -296,7 +338,7 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
if (restriction.equals("nonSpell")) {
|
||||
return !sa.isSpell();
|
||||
}
|
||||
|
||||
|
||||
if (restriction.equals("CumulativeUpkeep")) {
|
||||
if (sa.isCumulativeupkeep()) {
|
||||
return true;
|
||||
@@ -349,7 +391,7 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
* <p>
|
||||
* mana.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
public final String mana() {
|
||||
@@ -438,7 +480,7 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
* <p>
|
||||
* canProduce.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @param s
|
||||
* a {@link java.lang.String} object.
|
||||
* @return a boolean.
|
||||
@@ -468,7 +510,7 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
* <p>
|
||||
* isBasic.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
public final boolean isBasic() {
|
||||
@@ -541,7 +583,7 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
public Card getSourceCard() {
|
||||
return sourceCard;
|
||||
}
|
||||
|
||||
|
||||
public void setSourceCard(final Card host) {
|
||||
sourceCard = host;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardDamageMap;
|
||||
import forge.game.card.CardFactory;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostPart;
|
||||
@@ -46,6 +47,7 @@ import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.trigger.WrappedAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.Expressions;
|
||||
import forge.util.TextUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -130,6 +132,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
|
||||
private final List<Mana> payingMana = Lists.newArrayList();
|
||||
private final List<SpellAbility> paidAbilities = Lists.newArrayList();
|
||||
private Integer xManaCostPaid = null;
|
||||
|
||||
private HashMap<String, CardCollection> paidLists = Maps.newHashMap();
|
||||
|
||||
@@ -447,6 +450,23 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
payCosts = abCost;
|
||||
}
|
||||
|
||||
public boolean costHasX() {
|
||||
if (getPayCosts() == null) {
|
||||
return false;
|
||||
}
|
||||
return getPayCosts().hasXInAnyCostPart();
|
||||
}
|
||||
|
||||
public boolean costHasManaX() {
|
||||
if (getPayCosts() == null) {
|
||||
return false;
|
||||
}
|
||||
if (getPayCosts().hasNoManaCost()) {
|
||||
return false;
|
||||
}
|
||||
return getPayCosts().getCostMana().getAmountOfX() > 0;
|
||||
}
|
||||
|
||||
public SpellAbilityRestriction getRestrictions() {
|
||||
return restrictions;
|
||||
}
|
||||
@@ -1081,8 +1101,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
}
|
||||
|
||||
if (hasParam("MaxTotalTargetCMC") && entity instanceof Card) {
|
||||
final Card c = (Card) entity;
|
||||
if (c.getCMC() > tr.getMaxTotalCMC(c, this)) {
|
||||
int soFar = Aggregates.sum(getTargets().getTargetCards(), CardPredicates.Accessors.fnGetCmc);
|
||||
// only add if it isn't already targeting
|
||||
if (!isTargeting(entity)) {
|
||||
final Card c = (Card) entity;
|
||||
soFar += c.getCMC();
|
||||
}
|
||||
|
||||
if (soFar > tr.getMaxTotalCMC(getHostCard(), this)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1299,6 +1325,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
String announce = getParam("Announce");
|
||||
if (StringUtils.isBlank(announce)) {
|
||||
mapParams.put("Announce", variable);
|
||||
originalMapParams.put("Announce", variable);
|
||||
return;
|
||||
}
|
||||
String[] announcedOnes = TextUtil.split(announce, ',');
|
||||
@@ -1308,6 +1335,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
}
|
||||
}
|
||||
mapParams.put("Announce", announce + ";" + variable);
|
||||
originalMapParams.put("Announce", announce + ";" + variable);
|
||||
}
|
||||
|
||||
public boolean isXCost() {
|
||||
@@ -1961,4 +1989,11 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
public void setAlternativeCost(AlternativeCost ac) {
|
||||
altCost = ac;
|
||||
}
|
||||
|
||||
public Integer getXManaCostPaid() {
|
||||
return xManaCostPaid;
|
||||
}
|
||||
public void setXManaCostPaid(final Integer n) {
|
||||
xManaCostPaid = n;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +197,10 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
|
||||
this.setManaSpent(params.get("ConditionManaSpent"));
|
||||
}
|
||||
|
||||
if (params.containsKey("ConditionManaNotSpent")) {
|
||||
this.setManaNotSpent(params.get("ConditionManaNotSpent"));
|
||||
}
|
||||
|
||||
if (params.containsKey("ConditionCheckSVar")) {
|
||||
this.setSvarToCheck(params.get("ConditionCheckSVar"));
|
||||
}
|
||||
@@ -432,10 +436,21 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(this.getManaSpent())) {
|
||||
byte manaSpent = MagicColor.fromName(getManaSpent()); // they always check for single color
|
||||
if( 0 == (manaSpent & sa.getHostCard().getColorsPaid())) // no match of colors
|
||||
if (StringUtils.isNotEmpty(getManaSpent())) {
|
||||
for (String s : getManaSpent().split(" ")) {
|
||||
byte manaSpent = MagicColor.fromName(s);
|
||||
if( 0 == (manaSpent & sa.getHostCard().getColorsPaid())) // no match of colors
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (StringUtils.isNotEmpty(getManaNotSpent())) {
|
||||
byte toPay = 0;
|
||||
for (String s : getManaNotSpent().split(" ")) {
|
||||
toPay |= MagicColor.fromName(s);
|
||||
}
|
||||
if (toPay == (toPay & sa.getHostCard().getColorsPaid())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.getsVarToCheck() != null) {
|
||||
|
||||
@@ -83,7 +83,7 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
|
||||
// private ArrayList<Mana> payingMana = new ArrayList<Mana>();
|
||||
// private ArrayList<AbilityMana> paidAbilities = new
|
||||
// ArrayList<AbilityMana>();
|
||||
private int xManaPaid = 0;
|
||||
private Integer xManaPaid = null;
|
||||
|
||||
// Other Paid things
|
||||
private final HashMap<String, CardCollection> paidHash;
|
||||
@@ -116,7 +116,7 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
|
||||
splicedCards = sa.getSplicedCards();
|
||||
|
||||
// TODO getXManaCostPaid should be on the SA, not the Card
|
||||
xManaPaid = sa.getHostCard().getXManaCostPaid();
|
||||
xManaPaid = sa.getXManaCostPaid();
|
||||
|
||||
// Triggering info
|
||||
triggeringObjects = sa.getTriggeringObjects();
|
||||
@@ -200,7 +200,7 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
|
||||
// Set Cost specific things here
|
||||
ability.setPaidHash(paidHash);
|
||||
ability.setSplicedCards(splicedCards);
|
||||
ability.getHostCard().setXManaCostPaid(xManaPaid);
|
||||
ability.setXManaCostPaid(xManaPaid);
|
||||
|
||||
// Triggered
|
||||
ability.setTriggeringObjects(triggeringObjects);
|
||||
|
||||
@@ -89,6 +89,7 @@ public class SpellAbilityVariables implements Cloneable {
|
||||
this.lifeTotal = sav.getLifeTotal();
|
||||
this.lifeAmount = sav.getLifeAmount();
|
||||
this.manaSpent = sav.getManaSpent();
|
||||
this.manaNotSpent = sav.getManaNotSpent();
|
||||
this.targetValidTargeting = sav.getTargetValidTargeting();
|
||||
this.targetsSingleTarget = sav.targetsSingleTarget();
|
||||
this.presenceCondition = sav.getPresenceCondition();
|
||||
@@ -193,6 +194,7 @@ public class SpellAbilityVariables implements Cloneable {
|
||||
|
||||
/** The mana spent. */
|
||||
private String manaSpent = "";
|
||||
private String manaNotSpent = "";
|
||||
|
||||
/** The chosen colors string. */
|
||||
private String chosenColors = null;
|
||||
@@ -229,6 +231,13 @@ public class SpellAbilityVariables implements Cloneable {
|
||||
return this.manaSpent;
|
||||
}
|
||||
|
||||
public final void setManaNotSpent(final String s) {
|
||||
this.manaNotSpent = s;
|
||||
}
|
||||
public final String getManaNotSpent() {
|
||||
return this.manaNotSpent;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>zone</code>.
|
||||
|
||||
@@ -181,15 +181,9 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
|
||||
layers.add(StaticAbilityLayer.MODIFYPT);
|
||||
}
|
||||
|
||||
if (hasParam("AddHiddenKeyword")) {
|
||||
layers.add(StaticAbilityLayer.RULES);
|
||||
}
|
||||
|
||||
if (hasParam("IgnoreEffectCost") || hasParam("Goad") || hasParam("CanBlockAny") || hasParam("CanBlockAmount")) {
|
||||
layers.add(StaticAbilityLayer.RULES);
|
||||
}
|
||||
|
||||
if (hasParam("AdjustLandPlays")) {
|
||||
if (hasParam("AddHiddenKeyword")
|
||||
|| hasParam("IgnoreEffectCost") || hasParam("Goad") || hasParam("CanBlockAny") || hasParam("CanBlockAmount")
|
||||
|| hasParam("AdjustLandPlays") || hasParam("ControlVote") || hasParam("AdditionalVote") || hasParam("AdditionalOptionalVote")) {
|
||||
layers.add(StaticAbilityLayer.RULES);
|
||||
}
|
||||
|
||||
|
||||
@@ -513,6 +513,20 @@ public final class StaticAbilityContinuous {
|
||||
}
|
||||
}
|
||||
|
||||
if (params.containsKey("ControlVote")) {
|
||||
p.addControlVote(se.getTimestamp());
|
||||
}
|
||||
if (params.containsKey("AdditionalVote")) {
|
||||
String mhs = params.get("AdditionalVote");
|
||||
int add = AbilityUtils.calculateAmount(hostCard, mhs, stAb);
|
||||
p.addAdditionalVote(se.getTimestamp(), add);
|
||||
}
|
||||
if (params.containsKey("AdditionalOptionalVote")) {
|
||||
String mhs = params.get("AdditionalOptionalVote");
|
||||
int add = AbilityUtils.calculateAmount(hostCard, mhs, stAb);
|
||||
p.addAdditionalOptionalVote(se.getTimestamp(), add);
|
||||
}
|
||||
|
||||
if (params.containsKey("RaiseMaxHandSize")) {
|
||||
String rmhs = params.get("RaiseMaxHandSize");
|
||||
int rmax = AbilityUtils.calculateAmount(hostCard, rmhs, stAb);
|
||||
|
||||
@@ -53,7 +53,7 @@ public class TriggerCycled extends Trigger {
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public final void setTriggeringObjects(final SpellAbility sa, Map<AbilityKey, Object> runParams) {
|
||||
sa.setTriggeringObjectsFrom(runParams, AbilityKey.Card);
|
||||
sa.setTriggeringObjectsFrom(runParams, AbilityKey.Card, AbilityKey.Cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -473,13 +473,13 @@ public class WrappedAbility extends Ability {
|
||||
|
||||
TriggerHandler th = game.getTriggerHandler();
|
||||
|
||||
// set Trigger
|
||||
sa.setTrigger(regtrig);
|
||||
|
||||
if (decider != null && !decider.getController().confirmTrigger(this, triggerParams, this.isMandatory())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// set Trigger
|
||||
sa.setTrigger(regtrig);
|
||||
|
||||
if (!triggerParams.containsKey("NoTimestampCheck")) {
|
||||
timestampCheck();
|
||||
}
|
||||
@@ -556,4 +556,11 @@ public class WrappedAbility extends Ability {
|
||||
public void setAlternativeCost(AlternativeCost ac) {
|
||||
sa.setAlternativeCost(ac);
|
||||
}
|
||||
|
||||
public Integer getXManaCostPaid() {
|
||||
return sa.getXManaCostPaid();
|
||||
}
|
||||
public void setXManaCostPaid(final Integer n) {
|
||||
sa.setXManaCostPaid(n);
|
||||
}
|
||||
}
|
||||
@@ -314,9 +314,9 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
|
||||
// Run Cycled triggers
|
||||
if (sp.isCycling()) {
|
||||
runParams.clear();
|
||||
runParams.put(AbilityKey.Card, sp.getHostCard());
|
||||
game.getTriggerHandler().runTrigger(TriggerType.Cycled, runParams, false);
|
||||
Map<AbilityKey, Object> cycleParams = AbilityKey.mapFromCard(sp.getHostCard());
|
||||
cycleParams.put(AbilityKey.Cause, sp);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.Cycled, cycleParams, false);
|
||||
}
|
||||
|
||||
if (sp.hasParam("Crew")) {
|
||||
|
||||
@@ -134,6 +134,9 @@ public enum TrackableProperty {
|
||||
HasUnlimitedLandPlay(TrackableTypes.BooleanType),
|
||||
NumLandThisTurn(TrackableTypes.IntegerType),
|
||||
NumDrawnThisTurn(TrackableTypes.IntegerType),
|
||||
AdditionalVote(TrackableTypes.IntegerType),
|
||||
OptionalAdditionalVote(TrackableTypes.IntegerType),
|
||||
ControlVotes(TrackableTypes.BooleanType),
|
||||
Keywords(TrackableTypes.KeywordCollectionViewType, FreezeMode.IgnoresFreeze),
|
||||
Commander(TrackableTypes.CardViewCollectionType, FreezeMode.IgnoresFreeze),
|
||||
CommanderCast(TrackableTypes.IntegerMapType),
|
||||
|
||||
@@ -100,17 +100,21 @@ public class TrackableTypes {
|
||||
if (newCollection != null) {
|
||||
//swap in objects in old collection for objects in new collection
|
||||
for (int i = 0; i < newCollection.size(); i++) {
|
||||
T newObj = newCollection.get(i);
|
||||
if (newObj != null) {
|
||||
T existingObj = from.getTracker().getObj(itemType, newObj.getId());
|
||||
if (existingObj != null) { //if object exists already, update its changed properties
|
||||
existingObj.copyChangedProps(newObj);
|
||||
newCollection.remove(i);
|
||||
newCollection.add(i, existingObj);
|
||||
}
|
||||
else { //if object is new, cache in object lookup
|
||||
from.getTracker().putObj(itemType, newObj.getId(), newObj);
|
||||
try {
|
||||
T newObj = newCollection.get(i);
|
||||
if (newObj != null) {
|
||||
T existingObj = from.getTracker().getObj(itemType, newObj.getId());
|
||||
if (existingObj != null) { //if object exists already, update its changed properties
|
||||
existingObj.copyChangedProps(newObj);
|
||||
newCollection.remove(i);
|
||||
newCollection.add(i, existingObj);
|
||||
}
|
||||
else { //if object is new, cache in object lookup
|
||||
from.getTracker().putObj(itemType, newObj.getId(), newObj);
|
||||
}
|
||||
}
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
System.err.println("got an IndexOutOfBoundsException, trying to continue ...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="19"
|
||||
android:targetSdkVersion="21" />
|
||||
android:targetSdkVersion="26" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <!-- This one needs Android Runtime Permission for Android 6+ -->
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<packaging.type>jar</packaging.type>
|
||||
<build.min.memory>-Xms1024m</build.min.memory>
|
||||
<build.max.memory>-Xmx1536m</build.max.memory>
|
||||
<alpha-version>1.6.32.001</alpha-version>
|
||||
<alpha-version>1.6.33.001</alpha-version>
|
||||
<sign.keystore>keystore</sign.keystore>
|
||||
<sign.alias>alias</sign.alias>
|
||||
<sign.storepass>storepass</sign.storepass>
|
||||
@@ -19,7 +19,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.33-SNAPSHOT</version>
|
||||
<version>1.6.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-android</artifactId>
|
||||
@@ -104,24 +104,6 @@
|
||||
<artifactId>gdx-backend-android</artifactId>
|
||||
<version>1.9.10</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cache2k</groupId>
|
||||
<artifactId>cache2k-base-bom</artifactId>
|
||||
<version>1.2.4.Final</version>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cache2k</groupId>
|
||||
<artifactId>cache2k-core</artifactId>
|
||||
<version>1.2.4.Final</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cache2k</groupId>
|
||||
<artifactId>cache2k-api</artifactId>
|
||||
<version>1.2.4.Final</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
@@ -142,7 +124,7 @@
|
||||
<debug>true</debug>
|
||||
</sign>
|
||||
<sdk>
|
||||
<platform>25</platform>
|
||||
<platform>26</platform>
|
||||
</sdk>
|
||||
<dexForceJumbo>true</dexForceJumbo>
|
||||
<androidManifestFile>${project.basedir}/AndroidManifest.xml</androidManifestFile>
|
||||
@@ -183,7 +165,7 @@
|
||||
<debug>false</debug>
|
||||
</sign>
|
||||
<sdk>
|
||||
<platform>25</platform>
|
||||
<platform>26</platform>
|
||||
</sdk>
|
||||
<zipalign>
|
||||
<verbose>false</verbose>
|
||||
|
||||
@@ -29,20 +29,8 @@
|
||||
-dontwarn javax.**
|
||||
-dontwarn org.apache.logging.log4j.**
|
||||
-dontwarn module-info
|
||||
|
||||
# mandatory proguard rules for cache2k to keep the core implementation
|
||||
-dontwarn org.cache2k.impl.xmlConfiguration.**
|
||||
-dontwarn org.cache2k.impl.serverSide.**
|
||||
-keep interface org.cache2k.spi.Cache2kCoreProvider
|
||||
-keep public class * extends org.cache2k.spi.Cache2kCoreProvider
|
||||
# optional proguard rules for cache2k, to keep XML configuration code
|
||||
# if only programmatic configuration is used, these rules may be ommitted
|
||||
-keep interface org.cache2k.core.spi.CacheConfigurationProvider
|
||||
-keep public class * extends org.cache2k.core.spi.CacheConfigurationProvider
|
||||
-keepclassmembers public class * extends org.cache2k.configuration.ConfigurationBean {
|
||||
public void set*(...);
|
||||
public ** get*();
|
||||
}
|
||||
## Support library
|
||||
-dontwarn android.support.**
|
||||
|
||||
-keep class forge.** { *; }
|
||||
-keep class com.thoughtworks.xstream.** { *; }
|
||||
@@ -51,6 +39,7 @@
|
||||
-keep class com.google.common.** { *; }
|
||||
-keep class io.sentry.event.Event { *; }
|
||||
-keep class io.netty.util.internal.logging.** { *; }
|
||||
-keep class net.jpountz.** { *; }
|
||||
|
||||
-keepclassmembers class com.badlogic.gdx.backends.android.AndroidInput* {
|
||||
<init>(com.badlogic.gdx.Application, android.content.Context, java.lang.Object, com.badlogic.gdx.backends.android.AndroidApplicationConfiguration);
|
||||
|
||||
@@ -9,4 +9,4 @@
|
||||
|
||||
# Project target.
|
||||
project.type=0
|
||||
target=android-20
|
||||
target=android-26
|
||||
|
||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#f0f0f0</color>
|
||||
<color name="ic_launcher_background">#ffffff</color>
|
||||
</resources>
|
||||
@@ -1,16 +1,22 @@
|
||||
package forge.app;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.graphics.drawable.StateListDrawable;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.Uri;
|
||||
@@ -18,8 +24,17 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.Button;
|
||||
import android.widget.TableLayout;
|
||||
import android.widget.TableRow;
|
||||
import android.widget.TextView;
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.backends.android.AndroidApplication;
|
||||
import forge.Forge;
|
||||
@@ -35,13 +50,143 @@ import java.io.OutputStream;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class Main extends AndroidApplication {
|
||||
AndroidAdapter Gadapter;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
AndroidAdapter adapter = new AndroidAdapter(this.getContext());
|
||||
boolean permissiongranted = checkPermission();
|
||||
Gadapter = new AndroidAdapter(this.getContext());
|
||||
initForge(Gadapter, permissiongranted);
|
||||
|
||||
//permission
|
||||
if(!permissiongranted){
|
||||
//requestPermission();
|
||||
displayMessage(Gadapter);
|
||||
}
|
||||
}
|
||||
|
||||
private void displayMessage(AndroidAdapter adapter){
|
||||
TableLayout TL = new TableLayout(this);
|
||||
TableRow row = new TableRow(this);
|
||||
TableRow row2 = new TableRow(this);
|
||||
TextView text = new TextView(this);
|
||||
text.setGravity(Gravity.LEFT);
|
||||
text.setTypeface(Typeface.SERIF);
|
||||
|
||||
String title="Forge needs Storage Permission to run properly...\n" +
|
||||
"Follow these simple steps:\n\n";
|
||||
String steps = " 1) Tap \"Open App Details\" Button.\n" +
|
||||
" 2) Tap Permissions\n"+
|
||||
" 3) Turn on the Storage Permission.\n\n"+
|
||||
"(You can tap anywhere to exit and restart the app)\n\n";
|
||||
|
||||
SpannableString ss1= new SpannableString(title);
|
||||
ss1.setSpan(new StyleSpan(Typeface.BOLD), 0, ss1.length(), 0);
|
||||
text.append(ss1);
|
||||
text.append(steps);
|
||||
row.addView(text);
|
||||
row.setGravity(Gravity.CENTER);
|
||||
|
||||
int[] colors = {Color.TRANSPARENT,Color.TRANSPARENT};
|
||||
int[] pressed = {Color.GREEN,Color.GREEN};
|
||||
GradientDrawable gd = new GradientDrawable(
|
||||
GradientDrawable.Orientation.TOP_BOTTOM, colors);
|
||||
gd.setStroke(3, Color.DKGRAY);
|
||||
gd.setCornerRadius(100);
|
||||
|
||||
GradientDrawable gd2 = new GradientDrawable(
|
||||
GradientDrawable.Orientation.TOP_BOTTOM, pressed);
|
||||
gd2.setStroke(3, Color.DKGRAY);
|
||||
gd2.setCornerRadius(100);
|
||||
|
||||
Button button = new Button(this);
|
||||
button.setText("Open App Details");
|
||||
|
||||
StateListDrawable states = new StateListDrawable();
|
||||
|
||||
states.addState(new int[] {android.R.attr.state_pressed}, gd2);
|
||||
states.addState(new int[] { }, gd);
|
||||
|
||||
button.setBackground(states);
|
||||
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
Uri uri = Uri.fromParts("package", getPackageName(), null);
|
||||
intent.setData(uri);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
row2.addView(button);
|
||||
row2.setGravity(Gravity.CENTER);
|
||||
|
||||
TL.addView(row, new TableLayout.LayoutParams(TableLayout.LayoutParams.WRAP_CONTENT, TableLayout.LayoutParams.WRAP_CONTENT));
|
||||
TL.addView(row2, new TableLayout.LayoutParams(TableLayout.LayoutParams.WRAP_CONTENT, TableLayout.LayoutParams.WRAP_CONTENT));
|
||||
TL.setGravity(Gravity.CENTER);
|
||||
TL.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
adapter.exit();
|
||||
}
|
||||
});
|
||||
setContentView(TL);
|
||||
}
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (Gadapter!=null)
|
||||
Gadapter.exit();
|
||||
|
||||
super.onBackPressed();
|
||||
}
|
||||
private boolean checkPermission() {
|
||||
int pid = android.os.Process.myPid();
|
||||
int uid = android.os.Process.myUid();
|
||||
try {
|
||||
int result = this.getBaseContext().checkPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, pid, uid);
|
||||
if (result == PackageManager.PERMISSION_GRANTED) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
private void requestPermission() {
|
||||
//Show Information about why you need the permission
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("Storage Permission Denied...");
|
||||
builder.setMessage("This app needs storage permission to run properly.\n\n\n\n");
|
||||
builder.setPositiveButton("Open App Details", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.cancel();
|
||||
//ActivityCompat crashes... maybe it needs the appcompat v7???
|
||||
//ActivityCompat.requestPermissions(Main.this, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
|
||||
}
|
||||
});
|
||||
/*builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.cancel();
|
||||
}
|
||||
});
|
||||
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
|
||||
}
|
||||
});*/
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void initForge(AndroidAdapter adapter, boolean permissiongranted){
|
||||
boolean isPortrait;
|
||||
//establish assets directory
|
||||
if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
|
||||
Gdx.app.error("Forge", "Can't access external storage");
|
||||
@@ -65,15 +210,25 @@ public class Main extends AndroidApplication {
|
||||
//enforce orientation based on whether device is a tablet and user preference
|
||||
adapter.switchOrientationFile = assetsDir + "switch_orientation.ini";
|
||||
boolean landscapeMode = adapter.isTablet == !FileUtil.doesFileExist(adapter.switchOrientationFile);
|
||||
if (landscapeMode) {
|
||||
Main.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||
}
|
||||
else {
|
||||
Main.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
if (permissiongranted){
|
||||
if (landscapeMode) {
|
||||
isPortrait = false;
|
||||
Main.this.setRequestedOrientation(Build.VERSION.SDK_INT >= 26 ?
|
||||
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : //Oreo and above has virtual back/menu buttons
|
||||
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||
} else {
|
||||
isPortrait = true;
|
||||
Main.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
}
|
||||
} else {
|
||||
isPortrait = true;
|
||||
//set current orientation
|
||||
Main.this.setRequestedOrientation(Main.this.getResources().getConfiguration().orientation);
|
||||
}
|
||||
|
||||
boolean value = Build.VERSION.SDK_INT >= 26;
|
||||
initialize(Forge.getApp(new AndroidClipboard(), adapter, assetsDir, value));
|
||||
ForgePreferences prefs = FModel.getPreferences();
|
||||
boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_NETPLAY_COMPAT);
|
||||
initialize(Forge.getApp(new AndroidClipboard(), adapter, assetsDir, propertyConfig, isPortrait));
|
||||
}
|
||||
|
||||
/*@Override
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.33-SNAPSHOT</version>
|
||||
<version>1.6.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-desktop</artifactId>
|
||||
@@ -213,7 +213,7 @@
|
||||
<plugin>
|
||||
<groupId>com.akathist.maven.plugins.launch4j</groupId>
|
||||
<artifactId>launch4j-maven-plugin</artifactId>
|
||||
<version>1.5.2</version>
|
||||
<version>1.7.25</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>l4j-gui</id>
|
||||
@@ -235,7 +235,10 @@
|
||||
</classPath>
|
||||
<jre>
|
||||
<minVersion>1.8.0</minVersion>
|
||||
<maxHeapSize>1024</maxHeapSize>
|
||||
<maxHeapSize>4096</maxHeapSize>
|
||||
<opts>
|
||||
<opt>-Dfile.encoding=UTF-8</opt>
|
||||
</opts>
|
||||
</jre>
|
||||
<versionInfo>
|
||||
<fileVersion>
|
||||
@@ -367,7 +370,7 @@
|
||||
<plugin>
|
||||
<groupId>com.akathist.maven.plugins.launch4j</groupId>
|
||||
<artifactId>launch4j-maven-plugin</artifactId>
|
||||
<version>1.5.2</version>
|
||||
<version>1.7.25</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>l4j-gui</id>
|
||||
@@ -389,7 +392,10 @@
|
||||
</classPath>
|
||||
<jre>
|
||||
<minVersion>1.8.0</minVersion>
|
||||
<maxHeapSize>1024</maxHeapSize>
|
||||
<maxHeapSize>4096</maxHeapSize>
|
||||
<opts>
|
||||
<opt>-Dfile.encoding=UTF-8</opt>
|
||||
</opts>
|
||||
</jre>
|
||||
<versionInfo>
|
||||
<fileVersion>
|
||||
@@ -564,7 +570,7 @@
|
||||
<option value="-Dcom.apple.macos.useScreenMenuBar=true" />
|
||||
<option value="-Dcom.apple.mrj.application.apple.menu.about.name=Forge" />
|
||||
<option value="-Dcom.apple.smallTabs=true" />
|
||||
<option value="-Xmx1024M" />
|
||||
<option value="-Xmx4096M" />
|
||||
<option value="-Dapp.dir=$APP_ROOT/Contents/Resources/" />
|
||||
</bundleapp>
|
||||
<copy todir="${project.build.directory}/${project.build.finalName}-osx/Forge.app/Contents/Resources/res">
|
||||
@@ -675,7 +681,7 @@
|
||||
<option value="-Dcom.apple.macos.useScreenMenuBar=true" />
|
||||
<option value="-Dcom.apple.mrj.application.apple.menu.about.name=Forge" />
|
||||
<option value="-Dcom.apple.smallTabs=true" />
|
||||
<option value="-Xmx1024M" />
|
||||
<option value="-Xmx4096M" />
|
||||
<option value="-Dapp.dir=$APP_ROOT/Contents/Resources/" />
|
||||
</bundleapp>
|
||||
<copy todir="${project.build.directory}/${project.build.finalName}-osx/Forge.app/Contents/Resources/res">
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
cd "`dirname \"$0\"`"
|
||||
java -Xmx1024m -jar $project.build.finalName$
|
||||
cd $(dirname "${0}")
|
||||
java -Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
cd "`dirname \"$0\"`"
|
||||
java -Xmx1024m -jar $project.build.finalName$
|
||||
cd $(dirname "${0}")
|
||||
java -Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$
|
||||
|
||||
@@ -40,6 +40,7 @@ import javax.swing.WindowConstants;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.GuiBase;
|
||||
import forge.ImageCache;
|
||||
import forge.LobbyPlayer;
|
||||
import forge.Singletons;
|
||||
@@ -219,6 +220,10 @@ public enum FControl implements KeyEventDispatcher {
|
||||
|
||||
final ForgePreferences prefs = FModel.getPreferences();
|
||||
|
||||
//set ExperimentalNetworkOption from preference
|
||||
boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_NETPLAY_COMPAT);
|
||||
GuiBase.enablePropertyConfig(propertyConfig);
|
||||
|
||||
closeAction = CloseAction.valueOf(prefs.getPref(FPref.UI_CLOSE_ACTION));
|
||||
|
||||
final Localizer localizer = Localizer.getInstance();
|
||||
|
||||
@@ -103,7 +103,7 @@ public final class CEditorConstructed extends CDeckEditor<Deck> {
|
||||
case TinyLeaders:
|
||||
allSections.add(DeckSection.Commander);
|
||||
|
||||
commanderFilter = CardRulesPredicates.Presets.CAN_BE_COMMANDER;
|
||||
commanderFilter = CardRulesPredicates.Presets.CAN_BE_TINY_LEADERS_COMMANDER;
|
||||
commanderPool = ItemPool.createFrom(FModel.getMagicDb().getCommonCards().getAllCards(Predicates.compose(commanderFilter, PaperCard.FN_GET_RULES)), PaperCard.class);
|
||||
normalPool = ItemPool.createFrom(FModel.getMagicDb().getCommonCards().getAllCards(), PaperCard.class);
|
||||
|
||||
|
||||
@@ -917,7 +917,7 @@ public class VLobby implements ILobbyView {
|
||||
final List<String> usedNames = getPlayerNames();
|
||||
do {
|
||||
newName = NameGenerator.getRandomName(gender, type, usedNames);
|
||||
confirmMsg = localizer.getMessage("lblconfirmName").replace("%n","\"" +newName + "\"");
|
||||
confirmMsg = localizer.getMessage("lblconfirmName").replace("%s","\"" +newName + "\"");
|
||||
} while (!FOptionPane.showConfirmDialog(confirmMsg, title, localizer.getMessage("lblUseThisName"), localizer.getMessage("lblTryAgain"), true));
|
||||
|
||||
return newName;
|
||||
|
||||
@@ -200,4 +200,8 @@ public enum VSubmenuOnlineLobby implements IVSubmenu<CSubmenuOnlineLobby>, IOnli
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeConn(String msg) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,13 @@ public enum CSubmenuDownloaders implements ICDoc {
|
||||
VSubmenuDownloaders.SINGLETON_INSTANCE.showLicensing();
|
||||
}
|
||||
};
|
||||
private final UiCommand cmdCheckForUpdates = new UiCommand() {
|
||||
@Override
|
||||
public void run() {
|
||||
new AutoUpdater(false).attemptToUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
private final UiCommand cmdPicDownload = new UiCommand() {
|
||||
@Override public void run() {
|
||||
new GuiDownloader(new GuiDownloadPicturesLQ()).show();
|
||||
@@ -84,6 +91,7 @@ public enum CSubmenuDownloaders implements ICDoc {
|
||||
@Override
|
||||
public void initialize() {
|
||||
final VSubmenuDownloaders view = VSubmenuDownloaders.SINGLETON_INSTANCE;
|
||||
view.setCheckForUpdatesCommand(cmdCheckForUpdates);
|
||||
view.setDownloadPicsCommand(cmdPicDownload);
|
||||
view.setDownloadPicsHQCommand(cmdPicDownloadHQ);
|
||||
view.setDownloadSetPicsCommand(cmdSetDownload);
|
||||
|
||||
@@ -3,6 +3,7 @@ package forge.screens.home.settings;
|
||||
import forge.*;
|
||||
import forge.ai.AiProfileUtil;
|
||||
import forge.control.FControl.CloseAction;
|
||||
import forge.download.AutoUpdater;
|
||||
import forge.game.GameLogEntryType;
|
||||
import forge.gui.framework.FScreen;
|
||||
import forge.gui.framework.ICDoc;
|
||||
@@ -94,6 +95,19 @@ public enum CSubmenuPreferences implements ICDoc {
|
||||
}
|
||||
});
|
||||
|
||||
// This updates Experimental Network Option
|
||||
view.getCbUseExperimentalNetworkStream().addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(final ItemEvent arg0) {
|
||||
if (updating) { return; }
|
||||
|
||||
final boolean toggle = view.getCbUseExperimentalNetworkStream().isSelected();
|
||||
GuiBase.enablePropertyConfig(toggle);
|
||||
prefs.setPref(FPref.UI_NETPLAY_COMPAT, String.valueOf(toggle));
|
||||
prefs.save();
|
||||
}
|
||||
});
|
||||
|
||||
lstControls.clear(); // just in case
|
||||
lstControls.add(Pair.of(view.getCbAnte(), FPref.UI_ANTE));
|
||||
lstControls.add(Pair.of(view.getCbAnteMatchRarity(), FPref.UI_ANTE_MATCH_RARITY));
|
||||
@@ -113,6 +127,8 @@ public enum CSubmenuPreferences implements ICDoc {
|
||||
lstControls.add(Pair.of(view.getCbRemoveArtifacts(), FPref.DECKGEN_ARTIFACTS));
|
||||
lstControls.add(Pair.of(view.getCbSingletons(), FPref.DECKGEN_SINGLETONS));
|
||||
lstControls.add(Pair.of(view.getCbEnableAICheats(), FPref.UI_ENABLE_AI_CHEATS));
|
||||
lstControls.add(Pair.of(view.getCbEnableUnknownCards(), FPref.UI_LOAD_UNKNOWN_CARDS));
|
||||
lstControls.add(Pair.of(view.getCbUseExperimentalNetworkStream(), FPref.UI_NETPLAY_COMPAT));
|
||||
lstControls.add(Pair.of(view.getCbImageFetcher(), FPref.UI_ENABLE_ONLINE_IMAGE_FETCHER));
|
||||
lstControls.add(Pair.of(view.getCbDisplayFoil(), FPref.UI_OVERLAY_FOIL_EFFECT));
|
||||
lstControls.add(Pair.of(view.getCbRandomFoil(), FPref.UI_RANDOM_FOIL));
|
||||
@@ -225,6 +241,7 @@ public enum CSubmenuPreferences implements ICDoc {
|
||||
initializeGameLogVerbosityComboBox();
|
||||
initializeCloseActionComboBox();
|
||||
initializeDefaultFontSizeComboBox();
|
||||
initializeAutoUpdaterComboBox();
|
||||
initializeMulliganRuleComboBox();
|
||||
initializeAiProfilesComboBox();
|
||||
initializeStackAdditionsComboBox();
|
||||
@@ -251,6 +268,7 @@ public enum CSubmenuPreferences implements ICDoc {
|
||||
setPlayerNameButtonText();
|
||||
view.getCbDevMode().setSelected(ForgePreferences.DEV_MODE);
|
||||
view.getCbEnableMusic().setSelected(prefs.getPrefBoolean(FPref.UI_ENABLE_MUSIC));
|
||||
view.getCbUseExperimentalNetworkStream().setSelected(prefs.getPrefBoolean(FPref.UI_NETPLAY_COMPAT));
|
||||
|
||||
for(final Pair<JCheckBox, FPref> kv: lstControls) {
|
||||
kv.getKey().setSelected(prefs.getPrefBoolean(kv.getValue()));
|
||||
@@ -378,6 +396,16 @@ public enum CSubmenuPreferences implements ICDoc {
|
||||
panel.setComboBox(comboBox, selectedItem);
|
||||
}
|
||||
|
||||
private void initializeAutoUpdaterComboBox() {
|
||||
// TODO: Ideally we would filter out update paths based on the type of Forge people have
|
||||
final String[] updatePaths = AutoUpdater.updateChannels;
|
||||
final FPref updatePreference = FPref.AUTO_UPDATE;
|
||||
final FComboBoxPanel<String> panel = this.view.getCbpAutoUpdater();
|
||||
final FComboBox<String> comboBox = createComboBox(updatePaths, updatePreference);
|
||||
final String selectedItem = this.prefs.getPref(updatePreference);
|
||||
panel.setComboBox(comboBox, selectedItem);
|
||||
}
|
||||
|
||||
private void initializeMulliganRuleComboBox() {
|
||||
final String [] choices = MulliganDefs.getMulliganRuleNames();
|
||||
final FPref userSetting = FPref.MULLIGAN_RULE;
|
||||
|
||||
@@ -55,6 +55,7 @@ public enum VSubmenuDownloaders implements IVSubmenu<CSubmenuDownloaders> {
|
||||
private final JPanel pnlContent = new JPanel(new MigLayout("insets 0, gap 0, wrap, ay center"));
|
||||
private final FScrollPane scrContent = new FScrollPane(pnlContent, false);
|
||||
|
||||
private final FLabel btnCheckForUpdates = _makeButton(localizer.getMessage("btnCheckForUpdates"));
|
||||
private final FLabel btnDownloadSetPics = _makeButton(localizer.getMessage("btnDownloadSetPics"));
|
||||
private final FLabel btnDownloadPics = _makeButton(localizer.getMessage("btnDownloadPics"));
|
||||
private final FLabel btnDownloadPicsHQ = _makeButton(localizer.getMessage("btnDownloadPicsHQ"));
|
||||
@@ -80,6 +81,9 @@ public enum VSubmenuDownloaders implements IVSubmenu<CSubmenuDownloaders> {
|
||||
|
||||
if (javaRecentEnough()) {
|
||||
|
||||
pnlContent.add(btnCheckForUpdates, constraintsBTN);
|
||||
pnlContent.add(_makeLabel(localizer.getMessage("lblCheckForUpdates")), constraintsLBL);
|
||||
|
||||
pnlContent.add(btnDownloadPics, constraintsBTN);
|
||||
pnlContent.add(_makeLabel(localizer.getMessage("lblDownloadPics")), constraintsLBL);
|
||||
|
||||
@@ -162,6 +166,7 @@ public enum VSubmenuDownloaders implements IVSubmenu<CSubmenuDownloaders> {
|
||||
return EMenuGroup.SETTINGS;
|
||||
}
|
||||
|
||||
public void setCheckForUpdatesCommand(UiCommand command) { btnCheckForUpdates.setCommand(command); }
|
||||
public void setDownloadPicsCommand(UiCommand command) { btnDownloadPics.setCommand(command); }
|
||||
public void setDownloadPicsHQCommand(UiCommand command) { btnDownloadPicsHQ.setCommand(command); }
|
||||
public void setDownloadSetPicsCommand(UiCommand command) { btnDownloadSetPics.setCommand(command); }
|
||||
|
||||
@@ -25,8 +25,8 @@ import java.awt.event.FocusAdapter;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
/**
|
||||
@@ -107,6 +107,8 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
|
||||
private final JCheckBox cbShowStormCount = new OptionsCheckBox(localizer.getMessage("cbShowStormCount"));
|
||||
private final JCheckBox cbRemindOnPriority = new OptionsCheckBox(localizer.getMessage("cbRemindOnPriority"));
|
||||
private final JCheckBox cbUseSentry = new OptionsCheckBox(localizer.getMessage("cbUseSentry"));
|
||||
private final JCheckBox cbEnableUnknownCards = new OptionsCheckBox("Enable Unknown Cards");
|
||||
private final JCheckBox cbUseExperimentalNetworkStream = new OptionsCheckBox("Experimental Network Compatibility");
|
||||
|
||||
private final Map<FPref, KeyboardShortcutField> shortcutFields = new HashMap<>();
|
||||
|
||||
@@ -123,6 +125,7 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
|
||||
private final FComboBoxPanel<String> cbpCounterDisplayLocation =new FComboBoxPanel<>(localizer.getMessage("cbpCounterDisplayLocation")+":");
|
||||
private final FComboBoxPanel<String> cbpGraveyardOrdering = new FComboBoxPanel<>(localizer.getMessage("cbpGraveyardOrdering")+":");
|
||||
private final FComboBoxPanel<String> cbpDefaultLanguage = new FComboBoxPanel<>(localizer.getMessage("cbpSelectLanguage")+":");
|
||||
private final FComboBoxPanel<String> cbpAutoUpdater = new FComboBoxPanel<>(localizer.getMessage("cbpAutoUpdater")+":");
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -157,6 +160,10 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
|
||||
pnlPrefs.add(new SectionLabel(localizer.getMessage("GeneralConfiguration")), sectionConstraints);
|
||||
|
||||
// language
|
||||
|
||||
pnlPrefs.add(cbpAutoUpdater, comboBoxConstraints);
|
||||
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlAutoUpdater")), descriptionConstraints);
|
||||
|
||||
pnlPrefs.add(cbpDefaultLanguage, comboBoxConstraints);
|
||||
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlSelectLanguage")), descriptionConstraints);
|
||||
|
||||
@@ -282,6 +289,12 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
|
||||
pnlPrefs.add(cbLoadHistoricFormats, titleConstraints);
|
||||
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlLoadHistoricFormats")), descriptionConstraints);
|
||||
|
||||
pnlPrefs.add(cbEnableUnknownCards, titleConstraints);
|
||||
pnlPrefs.add(new NoteLabel("Enable Unknown Cards to be loaded to Unknown Set. (Requires restart)"), descriptionConstraints);
|
||||
|
||||
pnlPrefs.add(cbUseExperimentalNetworkStream, titleConstraints);
|
||||
pnlPrefs.add(new NoteLabel("Forge switches to compatible network stream. (If unsure, turn OFF this option)"), descriptionConstraints);
|
||||
|
||||
// Graphic Options
|
||||
pnlPrefs.add(new SectionLabel(localizer.getMessage("GraphicOptions")), sectionConstraints + ", gaptop 2%");
|
||||
|
||||
@@ -531,6 +544,10 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
|
||||
}
|
||||
}
|
||||
|
||||
public final FComboBoxPanel<String> getCbpAutoUpdater() {
|
||||
return cbpAutoUpdater;
|
||||
}
|
||||
|
||||
/** @return {@link javax.swing.JCheckBox} */
|
||||
public final JCheckBox getCbCompactMainMenu() {
|
||||
return cbCompactMainMenu;
|
||||
@@ -571,6 +588,16 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
|
||||
return cbEnableAICheats;
|
||||
}
|
||||
|
||||
/** @return {@link javax.swing.JCheckBox} */
|
||||
public JCheckBox getCbEnableUnknownCards() {
|
||||
return cbEnableUnknownCards;
|
||||
}
|
||||
|
||||
/** @return {@link javax.swing.JCheckBox} */
|
||||
public JCheckBox getCbUseExperimentalNetworkStream() {
|
||||
return cbUseExperimentalNetworkStream;
|
||||
}
|
||||
|
||||
/** @return {@link javax.swing.JCheckBox} */
|
||||
public JCheckBox getCbImageFetcher() {
|
||||
return cbImageFetcher;
|
||||
|
||||
@@ -49,9 +49,6 @@ public final class Main {
|
||||
//setup GUI interface
|
||||
GuiBase.setInterface(new GuiDesktop());
|
||||
|
||||
//set PropertyConfig log4j to true
|
||||
GuiBase.enablePropertyConfig(true);
|
||||
|
||||
//install our error handler
|
||||
ExceptionHandler.registerErrorHandling();
|
||||
|
||||
@@ -81,7 +78,7 @@ public final class Main {
|
||||
break;
|
||||
|
||||
default:
|
||||
System.out.println("Unknown mode.\nKnown mode is 'sim' ");
|
||||
System.out.println("Unknown mode.\nKnown mode is 'sim', 'parse' ");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ public class CardDatabaseHelper {
|
||||
|
||||
private static void initialize() {
|
||||
final CardStorageReader reader = new CardStorageReader(ForgeConstants.CARD_DATA_DIR, null, FModel.getPreferences().getPrefBoolean(FPref.LOAD_CARD_SCRIPTS_LAZILY));
|
||||
staticData = new StaticData(reader, ForgeConstants.EDITIONS_DIR, ForgeConstants.BLOCK_DATA_DIR);
|
||||
staticData = new StaticData(reader, ForgeConstants.EDITIONS_DIR, ForgeConstants.BLOCK_DATA_DIR, FModel.getPreferences().getPrefBoolean(FPref.UI_LOAD_UNKNOWN_CARDS));
|
||||
}
|
||||
|
||||
private static boolean hasBeenInitialized() {
|
||||
|
||||