Merge remote-tracking branch 'core-developers/master'

This commit is contained in:
klaxnek
2020-04-16 17:57:58 +02:00
493 changed files with 9747 additions and 1220 deletions

View File

@@ -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>

View File

@@ -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();

View File

@@ -1169,7 +1169,7 @@ public class ComputerUtilMana {
cost.increaseShard(shardToGrow, manaToAdd);
if (!test) {
card.setXManaCostPaid(manaToAdd / cost.getXcounter());
sa.setXManaCostPaid(manaToAdd / cost.getXcounter());
}
}

View File

@@ -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()) {

View File

@@ -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

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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

View File

@@ -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);
}
{

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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() {

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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.

View File

@@ -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) {

View 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);
}
};
}
}

View File

@@ -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.
*

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -177,7 +177,6 @@ public class StaticEffect {
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

View File

@@ -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 doesnt 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 Egos 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 objects 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>

View File

@@ -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))));
}

View File

@@ -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).");
}
}

View File

@@ -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);

View File

@@ -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"));
}

View File

@@ -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 ");

View File

@@ -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()) {

View File

@@ -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();
}
}

View File

@@ -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() {

View File

@@ -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());

View File

@@ -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

View File

@@ -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);

View File

@@ -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());
}

View File

@@ -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
}
@@ -893,6 +897,10 @@ public class Combat {
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
}

View File

@@ -123,7 +123,9 @@ 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);
for (Card c: targetCards) {
lkiList.add(CardUtil.getLKICopy(c));
}
cardList.addAll(doListPayment(ability, targetCards));
handleChangeZoneTrigger(ability);
return true;

View File

@@ -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."),

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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"));
}

View File

@@ -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;
@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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>.

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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")) {

View File

@@ -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),

View File

@@ -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 ...");
}
}
}

View File

@@ -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"/>

Binary file not shown.

View File

@@ -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>

View File

@@ -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);

View File

@@ -9,4 +9,4 @@
# Project target.
project.type=0
target=android-20
target=android-26

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -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>

View File

@@ -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

View File

@@ -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">

View File

@@ -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$

View File

@@ -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$

View File

@@ -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();

View File

@@ -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);

View File

@@ -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;

View File

@@ -200,4 +200,8 @@ public enum VSubmenuOnlineLobby implements IVSubmenu<CSubmenuOnlineLobby>, IOnli
}
return false;
}
@Override
public void closeConn(String msg) {
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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); }

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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() {

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