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

This commit is contained in:
klaxnek
2020-10-09 19:41:36 +02:00
314 changed files with 3880 additions and 1527 deletions

280
.gitignore vendored
View File

@@ -1,229 +1,77 @@
/*.idea
# Ignore IDEA config files
*.idea
*.iml
/*.tmp
/.metadata
/.recommenders
forge-ai/target
forge-core/target
forge-game/target
*.tmp
.metadata
.recommenders
# Ignore Eclipse config files
.settings
.classpath
.project
# Ignore VS Code config files
.vscode/settings.json
.vscode/launch.json
# Ignore NetBeans config files
nbactions.xml
# Ignore binaries, temp files and test output, everywhere
target
test-output
bin
gen
*.log
# TODO: specify what these ignores are for (releasing?)
forge.profile.properties
/jgv.txt
pom.xml.next
pom.xml.releaseBackup
pom.xml.tag
release.properties
# Ignore mobile-related resources
forge-gui-android/res/*/*
!forge-gui-android/res/*/ic_launcher.png
!forge-gui-android/res/*/ic_launcher*.png
!forge-gui-android/res/layout/main.xml
forge-gui-android/*.keystore
forge-gui-android/assets/fallback_skin/Thumbs.db
forge-gui-android/bin
forge-gui-android/gen
forge-gui-android/res/Thumbs.db
forge-gui-android/res/bin
forge-gui-android/res/drawable-hdpi/Thumbs.db
forge-gui-android/res/drawable-hdpi/bin
forge-gui-android/res/drawable-hdpi/gen
forge-gui-android/res/drawable-hdpi/target
forge-gui-android/res/drawable-ldpi/Thumbs.db
forge-gui-android/res/drawable-ldpi/bin
forge-gui-android/res/drawable-ldpi/gen
forge-gui-android/res/drawable-ldpi/target
forge-gui-android/res/drawable-mdpi/Thumbs.db
forge-gui-android/res/drawable-mdpi/bin
forge-gui-android/res/drawable-mdpi/gen
forge-gui-android/res/drawable-mdpi/target
forge-gui-android/res/drawable-xhdpi/Thumbs.db
forge-gui-android/res/drawable-xhdpi/bin
forge-gui-android/res/drawable-xhdpi/gen
forge-gui-android/res/drawable-xhdpi/target
forge-gui-android/res/drawable-xxhdpi/Thumbs.db
forge-gui-android/res/drawable-xxhdpi/bin
forge-gui-android/res/drawable-xxhdpi/gen
forge-gui-android/res/drawable-xxhdpi/target
forge-gui-android/res/gen
forge-gui-android/res/layout/Thumbs.db
forge-gui-android/res/layout/bin
forge-gui-android/res/layout/gen
forge-gui-android/res/layout/target
forge-gui-android/res/target
forge-gui-android/res/values/Thumbs.db
forge-gui-android/res/values/bin
forge-gui-android/res/values/gen
forge-gui-android/res/values/target
forge-gui-android/target
forge-gui-desktop/target
forge-gui-ios/target
forge-gui-mobile-dev/bin
forge-gui-mobile-dev/fallback_skin/Thumbs.db
forge-gui-android/**/Thumbs.db
forge-gui-mobile-dev/**/Thumbs.db
forge-gui-mobile-dev/res
forge-gui-mobile-dev/target
forge-gui-mobile-dev/testAssets
forge-gui-mobile/bin
forge-gui-mobile/target
forge-gui/forge.profile.properties
forge-gui/res/*.log
forge-gui/res/PerSetTrackingResults
forge-gui/res/cardsfolder/*.bat
forge-gui/res/PerSetTrackingResults
forge-gui/res/decks
forge-gui/res/layouts
forge-gui/res/pics*
forge-gui/res/pics_product
forge-gui/res/skins/**/PerSetTrackingResults
forge-gui/res/skins/**/decks
forge-gui/res/skins/**/layouts
forge-gui/res/skins/**/pics*
forge-gui/res/skins/**/pics_product
forge-gui/res/quest/world/1996-05[!!-~]Ice[!!-~]Age/duels/.directory
forge-gui/res/skins/*.log
forge-gui/res/skins/PerSetTrackingResults
forge-gui/res/skins/Thumbs.db
forge-gui/res/skins/arabian_nights/*.log
forge-gui/res/skins/arabian_nights/PerSetTrackingResults
forge-gui/res/skins/arabian_nights/Thumbs.db
forge-gui/res/skins/arabian_nights/decks
forge-gui/res/skins/arabian_nights/layouts
forge-gui/res/skins/arabian_nights/pics*
forge-gui/res/skins/arabian_nights/pics_product
forge-gui/res/skins/comic/*.log
forge-gui/res/skins/comic/PerSetTrackingResults
forge-gui/res/skins/comic/Thumbs.db
forge-gui/res/skins/comic/decks
forge-gui/res/skins/comic/layouts
forge-gui/res/skins/comic/pics*
forge-gui/res/skins/comic/pics_product
forge-gui/res/skins/dark_ascension/*.log
forge-gui/res/skins/dark_ascension/PerSetTrackingResults
forge-gui/res/skins/dark_ascension/Thumbs.db
forge-gui/res/skins/dark_ascension/decks
forge-gui/res/skins/dark_ascension/layouts
forge-gui/res/skins/dark_ascension/pics*
forge-gui/res/skins/dark_ascension/pics_product
forge-gui/res/skins/decks
forge-gui/res/skins/default/*.log
forge-gui/res/skins/default/PerSetTrackingResults
forge-gui/res/skins/default/Thumbs.db
forge-gui/res/skins/default/decks
forge-gui/res/skins/default/layouts
forge-gui/res/skins/default/pics*
forge-gui/res/skins/default/pics_product
forge-gui/res/skins/firebloom/*.log
forge-gui/res/skins/firebloom/PerSetTrackingResults
forge-gui/res/skins/firebloom/Thumbs.db
forge-gui/res/skins/firebloom/decks
forge-gui/res/skins/firebloom/layouts
forge-gui/res/skins/firebloom/pics*
forge-gui/res/skins/firebloom/pics_product
forge-gui/res/skins/inferno/*.log
forge-gui/res/skins/inferno/PerSetTrackingResults
forge-gui/res/skins/inferno/Thumbs.db
forge-gui/res/skins/inferno/decks
forge-gui/res/skins/inferno/layouts
forge-gui/res/skins/inferno/pics*
forge-gui/res/skins/inferno/pics_product
forge-gui/res/skins/innistrad/*.log
forge-gui/res/skins/innistrad/PerSetTrackingResults
forge-gui/res/skins/innistrad/Thumbs.db
forge-gui/res/skins/innistrad/decks
forge-gui/res/skins/innistrad/layouts
forge-gui/res/skins/innistrad/pics*
forge-gui/res/skins/innistrad/pics_product
forge-gui/res/skins/journeyman/*.log
forge-gui/res/skins/journeyman/PerSetTrackingResults
forge-gui/res/skins/journeyman/Thumbs.db
forge-gui/res/skins/journeyman/decks
forge-gui/res/skins/journeyman/layouts
forge-gui/res/skins/journeyman/pics*
forge-gui/res/skins/journeyman/pics_product
forge-gui/res/skins/kamigawa/*.log
forge-gui/res/skins/kamigawa/PerSetTrackingResults
forge-gui/res/skins/kamigawa/Thumbs.db
forge-gui/res/skins/kamigawa/decks
forge-gui/res/skins/kamigawa/layouts
forge-gui/res/skins/kamigawa/pics*
forge-gui/res/skins/kamigawa/pics_product
forge-gui/res/skins/layouts
forge-gui/res/skins/marble_blue/*.log
forge-gui/res/skins/marble_blue/PerSetTrackingResults
forge-gui/res/skins/marble_blue/Thumbs.db
forge-gui/res/skins/marble_blue/decks
forge-gui/res/skins/marble_blue/layouts
forge-gui/res/skins/marble_blue/pics*
forge-gui/res/skins/marble_blue/pics_product
forge-gui/res/skins/metalcraft/*.log
forge-gui/res/skins/metalcraft/PerSetTrackingResults
forge-gui/res/skins/metalcraft/Thumbs.db
forge-gui/res/skins/metalcraft/decks
forge-gui/res/skins/metalcraft/layouts
forge-gui/res/skins/metalcraft/pics*
forge-gui/res/skins/metalcraft/pics_product
forge-gui/res/skins/mythic_rare/*.log
forge-gui/res/skins/mythic_rare/PerSetTrackingResults
forge-gui/res/skins/mythic_rare/Thumbs.db
forge-gui/res/skins/mythic_rare/decks
forge-gui/res/skins/mythic_rare/layouts
forge-gui/res/skins/mythic_rare/pics*
forge-gui/res/skins/mythic_rare/pics_product
forge-gui/res/skins/phyrexia/*.log
forge-gui/res/skins/phyrexia/PerSetTrackingResults
forge-gui/res/skins/phyrexia/Thumbs.db
forge-gui/res/skins/phyrexia/decks
forge-gui/res/skins/phyrexia/layouts
forge-gui/res/skins/phyrexia/pics*
forge-gui/res/skins/phyrexia/pics_product
forge-gui/res/skins/pics*
forge-gui/res/skins/pics_product
forge-gui/res/skins/ravnica/*.log
forge-gui/res/skins/ravnica/PerSetTrackingResults
forge-gui/res/skins/ravnica/Thumbs.db
forge-gui/res/skins/ravnica/decks
forge-gui/res/skins/ravnica/layouts
forge-gui/res/skins/ravnica/pics*
forge-gui/res/skins/ravnica/pics_product
forge-gui/res/skins/rebel/*.log
forge-gui/res/skins/rebel/PerSetTrackingResults
forge-gui/res/skins/rebel/Thumbs.db
forge-gui/res/skins/rebel/decks
forge-gui/res/skins/rebel/layouts
forge-gui/res/skins/rebel/pics*
forge-gui/res/skins/rebel/pics_product
forge-gui/res/skins/sleeping_forest/*.log
forge-gui/res/skins/sleeping_forest/PerSetTrackingResults
forge-gui/res/skins/sleeping_forest/Thumbs.db
forge-gui/res/skins/sleeping_forest/decks
forge-gui/res/skins/sleeping_forest/layouts
forge-gui/res/skins/sleeping_forest/pics*
forge-gui/res/skins/sleeping_forest/pics_product
forge-gui/res/skins/smith/*.log
forge-gui/res/skins/smith/PerSetTrackingResults
forge-gui/res/skins/smith/Thumbs.db
forge-gui/res/skins/smith/decks
forge-gui/res/skins/smith/layouts
forge-gui/res/skins/smith/pics*
forge-gui/res/skins/smith/pics_product
forge-gui/res/skins/the_dale/*.log
forge-gui/res/skins/the_dale/PerSetTrackingResults
forge-gui/res/skins/the_dale/Thumbs.db
forge-gui/res/skins/the_dale/decks
forge-gui/res/skins/the_dale/layouts
forge-gui/res/skins/the_dale/pics*
forge-gui/res/skins/the_dale/pics_product
forge-gui/res/skins/the_simpsons/*.log
forge-gui/res/skins/the_simpsons/PerSetTrackingResults
forge-gui/res/skins/the_simpsons/Thumbs.db
forge-gui/res/skins/the_simpsons/decks
forge-gui/res/skins/the_simpsons/layouts
forge-gui/res/skins/the_simpsons/pics*
forge-gui/res/skins/the_simpsons/pics_product
forge-gui/res/skins/zendikar/*.log
forge-gui/res/skins/zendikar/PerSetTrackingResults
forge-gui/res/skins/zendikar/Thumbs.db
forge-gui/res/skins/zendikar/decks
forge-gui/res/skins/zendikar/layouts
forge-gui/res/skins/zendikar/pics*
forge-gui/res/skins/zendikar/pics_product
forge-gui/target
forge-gui/tools/AllCards.json
forge-gui/tools/EditionTrackingResults
forge-gui/tools/PerSetTrackingResults
forge-gui/tools/oracleScript.log
/forge.profile.properties
/jgv.txt
/nbactions.xml
/pom.xml.next
/pom.xml.releaseBackup
/pom.xml.tag
/release.properties
/target
/test-output
.settings
.classpath
.project
.vscode/settings.json
.vscode/launch.json

View File

@@ -2103,9 +2103,9 @@ public class AiController {
return filterList(list, CardTraitPredicates.hasParam("AiLogic", logic));
}
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, int min, int num, boolean allowRepeat) {
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, List<AbilitySub> possible, int min, int num, boolean allowRepeat) {
if (simPicker != null) {
return simPicker.chooseModeForAbility(sa, min, num, allowRepeat);
return simPicker.chooseModeForAbility(sa, possible, min, num, allowRepeat);
}
return null;
}

View File

@@ -101,7 +101,9 @@ public class ComputerUtil {
sa = GameActionUtil.addExtraKeywordCost(sa);
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
CharmEffect.makeChoices(sa);
if (!CharmEffect.makeChoices(sa)) {
return false;
}
}
if (chooseTargets != null) {
chooseTargets.run();
@@ -261,7 +263,9 @@ public class ComputerUtil {
newSA.setHostCard(game.getAction().moveToStack(source, sa));
if (newSA.getApi() == ApiType.Charm && !newSA.isWrapper()) {
CharmEffect.makeChoices(newSA);
if (!CharmEffect.makeChoices(sa)) {
return false;
}
}
}

View File

@@ -800,8 +800,8 @@ public class PlayerControllerAi extends PlayerController {
}
@Override
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, int min, int num, boolean allowRepeat) {
List<AbilitySub> result = brains.chooseModeForAbility(sa, min, num, allowRepeat);
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, List<AbilitySub> possible, int min, int num, boolean allowRepeat) {
List<AbilitySub> result = brains.chooseModeForAbility(sa, possible, min, num, allowRepeat);
if (result != null) {
return result;
}

View File

@@ -251,7 +251,7 @@ public class AnimateAi extends SpellAbilityAi {
&& sa.getTargetRestrictions() != null
&& sa.getTargetRestrictions().getMinTargets(sa.getHostCard(), sa) == 0;
final CardType types = new CardType();
final CardType types = new CardType(true);
if (sa.hasParam("Types")) {
types.addAll(Arrays.asList(sa.getParam("Types").split(",")));
}
@@ -340,6 +340,14 @@ public class AnimateAi extends SpellAbilityAi {
// select the worst of the best
final Card worst = ComputerUtilCard.getWorstAI(maxList);
if (worst.isLand()) {
// e.g. Clan Guildmage, make sure we're not using the same land we want to animate to activate the ability
this.holdAnimatedTillMain2(ai, worst);
if (!ComputerUtilMana.canPayManaCost(sa, ai, 0)) {
this.releaseHeldTillMain2(ai, worst);
return false;
}
}
this.rememberAnimatedThisTurn(ai, worst);
sa.getTargets().add(worst);
return true;
@@ -383,12 +391,12 @@ public class AnimateAi extends SpellAbilityAi {
}
}
final CardType types = new CardType();
final CardType types = new CardType(true);
if (sa.hasParam("Types")) {
types.addAll(Arrays.asList(sa.getParam("Types").split(",")));
}
final CardType removeTypes = new CardType();
final CardType removeTypes = new CardType(true);
if (sa.hasParam("RemoveTypes")) {
removeTypes.addAll(Arrays.asList(sa.getParam("RemoveTypes").split(",")));
}
@@ -564,4 +572,12 @@ public class AnimateAi extends SpellAbilityAi {
public static boolean isAnimatedThisTurn(Player ai, Card c) {
return AiCardMemory.isRememberedCard(ai, c, AiCardMemory.MemorySet.ANIMATED_THIS_TURN);
}
private void holdAnimatedTillMain2(Player ai, Card c) {
AiCardMemory.rememberCard(ai, c, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
}
private void releaseHeldTillMain2(Player ai, Card c) {
AiCardMemory.forgetCard(ai, c, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
}
}

View File

@@ -2,7 +2,9 @@ package forge.ai.ability;
import com.google.common.collect.Lists;
import forge.ai.*;
import forge.game.ability.AbilityUtils;
import forge.game.ability.effects.CharmEffect;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
@@ -19,10 +21,13 @@ public class CharmAi extends SpellAbilityAi {
// sa is Entwined, no need for extra logic
if (sa.isEntwine()) {
return true;
}
}
final Card source = sa.getHostCard();
final int num = AbilityUtils.calculateAmount(source, sa.getParamOrDefault("CharmNum", "1"), sa);
final int min = sa.hasParam("MinCharmNum") ? AbilityUtils.calculateAmount(source, sa.getParamOrDefault("MinCharmNum", "1"), sa) : num;
final int num = Integer.parseInt(sa.hasParam("CharmNum") ? sa.getParam("CharmNum") : "1");
final int min = sa.hasParam("MinCharmNum") ? Integer.parseInt(sa.getParam("MinCharmNum")) : num;
boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now?
// Reset the chosen list otherwise it will be locked in forever by earlier calls

View File

@@ -1,26 +1,20 @@
package forge.ai.ability;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import forge.ai.AiCardMemory;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.ai.*;
import forge.card.CardType;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.ability.ApiType;
import forge.game.card.*;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import java.util.List;
import java.util.HashSet;
import java.util.Set;
public class ChooseTypeAi extends SpellAbilityAi {
@Override
@@ -31,6 +25,8 @@ public class ChooseTypeAi extends SpellAbilityAi {
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Mirror Entity Avatar")) {
return doMirrorEntityLogic(aiPlayer, sa);
}
} else if ("MostProminentOppControls".equals(sa.getParam("AILogic"))) {
return !chooseType(sa, aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield)).isEmpty();
}
return doTriggerAINoCost(aiPlayer, sa, false);
@@ -44,27 +40,11 @@ public class ChooseTypeAi extends SpellAbilityAi {
return false;
}
CardCollectionView otb = aiPlayer.getCardsIn(ZoneType.Battlefield);
List<String> valid = Lists.newArrayList(CardType.getAllCreatureTypes());
String chosenType = ComputerUtilCard.getMostProminentType(otb, valid);
String chosenType = chooseType(sa, aiPlayer.getCardsIn(ZoneType.Battlefield));
if (chosenType.isEmpty()) {
// Account for the situation when only changelings are on the battlefield
boolean allChangeling = false;
for (Card c : otb) {
if (c.isCreature() && c.hasStartOfKeyword(Keyword.CHANGELING.toString())) {
chosenType = Aggregates.random(valid); // just choose a random type for changelings
allChangeling = true;
break;
}
}
if (!allChangeling) {
// Still empty, probably no creatures on board
return false;
}
return false;
}
int maxX = ComputerUtilMana.determineMaxAffordableX(aiPlayer, sa);
int avgPower = 0;
@@ -127,4 +107,40 @@ public class ChooseTypeAi extends SpellAbilityAi {
return true;
}
private String chooseType(SpellAbility sa, CardCollectionView cards) {
Set<String> valid = new HashSet<>();
if (sa.getSubAbility() != null && sa.getSubAbility().getApi() == ApiType.PumpAll
&& sa.getSubAbility().isCurse() && sa.getSubAbility().hasParam("NumDef")) {
final SpellAbility pumpSa = sa.getSubAbility();
final int defense = AbilityUtils.calculateAmount(sa.getHostCard(), pumpSa.getParam("NumDef"), pumpSa);
for (Card c : cards) {
if (c.isCreature() && c.getNetToughness() <= -defense) {
valid.addAll(c.getType().getCreatureTypes());
}
}
} else {
valid.addAll(CardType.getAllCreatureTypes());
}
String chosenType = ComputerUtilCard.getMostProminentType(cards, valid);
if (chosenType.isEmpty()) {
// Account for the situation when only changelings are on the battlefield
boolean allChangeling = false;
for (Card c : cards) {
if (c.isCreature() && c.hasStartOfKeyword(Keyword.CHANGELING.toString())) {
chosenType = Aggregates.random(valid); // just choose a random type for changelings
allChangeling = true;
break;
}
}
if (!allChangeling) {
// Still empty, probably no creatures on board
return "";
}
}
return chosenType;
}
}

View File

@@ -9,7 +9,6 @@ import forge.ai.ability.ExploreAi;
import forge.ai.simulation.GameStateEvaluator.Score;
import forge.game.Game;
import forge.game.ability.ApiType;
import forge.game.ability.effects.CharmEffect;
import forge.game.card.*;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -371,14 +370,12 @@ public class SpellAbilityPicker {
return bestScore;
}
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, int min, int num, boolean allowRepeat) {
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, List<AbilitySub> choices, int min, int num, boolean allowRepeat) {
if (interceptor != null) {
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
return interceptor.chooseModesForAbility(choices, min, num, allowRepeat);
}
if (plan != null && plan.getSelectedDecision() != null && plan.getSelectedDecision().modes != null) {
Plan.Decision decision = plan.getSelectedDecision();
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
// TODO: Validate that there's no discrepancies between choices and modes?
List<AbilitySub> plannedModes = SpellAbilityChoicesIterator.getModeCombination(choices, decision.modes);
if (plan.getSelectedDecision().targets != null) {

View File

@@ -150,6 +150,9 @@ public final class ImageKeys {
// try with upper case set
file = findFile(dir, setlessFilename + "_" + setCode.toUpperCase());
if (file != null) { return file; }
// try with lower case set
file = findFile(dir, setlessFilename + "_" + setCode.toLowerCase());
if (file != null) { return file; }
// try without set name
file = findFile(dir, setlessFilename);
if (file != null) { return file; }

View File

@@ -123,6 +123,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
private String additionalUnlockSet = "";
private boolean smallSetOverride = false;
private String boosterMustContain = "";
private String boosterReplaceSlotFromPrintSheet = "";
private boolean doublePickToStartRound = false;
private final CardInSet[] cards;
private final Map<String, Integer> tokenNormalized;
@@ -193,6 +194,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
public boolean getSmallSetOverride() { return smallSetOverride; }
public boolean getDoublePickToStartRound() { return doublePickToStartRound; }
public String getBoosterMustContain() { return boosterMustContain; }
public String getBoosterReplaceSlotFromPrintSheet() { return boosterReplaceSlotFromPrintSheet; }
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
@@ -382,6 +384,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
res.doublePickToStartRound = section.getBoolean("DoublePick", false); // for "small" sets with over 200 cards (e.g. Eldritch Moon)
res.boosterMustContain = section.get("BoosterMustContain", ""); // e.g. Dominaria guaranteed legendary creature
res.boosterReplaceSlotFromPrintSheet = section.get("BoosterReplaceSlotFromPrintSheet", ""); // e.g. Zendikar Rising guaranteed double-faced card
return res;
}

View File

@@ -487,7 +487,7 @@ public final class CardRules implements ICardCharacteristics {
if ("T".equals(key)) {
this.faces[this.curFace].addTrigger(value);
} else if ("Types".equals(key)) {
this.faces[this.curFace].setType(CardType.parse(value));
this.faces[this.curFace].setType(CardType.parse(value, false));
} else if ("Text".equals(key) && !"no text".equals(value) && StringUtils.isNotBlank(value)) {
this.faces[this.curFace].setNonAbilityText(value);
}
@@ -557,7 +557,7 @@ public final class CardRules implements ICardCharacteristics {
CardAiHints cah = new CardAiHints(true, true, true, null, null, null);
CardFace[] faces = { new CardFace(name), null};
faces[0].setColor(ColorSet.fromMask(0));
faces[0].setType(CardType.parse(""));
faces[0].setType(CardType.parse("", false));
faces[0].setOracleText("This card is not supported by Forge. Whenever you start a game with this card, it will be bugged.");
faces[0].setNonAbilityText("This card is not supported by Forge.\nWhenever you start a game with this card, it will be bugged.");
faces[0].assignMissingFields();

View File

@@ -50,7 +50,9 @@ import forge.util.Settable;
public final class CardType implements Comparable<CardType>, CardTypeView {
private static final long serialVersionUID = 4629853583167022151L;
public static final CardTypeView EMPTY = new CardType();
public static final CardTypeView EMPTY = new CardType(false);
public static final String AllCreatureTypes = "AllCreatureTypes";
public enum CoreType {
Artifact(true),
@@ -109,11 +111,14 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
private final Set<CoreType> coreTypes = EnumSet.noneOf(CoreType.class);
private final Set<Supertype> supertypes = EnumSet.noneOf(Supertype.class);
private final Set<String> subtypes = Sets.newLinkedHashSet();
private boolean incomplete = false;
private transient String calculatedType = null;
public CardType() {
public CardType(boolean incomplete) {
this.incomplete = incomplete;
}
public CardType(final Iterable<String> from0) {
public CardType(final Iterable<String> from0, boolean incomplete) {
this.incomplete = incomplete;
addAll(from0);
}
public CardType(final CardType from0) {
@@ -152,6 +157,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
changed = true;
}
}
sanisfySubtypes();
return changed;
}
public boolean addAll(final CardType type) {
@@ -159,6 +165,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
if (coreTypes.addAll(type.coreTypes)) { changed = true; }
if (supertypes.addAll(type.supertypes)) { changed = true; }
if (subtypes.addAll(type.subtypes)) { changed = true; }
sanisfySubtypes();
return changed;
}
public boolean addAll(final CardTypeView type) {
@@ -166,6 +173,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
if (Iterables.addAll(coreTypes, type.getCoreTypes())) { changed = true; }
if (Iterables.addAll(supertypes, type.getSupertypes())) { changed = true; }
if (Iterables.addAll(subtypes, type.getSubtypes())) { changed = true; }
sanisfySubtypes();
return changed;
}
@@ -175,6 +183,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
if (supertypes.removeAll(type.supertypes)) { changed = true; }
if (subtypes.removeAll(type.subtypes)) { changed = true; }
if (changed) {
sanisfySubtypes();
calculatedType = null;
return true;
}
@@ -211,6 +220,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
}
if (changed) {
sanisfySubtypes();
calculatedType = null;
}
return changed;
@@ -223,7 +233,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
}
boolean changed = Iterables.removeIf(subtypes, Predicates.IS_CREATURE_TYPE);
// need to remove AllCreatureTypes too when setting Creature Type
if (subtypes.remove("AllCreatureTypes")) {
if (subtypes.remove(AllCreatureTypes)) {
changed = true;
}
subtypes.addAll(ctypes);
@@ -252,7 +262,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
final Set<String> creatureTypes = Sets.newHashSet();
if (isCreature() || isTribal()) {
for (final String t : subtypes) {
if (isACreatureType(t) || t.equals("AllCreatureTypes")) {
if (isACreatureType(t) || t.equals(AllCreatureTypes)) {
creatureTypes.add(t);
}
}
@@ -302,7 +312,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
}
@Override
public boolean hasSubtype(final String subtype) {
if (isACreatureType(subtype) && subtypes.contains("AllCreatureTypes")) {
if (isACreatureType(subtype) && subtypes.contains(AllCreatureTypes)) {
return true;
}
return subtypes.contains(subtype);
@@ -315,7 +325,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
creatureType = toMixedCase(creatureType);
if (!isACreatureType(creatureType)) { return false; }
return subtypes.contains(creatureType) || subtypes.contains("AllCreatureTypes");
return subtypes.contains(creatureType) || subtypes.contains(AllCreatureTypes);
}
private static String toMixedCase(final String s) {
if (s.isEmpty()) {
@@ -485,7 +495,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
if (ct.isRemoveCreatureTypes()) {
Iterables.removeIf(newType.subtypes, Predicates.IS_CREATURE_TYPE);
// need to remove AllCreatureTypes too when removing creature Types
newType.subtypes.remove("AllCreatureTypes");
newType.subtypes.remove(AllCreatureTypes);
}
if (ct.isRemoveArtifactTypes()) {
Iterables.removeIf(newType.subtypes, Predicates.IS_ARTIFACT_TYPE);
@@ -503,29 +513,37 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
}
// sanisfy subtypes
if (newType != null && !newType.subtypes.isEmpty()) {
if (!newType.isCreature() && !newType.isTribal()) {
Iterables.removeIf(newType.subtypes, Predicates.IS_CREATURE_TYPE);
newType.subtypes.remove("AllCreatureTypes");
}
if (!newType.isLand()) {
Iterables.removeIf(newType.subtypes, Predicates.IS_LAND_TYPE);
}
if (!newType.isArtifact()) {
Iterables.removeIf(newType.subtypes, Predicates.IS_ARTIFACT_TYPE);
}
if (!newType.isEnchantment()) {
Iterables.removeIf(newType.subtypes, Predicates.IS_ENCHANTMENT_TYPE);
}
if (!newType.isInstant() && !newType.isSorcery()) {
Iterables.removeIf(newType.subtypes, Predicates.IS_SPELL_TYPE);
}
if (!newType.isPlaneswalker() && !newType.isEmblem()) {
Iterables.removeIf(newType.subtypes, Predicates.IS_WALKER_TYPE);
}
newType.sanisfySubtypes();
}
return newType == null ? this : newType;
}
public void sanisfySubtypes() {
// incomplete types are used for changing effects
if (this.incomplete) {
return;
}
if (!isCreature() && !isTribal()) {
Iterables.removeIf(subtypes, Predicates.IS_CREATURE_TYPE);
subtypes.remove(AllCreatureTypes);
}
if (!isLand()) {
Iterables.removeIf(subtypes, Predicates.IS_LAND_TYPE);
}
if (!isArtifact()) {
Iterables.removeIf(subtypes, Predicates.IS_ARTIFACT_TYPE);
}
if (!isEnchantment()) {
Iterables.removeIf(subtypes, Predicates.IS_ENCHANTMENT_TYPE);
}
if (!isInstant() && !isSorcery()) {
Iterables.removeIf(subtypes, Predicates.IS_SPELL_TYPE);
}
if (!isPlaneswalker() && !isEmblem()) {
Iterables.removeIf(subtypes, Predicates.IS_WALKER_TYPE);
}
}
@Override
public Iterator<String> iterator() {
final Iterator<CoreType> coreTypeIterator = coreTypes.iterator();
@@ -560,7 +578,64 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
return toString().compareTo(o.toString());
}
public boolean sharesSubtypeWith(final CardType ctOther) {
public boolean sharesCreaturetypeWith(final CardTypeView ctOther) {
if (ctOther == null) {
return false;
}
if (this.subtypes.contains(AllCreatureTypes) && ctOther.hasSubtype(AllCreatureTypes)) {
return true;
}
for (final String type : getCreatureTypes()) {
if (ctOther.hasCreatureType(type)) {
return true;
}
}
return false;
}
public boolean sharesLandTypeWith(final CardTypeView ctOther) {
if (ctOther == null) {
return false;
}
for (final String type : getLandTypes()) {
if (ctOther.hasSubtype(type)) {
return true;
}
}
return false;
}
public boolean sharesPermanentTypeWith(final CardTypeView ctOther) {
if (ctOther == null) {
return false;
}
for (final CoreType type : getCoreTypes()) {
if (type.isPermanent && ctOther.hasType(type)) {
return true;
}
}
return false;
}
public boolean sharesCardTypeWith(final CardTypeView ctOther) {
if (ctOther == null) {
return false;
}
for (final CoreType type : getCoreTypes()) {
if (ctOther.hasType(type)) {
return true;
}
}
return false;
}
public boolean sharesSubtypeWith(final CardTypeView ctOther) {
if (ctOther == null) {
return false;
}
for (final String t : ctOther.getSubtypes()) {
if (hasSubtype(t)) {
return true;
@@ -569,11 +644,11 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
return false;
}
public static CardType parse(final String typeText) {
public static CardType parse(final String typeText, boolean incomplete) {
// Most types and subtypes, except "Serra's Realm" and
// "Bolas's Meditation Realm" consist of only one word
final char space = ' ';
final CardType result = new CardType();
final CardType result = new CardType(incomplete);
int iTypeStart = 0;
int iSpace = typeText.indexOf(space);
@@ -593,7 +668,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
}
public static CardType combine(final CardType a, final CardType b) {
final CardType result = new CardType();
final CardType result = new CardType(false);
result.supertypes.addAll(a.supertypes);
result.supertypes.addAll(b.supertypes);
result.coreTypes.addAll(a.coreTypes);

View File

@@ -19,6 +19,12 @@ public interface CardTypeView extends Iterable<String>, Serializable {
boolean hasSupertype(Supertype supertype);
boolean hasSubtype(String subtype);
boolean hasCreatureType(String creatureType);
public boolean sharesCreaturetypeWith(final CardTypeView ctOther);
public boolean sharesLandTypeWith(final CardTypeView ctOther);
public boolean sharesPermanentTypeWith(final CardTypeView ctOther);
public boolean sharesCardTypeWith(final CardTypeView ctOther);
boolean isPermanent();
boolean isCreature();
boolean isPlaneswalker();

View File

@@ -339,6 +339,11 @@ public class BoosterGenerator {
if (!boosterMustContain.isEmpty()) {
ensureGuaranteedCardInBooster(result, template, boosterMustContain);
}
String boosterReplaceSlotFromPrintSheet = edition.getBoosterReplaceSlotFromPrintSheet();
if(!boosterReplaceSlotFromPrintSheet.isEmpty()) {
replaceCardFromExtraSheet(result, boosterReplaceSlotFromPrintSheet);
}
}
return result;
@@ -390,27 +395,69 @@ public class BoosterGenerator {
if (!possibleCards.isEmpty()) {
PaperCard toAdd = Aggregates.random(possibleCards);
PaperCard toRepl = null;
CardRarity tgtRarity = toAdd.getRarity();
// remove the first card of the same rarity, replace it with toAdd. Keep the foil state.
for (PaperCard repl : result) {
if (repl.getRarity() == tgtRarity) {
toRepl = repl;
break;
}
}
if (toRepl != null) {
if (toRepl.isFoil()) {
toAdd = StaticData.instance().getCommonCards().getFoiled(toAdd);
}
result.remove(toRepl);
result.add(toAdd);
}
BoosterGenerator.replaceCard(result, toAdd);
}
}
}
/**
* Replaces an already present card in the booster with a card from the supplied print sheet.
* Nothing is replaced if there is no matching rarity found.
* @param booster in which a card gets replaced
* @param printSheetKey
*/
public static void replaceCardFromExtraSheet(List<PaperCard> booster, String printSheetKey) {
PrintSheet replacementSheet = StaticData.instance().getPrintSheets().get(printSheetKey);
PaperCard toAdd = replacementSheet.random(1, false).get(0);
BoosterGenerator.replaceCard(booster, toAdd);
}
/**
* Replaces an already present card with the supplied card of the same (or similar in case or rare/mythic)
* rarity in the supplied booster. Nothing is replaced if there is no matching rarity found.
* @param booster in which a card gets replaced
* @param toAdd new card which replaces a card in the booster
*/
public static void replaceCard(List<PaperCard> booster, PaperCard toAdd) {
Predicate<PaperCard> rarityPredicate = null;
switch(toAdd.getRarity()){
case BasicLand:
rarityPredicate = Presets.IS_BASIC_LAND;
break;
case Common:
rarityPredicate = Presets.IS_COMMON;
break;
case Uncommon:
rarityPredicate = Presets.IS_UNCOMMON;
break;
case Rare:
case MythicRare:
rarityPredicate = Presets.IS_RARE_OR_MYTHIC;
break;
default:
rarityPredicate = Presets.IS_SPECIAL;
}
PaperCard toReplace = null;
// Find first card in booster that matches the rarity
for (PaperCard card : booster) {
if(rarityPredicate.apply(card)) {
toReplace = card;
break;
}
}
// Replace card if match is found
if (toReplace != null) {
// Keep the foil state
if (toReplace.isFoil()) {
toAdd = StaticData.instance().getCommonCards().getFoiled(toAdd);
}
booster.remove(toReplace);
booster.add(toAdd);
}
}
public static void addCardsFromExtraSheet(List<PaperCard> dest, String printSheetKey) {
PrintSheet extraSheet = getPrintSheet(printSheetKey);

View File

@@ -20,9 +20,12 @@ package forge.game;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Table;
import com.google.common.eventbus.EventBus;
import forge.card.CardRarity;
import forge.card.CardStateName;
@@ -53,6 +56,8 @@ import forge.util.Visitor;
import java.util.*;
import org.apache.commons.lang3.tuple.Pair;
/**
* Represents the state of a <i>single game</i>, a new instance is created for each game.
*/
@@ -86,6 +91,8 @@ public class Game {
private Map<Player, PlayerCollection> attackedThisTurn = Maps.newHashMap();
private Map<Player, PlayerCollection> attackedLastTurn = Maps.newHashMap();
private Table<CounterType, Player, List<Pair<Card, Integer>>> countersAddedThisTurn = HashBasedTable.create();
private Player monarch = null;
private Player monarchBeginTurn = null;
@@ -939,4 +946,46 @@ public class Game {
}
return result;
}
public void onCleanupPhase() {
clearCounterAddedThisTurn();
for (Player player : getPlayers()) {
player.onCleanupPhase();
}
}
public void addCounterAddedThisTurn(Player putter, CounterType cType, Card card, Integer value) {
if (putter == null || card == null || value <= 0) {
return;
}
List<Pair<Card, Integer>> result = countersAddedThisTurn.get(cType, putter);
if (result == null) {
result = Lists.newArrayList();
}
result.add(Pair.of(CardUtil.getLKICopy(card), value));
if (!countersAddedThisTurn.contains(cType, putter)) {
countersAddedThisTurn.put(cType, putter, result);
}
}
public int getCounterAddedThisTurn(CounterType cType, String validPlayer, String validCard, Card source, Player sourceController, SpellAbility spellAbility) {
int result = 0;
if (!countersAddedThisTurn.containsRow(cType)) {
return result;
}
for (Map.Entry<Player, List<Pair<Card, Integer>>> e : countersAddedThisTurn.row(cType).entrySet()) {
if (e.getKey().isValid(validPlayer.split(","), sourceController, source, spellAbility)) {
for (Pair<Card, Integer> p : e.getValue()) {
if (p.getKey().isValid(validCard.split(","), sourceController, source, spellAbility)) {
result += p.getValue();
}
}
}
}
return result;
}
public void clearCounterAddedThisTurn() {
countersAddedThisTurn.clear();
}
}

View File

@@ -286,7 +286,7 @@ public final class AbilityFactory {
spellAbility.setDescription(sb.toString());
} else if (api == ApiType.Charm) {
spellAbility.setDescription(CharmEffect.makeSpellDescription(spellAbility));
spellAbility.setDescription(CharmEffect.makeFormatedDescription(spellAbility));
} else {
spellAbility.setDescription("");
}

View File

@@ -374,7 +374,7 @@ public class AbilityUtils {
if (StringUtils.isBlank(amount)) { return 0; }
if (card == null) { return 0; }
final Player player = card.getController();
final Game game = player.getGame();
final Game game = player == null ? card.getGame() : player.getGame();
// Strip and save sign for calculations
final boolean startsWithPlus = amount.charAt(0) == '+';
@@ -1593,6 +1593,8 @@ public class AbilityUtils {
final String[] sq;
sq = l[0].split("\\.");
final Game game = c.getGame();
if (ctb != null) {
// Count$Compare <int comparator value>.<True>.<False>
if (sq[0].startsWith("Compare")) {
@@ -1776,6 +1778,13 @@ public class AbilityUtils {
}
}
}
if (l[0].startsWith("CountersAddedThisTurn")) {
final String[] parts = l[0].split(" ");
CounterType cType = CounterType.getType(parts[1]);
return CardFactoryUtil.doXMath(game.getCounterAddedThisTurn(cType, parts[2], parts[3], c, sa.getActivatingPlayer(), sa), expr, c);
}
}
}
return CardFactoryUtil.xCount(c, s2);

View File

@@ -78,14 +78,17 @@ public abstract class SpellAbilityEffect {
// Own description
String stackDesc = params.get("StackDescription");
if (stackDesc != null) {
if ("SpellDescription".equalsIgnoreCase(stackDesc)) { // by typing "none" they want to suppress output
// by typing "SpellDescription" they want to bypass the Effect's string builder
if ("SpellDescription".equalsIgnoreCase(stackDesc)) {
if (params.get("SpellDescription") != null) {
sb.append(TextUtil.fastReplace(params.get("SpellDescription"),
"CARDNAME", sa.getHostCard().getName()));
}
if (sa.getTargets() != null && !sa.getTargets().getTargets().isEmpty()) {
sb.append(" (Targeting: ").append(sa.getTargets().getTargets()).append(")");
}
String currentName = (sa.getHostCard().getName());
String desc1 = TextUtil.fastReplace(params.get("SpellDescription"), "CARDNAME", currentName);
String desc = TextUtil.fastReplace(desc1, "NICKNAME", currentName.split(",")[0]);
sb.append(desc);
}
if (sa.getTargets() != null && !sa.getTargets().getTargets().isEmpty()) {
sb.append(" (Targeting: ").append(sa.getTargets().getTargets()).append(")");
}
} else if (!"None".equalsIgnoreCase(stackDesc)) { // by typing "none" they want to suppress output
makeSpellDescription(sa, sb, stackDesc);
}
@@ -147,7 +150,11 @@ public abstract class SpellAbilityEffect {
if ("}".equals(t)) { isPlainText = true; continue; }
if (isPlainText) {
sb.append(TextUtil.fastReplace(t, "CARDNAME", sa.getHostCard().getName()));
if(t.startsWith("NICKNAME")) {
sb.append(TextUtil.fastReplace(t,"NICKNAME", sa.getHostCard().getName().split(",")[0]));
} else {
sb.append(TextUtil.fastReplace(t, "CARDNAME", sa.getHostCard().getName()));
}
} else {
final List<? extends GameObject> objs;
if (t.startsWith("p:")) {

View File

@@ -51,12 +51,12 @@ public class AnimateAllEffect extends AnimateEffectBase {
final boolean permanent = sa.hasParam("Permanent");
final CardType types = new CardType();
final CardType types = new CardType(true);
if (sa.hasParam("Types")) {
types.addAll(Arrays.asList(sa.getParam("Types").split(",")));
}
final CardType removeTypes = new CardType();
final CardType removeTypes = new CardType(true);
if (sa.hasParam("RemoveTypes")) {
removeTypes.addAll(Arrays.asList(sa.getParam("RemoveTypes").split(",")));
}

View File

@@ -64,12 +64,12 @@ public class AnimateEffect extends AnimateEffectBase {
final boolean permanent = sa.hasParam("Permanent");
final CardType types = new CardType();
final CardType types = new CardType(true);
if (sa.hasParam("Types")) {
types.addAll(Arrays.asList(sa.getParam("Types").split(",")));
}
final CardType removeTypes = new CardType();
final CardType removeTypes = new CardType(true);
if (sa.hasParam("RemoveTypes")) {
removeTypes.addAll(Arrays.asList(sa.getParam("RemoveTypes").split(",")));
}

View File

@@ -36,7 +36,9 @@ import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView;
import forge.util.Localizer;
import forge.util.CardTranslation;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -201,6 +203,26 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
}
sb.append(" Then shuffle that library.");
} else if (origin.equals("Sideboard")) {
sb.append(chooserNames);
//currently Reveal is always True in ChangeZone
if (sa.hasParam("Reveal")) {
sb.append(" may reveal ").append(num).append(" ").append(type).append(" from outside the game and put ");
if (num == 1) {
sb.append("it ");
} else {
sb.append("them ");
}
sb.append("into their ").append(destination.toLowerCase()).append(".");
} else {
if (sa.hasParam("Mandatory")) {
sb.append(" puts ");
} else {
sb.append(" may put ");
}
sb.append(num).append(" ").append(type).append(" from outside the game into their ");
sb.append(destination.toLowerCase()).append(".");
}
} else if (origin.equals("Hand")) {
sb.append(chooserNames);
if (!chooserNames.equals(fetcherNames)) {
@@ -282,7 +304,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
final StringBuilder sbTargets = new StringBuilder();
Iterable<Card> tgts;
if (sa.usesTargeting()) {
tgts = sa.getTargets().getTargetCards();
@@ -290,10 +311,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
// otherwise add self to list and go from there
tgts = sa.knownDetermineDefined(sa.getParam("Defined"));
}
for (final Card c : tgts) {
sbTargets.append(" ").append(c);
}
sbTargets.append(" ").append(StringUtils.join(tgts, ", "));
final String targetname = sbTargets.toString();
@@ -302,12 +320,16 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
final String fromGraveyard = " from the graveyard";
if (destination.equals(ZoneType.Battlefield)) {
sb.append("Put").append(targetname);
if (ZoneType.Graveyard.equals(origin)) {
sb.append(fromGraveyard);
sb.append("Return").append(targetname);
} else {
sb.append("Put").append(targetname);
}
if (ZoneType.Graveyard.equals(origin)) {
sb.append(fromGraveyard).append(" to the battlefield");
} else {
sb.append(" onto the battlefield");
}
sb.append(" onto the battlefield");
if (sa.hasParam("Tapped")) {
sb.append(" tapped");
}
@@ -1159,9 +1181,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
if (sa.hasParam("FaceDownAddType")) {
for (String type : sa.getParam("FaceDownAddType").split(",")) {
c.addType(type);
}
c.addType(Arrays.asList(sa.getParam("FaceDownAddType").split(" & ")));
}
if (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness")

View File

@@ -20,27 +20,24 @@ public class CharmEffect extends SpellAbilityEffect {
public static List<AbilitySub> makePossibleOptions(final SpellAbility sa) {
final Card source = sa.getHostCard();
Iterable<Object> restriction = null;
List<String> restriction = null;
if (sa.hasParam("ChoiceRestriction")) {
String rest = sa.getParam("ChoiceRestriction");
if (rest.equals("NotRemembered")) {
restriction = source.getRemembered();
if (rest.equals("ThisGame")) {
restriction = source.getChosenModesGame(sa);
} else if (rest.equals("ThisTurn")) {
restriction = source.getChosenModesTurn(sa);
}
}
int indx = 0;
List<AbilitySub> choices = Lists.newArrayList(sa.getAdditionalAbilityList("Choices"));
if (restriction != null) {
List<AbilitySub> toRemove = Lists.newArrayList();
for (Object o : restriction) {
if (o instanceof AbilitySub) {
String abText = ((AbilitySub)o).getDescription();
for (AbilitySub ch : choices) {
if (ch.getDescription().equals(abText)) {
toRemove.add(ch);
}
}
for (AbilitySub ch : choices) {
if (restriction.contains(ch.getDescription())) {
toRemove.add(ch);
}
}
choices.removeAll(toRemove);
@@ -54,55 +51,24 @@ public class CharmEffect extends SpellAbilityEffect {
return choices;
}
public static String makeSpellDescription(SpellAbility sa) {
int num = Integer.parseInt(sa.getParamOrDefault("CharmNum", "1"));
int min = Integer.parseInt(sa.getParamOrDefault("MinCharmNum", String.valueOf(num)));
boolean repeat = sa.hasParam("CanRepeatModes");
boolean random = sa.hasParam("Random");
boolean oppChooses = "Opponent".equals(sa.getParam("Chooser"));
List<AbilitySub> list = CharmEffect.makePossibleOptions(sa);
StringBuilder sb = new StringBuilder();
sb.append(sa.getCostDescription());
sb.append(oppChooses ? "An opponent chooses " : "Choose ");
if (num == min) {
sb.append(Lang.getNumeral(num));
} else if (min == 0) {
sb.append("up to ").append(Lang.getNumeral(num));
} else {
sb.append(Lang.getNumeral(min)).append(" or ").append(list.size() == 2 ? "both" : "more");
}
if (random) {
sb.append("at random.");
}
if (repeat) {
sb.append(". You may choose the same mode more than once.");
}
sb.append(" - ");
int i = 0;
for (AbilitySub sub : list) {
if (i > 0) {
sb.append("; ");
}
sb.append(sub.getParam("SpellDescription"));
++i;
}
return sb.toString();
}
public static String makeFormatedDescription(SpellAbility sa) {
int num = Integer.parseInt(sa.getParamOrDefault("CharmNum", "1"));
int min = Integer.parseInt(sa.getParamOrDefault("MinCharmNum", String.valueOf(num)));
Card source = sa.getHostCard();
List<AbilitySub> list = CharmEffect.makePossibleOptions(sa);
final int num;
// hotfix for Vindictive Lich when using getCardForUi
if (source.getController() == null && sa.getParamOrDefault("CharmNum", "1").contains("MaxUniqueOpponents")) {
// using getCardForUi game is not set, so can't guess max charm
num = Integer.MAX_VALUE;
} else {
num = Math.min(AbilityUtils.calculateAmount(source, sa.getParamOrDefault("CharmNum", "1"), sa), list.size());
}
final int min = sa.hasParam("MinCharmNum") ? AbilityUtils.calculateAmount(source, sa.getParamOrDefault("MinCharmNum", "1"), sa) : num;
boolean repeat = sa.hasParam("CanRepeatModes");
boolean random = sa.hasParam("Random");
boolean oppChooses = "Opponent".equals(sa.getParam("Chooser"));
List<AbilitySub> list = CharmEffect.makePossibleOptions(sa);
StringBuilder sb = new StringBuilder();
sb.append(sa.getCostDescription());
sb.append(oppChooses ? "An opponent chooses " : "Choose ");
@@ -117,8 +83,10 @@ public class CharmEffect extends SpellAbilityEffect {
if (sa.hasParam("ChoiceRestriction")) {
String rest = sa.getParam("ChoiceRestriction");
if (rest.equals("NotRemembered")) {
if (rest.equals("ThisGame")) {
sb.append(" that hasn't been chosen");
} else if (rest.equals("ThisTurn")) {
sb.append(" that hasn't been chosen this turn");
}
}
@@ -163,39 +131,52 @@ public class CharmEffect extends SpellAbilityEffect {
//this resets all previous choices
sa.setSubAbility(null);
List<AbilitySub> choices = makePossibleOptions(sa);
// Entwine does use all Choices
if (sa.isEntwine()) {
chainAbilities(sa, makePossibleOptions(sa));
return true;
}
final int num = sa.hasParam("CharmNumOnResolve") ?
AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("CharmNumOnResolve"), sa)
: Integer.parseInt(sa.getParamOrDefault("CharmNum", "1"));
final int min = sa.hasParam("MinCharmNum") ? Integer.parseInt(sa.getParam("MinCharmNum")) : num;
if (sa.hasParam("Random")) {
chainAbilities(sa, Aggregates.random(makePossibleOptions(sa), num));
chainAbilities(sa, choices);
return true;
}
Card source = sa.getHostCard();
Player activator = sa.getActivatingPlayer();
final int num = Math.min(AbilityUtils.calculateAmount(source, sa.getParamOrDefault("CharmNum", "1"), sa), choices.size());
final int min = sa.hasParam("MinCharmNum") ? AbilityUtils.calculateAmount(source, sa.getParamOrDefault("MinCharmNum", "1"), sa) : num;
// if the amount of choices is smaller than min then they can't be chosen
if (min > choices.size()) {
return false;
}
if (sa.hasParam("Random")) {
chainAbilities(sa, Aggregates.random(choices, num));
return true;
}
Player chooser = sa.getActivatingPlayer();
if (sa.hasParam("Chooser")) {
// Three modal cards require you to choose a player to make the modal choice'
// Two of these also reference the chosen player during the spell effect
//String choosers = sa.getParam("Chooser");
//String choosers = sa.getParam("Chooser");
FCollection<Player> opponents = activator.getOpponents(); // all cards have Choser$ Opponent, so it's hardcoded here
chooser = activator.getController().chooseSingleEntityForEffect(opponents, sa, "Choose an opponent", null);
source.setChosenPlayer(chooser);
}
List<AbilitySub> chosen = chooser.getController().chooseModeForAbility(sa, min, num, sa.hasParam("CanRepeatModes"));
List<AbilitySub> chosen = chooser.getController().chooseModeForAbility(sa, choices, min, num, sa.hasParam("CanRepeatModes"));
chainAbilities(sa, chosen);
return chosen != null && !chosen.isEmpty();
// trigger without chosen modes are removed from stack
if (sa.isTrigger()) {
return chosen != null && !chosen.isEmpty();
}
// for spells and activated abilities it is possible to chose zero if minCharmNum allows it
return true;
}
private static void chainAbilities(SpellAbility sa, List<AbilitySub> chosen) {
@@ -239,7 +220,7 @@ public class CharmEffect extends SpellAbilityEffect {
// add Clone to Tail of sa
sa.appendSubAbility(clone);
}
}

View File

@@ -38,7 +38,7 @@ public class StoreSVarEffect extends SpellAbilityEffect {
int value = 0;
if (type.equals("Count")) {
value = CardFactoryUtil.xCount(source, expr);
value = AbilityUtils.xCount(source, expr, sa);
}
else if (type.equals("Number")) {
value = Integer.valueOf(expr);

View File

@@ -26,7 +26,6 @@ import forge.ImageKeys;
import forge.StaticData;
import forge.card.*;
import forge.card.CardDb.SetPreference;
import forge.card.CardType.CoreType;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostParser;
import forge.game.*;
@@ -34,7 +33,6 @@ import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.ability.effects.CharmEffect;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.cost.CostSacrifice;
@@ -280,6 +278,11 @@ public class Card extends GameEntity implements Comparable<Card> {
private final Table<SpellAbility, StaticAbility, Integer> numberTurnActivationsStatic = HashBasedTable.create();
private final Table<SpellAbility, StaticAbility, Integer> numberGameActivationsStatic = HashBasedTable.create();
private final Map<SpellAbility, List<String>> chosenModesTurn = Maps.newHashMap();
private final Map<SpellAbility, List<String>> chosenModesGame = Maps.newHashMap();
private final Table<SpellAbility, StaticAbility, List<String>> chosenModesTurnStatic = HashBasedTable.create();
private final Table<SpellAbility, StaticAbility, List<String>> chosenModesGameStatic = HashBasedTable.create();
// Enumeration for CMC request types
public enum SplitCMCMode {
@@ -664,6 +667,7 @@ public class Card extends GameEntity implements Comparable<Card> {
}
facedown = false;
updateStateForView(); //fixes cards with backside viewable
// need to run faceup commands, currently
// it does cleanup the modified facedown state
if (result) {
@@ -1239,7 +1243,7 @@ public class Card extends GameEntity implements Comparable<Card> {
final int loyaltyBefore = getCurrentLoyalty();
setCounters(counterType, newValue);
getController().addCounterToPermThisTurn(counterType, addAmount);
getGame().addCounterAddedThisTurn(source, counterType, this, addAmount);
view.updateCounters(this);
//fire card stats changed event if p/t bonuses or loyalty changed from added counters
@@ -1267,7 +1271,8 @@ public class Card extends GameEntity implements Comparable<Card> {
}
} else {
setCounters(counterType, newValue);
getController().addCounterToPermThisTurn(counterType, addAmount);
getGame().addCounterAddedThisTurn(source, counterType, this, addAmount);
view.updateCounters(this);
}
if (newValue <= 0) {
@@ -1714,8 +1719,7 @@ public class Card extends GameEntity implements Comparable<Card> {
final String costString2 = keyword.split(":")[2];
final Cost cost1 = new Cost(costString1, false);
final Cost cost2 = new Cost(costString2, false);
sbLong.append("As an additional cost to cast ")
.append(getName()).append(", ")
sbLong.append("As an additional cost to cast this spell, ")
.append(cost1.toSimpleString())
.append(" or pay ")
.append(cost2.toSimpleString())
@@ -2070,8 +2074,9 @@ public class Card extends GameEntity implements Comparable<Card> {
}
final Card host = stAb.getHostCard();
if (isValid(stAb.getParam("ValidAttacker").split(","), host.getController(), host, null)) {
String desc = stAb.toString();
desc = TextUtil.fastReplace(desc, "CARDNAME", host.getName());
String currentName = (host.getName());
String desc1 = TextUtil.fastReplace(stAb.toString(), "CARDNAME", currentName);
String desc = TextUtil.fastReplace(desc1,"NICKNAME", currentName.split(",")[0]);
if (host.getEffectSource() != null) {
desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", host.getEffectSource().getName());
}
@@ -2203,8 +2208,7 @@ public class Card extends GameEntity implements Comparable<Card> {
final String[] k = keyword.split(":");
final Cost cost1 = new Cost(k[1], false);
final Cost cost2 = new Cost(k[2], false);
sbBefore.append("As an additional cost to cast ")
.append(state.getName()).append(", ")
sbBefore.append("As an additional cost to cast this spell, ")
.append(cost1.toSimpleString())
.append(" or pay ")
.append(cost2.toSimpleString())
@@ -2325,14 +2329,7 @@ public class Card extends GameEntity implements Comparable<Card> {
private String formatSpellAbility(final SpellAbility sa) {
final StringBuilder sb = new StringBuilder();
final String elementText = sa.toString();
//Determine if a card has multiple choices, then format it in an easier to read list.
if (ApiType.Charm.equals(sa.getApi())) {
sb.append(CharmEffect.makeFormatedDescription(sa));
} else {
sb.append(elementText).append("\r\n");
}
sb.append(sa.toString()).append("\r\n");
return sb.toString();
}
@@ -3100,6 +3097,10 @@ public class Card extends GameEntity implements Comparable<Card> {
currentState.addType(type0);
}
public final void addType(final Iterable<String> type0) {
currentState.addType(type0);
}
public final void removeType(final CardType.Supertype st) {
currentState.removeType(st);
}
@@ -3197,11 +3198,11 @@ public class Card extends GameEntity implements Comparable<Card> {
CardType addType = null;
CardType removeType = null;
if (types != null) {
addType = new CardType(types);
addType = new CardType(types, true);
}
if (removeTypes != null) {
removeType = new CardType(removeTypes);
removeType = new CardType(removeTypes, true);
}
addChangedCardTypes(addType, removeType, removeSuperTypes, removeCardTypes, removeSubTypes,
@@ -3962,7 +3963,7 @@ public class Card extends GameEntity implements Comparable<Card> {
public final void addChangedTextTypeWord(final String originalWord, final String newWord, final Long timestamp) {
changedTextTypes.add(timestamp, originalWord, newWord);
if (getType().hasSubtype(originalWord)) {
addChangedCardTypes(CardType.parse(newWord), CardType.parse(originalWord),
addChangedCardTypes(CardType.parse(newWord, true), CardType.parse(originalWord, true),
false, false, false, false, false, false, false, timestamp);
}
updateKeywordsChangedText(timestamp);
@@ -4603,84 +4604,34 @@ public class Card extends GameEntity implements Comparable<Card> {
if (c1 == null) {
return false;
}
for (final String type : getType().getCreatureTypes()) {
if (type.equals("AllCreatureTypes") && c1.hasACreatureType()) {
return true;
}
if (c1.getType().hasCreatureType(type)) {
return true;
}
}
return false;
return getType().sharesCreaturetypeWith(c1.getType());
}
public final boolean sharesLandTypeWith(final Card c1) {
if (c1 == null) {
return false;
}
for (final String type : getType().getLandTypes()) {
if (c1.getType().hasSubtype(type)) {
return true;
}
}
return false;
return getType().sharesLandTypeWith(c1.getType());
}
public final boolean sharesPermanentTypeWith(final Card c1) {
if (c1 == null) {
return false;
}
for (final CoreType type : getType().getCoreTypes()) {
if (type.isPermanent && c1.getType().hasType(type)) {
return true;
}
}
return false;
return getType().sharesPermanentTypeWith(c1.getType());
}
public final boolean sharesCardTypeWith(final Card c1) {
for (final CoreType type : getType().getCoreTypes()) {
if (c1.getType().hasType(type)) {
return true;
}
if (c1 == null) {
return false;
}
return false;
}
public final boolean sharesTypeWith(final Card c1) {
for (final String type : getType()) {
if (c1.getType().hasStringType(type)) {
return true;
}
}
return false;
return getType().sharesCardTypeWith(c1.getType());
}
public final boolean sharesControllerWith(final Card c1) {
return c1 != null && getController().equals(c1.getController());
}
public final boolean hasACreatureType() {
for (final String type : getType().getSubtypes()) {
if (forge.card.CardType.isACreatureType(type) || type.equals("AllCreatureTypes")) {
return true;
}
}
return false;
}
public final boolean hasALandType() {
for (final String type : getType().getSubtypes()) {
if (forge.card.CardType.isALandType(type) || forge.card.CardType.isABasicLandType(type)) {
return true;
}
}
return false;
}
public final boolean hasABasicLandType() {
for (final String type : getType().getSubtypes()) {
if (forge.card.CardType.isABasicLandType(type)) {
@@ -5316,8 +5267,8 @@ public class Card extends GameEntity implements Comparable<Card> {
public final void animateBestow(final boolean updateView) {
bestowTimestamp = getGame().getNextTimestamp();
addChangedCardTypes(new CardType(Collections.singletonList("Aura")),
new CardType(Collections.singletonList("Creature")),
addChangedCardTypes(new CardType(Collections.singletonList("Aura"), true),
new CardType(Collections.singletonList("Creature"), true),
false, false, false, false, false, false, true, bestowTimestamp, updateView);
addChangedCardKeywords(Collections.singletonList("Enchant creature"), Lists.newArrayList(),
false, false, bestowTimestamp, updateView);
@@ -5915,6 +5866,7 @@ public class Card extends GameEntity implements Comparable<Card> {
clearBlockedThisTurn();
resetMayPlayTurn();
resetExtertedThisTurn();
resetChosenModeTurn();
}
public boolean hasETBTrigger(final boolean drawbackOnly) {
@@ -6045,6 +5997,15 @@ public class Card extends GameEntity implements Comparable<Card> {
}
}
if (isPermanent() && !isLand() && source.getActivatingPlayer().hasKeyword("You can't sacrifice nonland permanents to cast spells or activate abilities.")) {
Cost srcCost = source.getPayCosts();
if (srcCost != null) {
if (srcCost.hasSpecificCostType(CostSacrifice.class)) {
return false;
}
}
}
return getController().canSacrificeBy(source);
}
@@ -6582,6 +6543,94 @@ public class Card extends GameEntity implements Comparable<Card> {
numberTurnActivationsStatic.clear();
}
public List<String> getChosenModesTurn(SpellAbility ability) {
SpellAbility original = null;
SpellAbility root = ability.getRootAbility();
// because trigger spell abilities are copied, try to get original one
if (root.isTrigger()) {
original = root.getTrigger().getOverridingAbility();
} else {
original = ability.getOriginalAbility();
if (original == null) {
original = ability;
}
}
if (ability.getGrantorStatic() != null) {
return chosenModesTurnStatic.get(original, ability.getGrantorStatic());
}
return chosenModesTurn.get(original);
}
public List<String> getChosenModesGame(SpellAbility ability) {
SpellAbility original = null;
SpellAbility root = ability.getRootAbility();
// because trigger spell abilities are copied, try to get original one
if (root.isTrigger()) {
original = root.getTrigger().getOverridingAbility();
} else {
original = ability.getOriginalAbility();
if (original == null) {
original = ability;
}
}
if (ability.getGrantorStatic() != null) {
return chosenModesGameStatic.get(original, ability.getGrantorStatic());
}
return chosenModesGame.get(original);
}
public void addChosenModes(SpellAbility ability, String mode) {
SpellAbility original = null;
SpellAbility root = ability.getRootAbility();
// because trigger spell abilities are copied, try to get original one
if (root.isTrigger()) {
original = root.getTrigger().getOverridingAbility();
} else {
original = ability.getOriginalAbility();
if (original == null) {
original = ability;
}
}
if (ability.getGrantorStatic() != null) {
List<String> result = chosenModesTurnStatic.get(original, ability.getGrantorStatic());
if (result == null) {
result = Lists.newArrayList();
chosenModesTurnStatic.put(original, ability.getGrantorStatic(), result);
}
result.add(mode);
result = chosenModesGameStatic.get(original, ability.getGrantorStatic());
if (result == null) {
result = Lists.newArrayList();
chosenModesGameStatic.put(original, ability.getGrantorStatic(), result);
}
result.add(mode);
} else {
List<String> result = chosenModesTurn.get(original);
if (result == null) {
result = Lists.newArrayList();
chosenModesTurn.put(original, result);
}
result.add(mode);
result = chosenModesGame.get(original);
if (result == null) {
result = Lists.newArrayList();
chosenModesGame.put(original, result);
}
result.add(mode);
}
}
public void resetChosenModeTurn() {
chosenModesTurn.clear();
chosenModesTurnStatic.clear();
}
public int getPlaneswalkerAbilityActivated() {
return planeswalkerAbilityActivated;
}

View File

@@ -587,7 +587,7 @@ public class CardFactory {
String shortColors = "";
if (sa.hasParam("AddTypes")) {
types.addAll(Arrays.asList(sa.getParam("AddTypes").split(",")));
types.addAll(Arrays.asList(sa.getParam("AddTypes").split(" & ")));
}
if (sa.hasParam("AddKeywords")) {
@@ -654,9 +654,7 @@ public class CardFactory {
state.removeType(CardType.Supertype.Legendary);
}
for (final String type : types) {
state.addType(type);
}
state.addType(types);
if (creatureTypes != null) {
state.setCreatureTypes(creatureTypes);
@@ -731,7 +729,8 @@ public class CardFactory {
String name = TextUtil.fastReplace(
TextUtil.fastReplace(host.getName(), ",", ""),
" ", "_").toLowerCase();
state.setImageKey(ImageKeys.getTokenKey("embalm_" + name));
String set = host.getSetCode().toLowerCase();
state.setImageKey(ImageKeys.getTokenKey("embalm_" + name + "_" + set));
}
if (sa.hasParam("Eternalize") && out.isEternalized()) {
@@ -744,7 +743,8 @@ public class CardFactory {
String name = TextUtil.fastReplace(
TextUtil.fastReplace(host.getName(), ",", ""),
" ", "_").toLowerCase();
state.setImageKey(ImageKeys.getTokenKey("eternalize_" + name));
String set = host.getSetCode().toLowerCase();
state.setImageKey(ImageKeys.getTokenKey("eternalize_" + name + "_" + set));
}
// set the host card for copied replacement effects
@@ -793,7 +793,7 @@ public class CardFactory {
if (sa.hasParam("SetCreatureTypes")) {
// currently only Changeling and similar should be affected by that
// other cards using AddType$ ChosenType should not
if (sta.hasParam("AddType") && "AllCreatureTypes".equals(sta.getParam("AddType"))) {
if (sta.hasParam("AddType") && CardType.AllCreatureTypes.equals(sta.getParam("AddType"))) {
state.removeStaticAbility(sta);
}
}

View File

@@ -841,14 +841,6 @@ public class CardFactoryUtil {
return doXMath(maxNum, m, c);
}
// Count$CountersAddedToPermYouCtrl <CounterType>
if (l[0].startsWith("CountersAddedToPermYouCtrl")) {
final String[] components = l[0].split(" ", 2);
final CounterType counterType = CounterType.getType(components[1]);
int n = cc.getCounterToPermThisTurn(counterType);
return doXMath(n, m, c);
}
if (l[0].startsWith("CommanderCastFromCommandZone")) {
// only used by Opal Palace, and it does add the trigger to the card
return doXMath(cc.getCommanderCast(c), m, c);
@@ -1124,7 +1116,7 @@ public class CardFactoryUtil {
for (Card card : cards) {
Iterables.addAll(creatTypes, card.getType().getCreatureTypes());
}
int n = creatTypes.contains("AllCreatureTypes") ? CardType.getAllCreatureTypes().size() : creatTypes.size();
int n = creatTypes.contains(CardType.AllCreatureTypes) ? CardType.getAllCreatureTypes().size() : creatTypes.size();
return doXMath(n, m, c);
}
@@ -1206,7 +1198,7 @@ public class CardFactoryUtil {
// Figure out how to count each class separately.
for (Card card : adventurers) {
Set<String> creatureTypes = card.getType().getCreatureTypes();
boolean anyType = creatureTypes.contains("AllCreatureTypes");
boolean anyType = creatureTypes.contains(CardType.AllCreatureTypes);
creatureTypes.retainAll(partyTypes);
if (anyType || creatureTypes.size() == 4) {
@@ -2005,7 +1997,7 @@ public class CardFactoryUtil {
// Remove Duplicated types
final Set<String> creatureTypes = c.getType().getCreatureTypes();
for (String creatureType : creatureTypes) {
if (creatureType.equals("AllCreatureTypes")) {
if (creatureType.equals(CardType.AllCreatureTypes)) {
allCreatureType++;
}
else {
@@ -2913,7 +2905,7 @@ public class CardFactoryUtil {
final String repeatStr = "DB$ RepeatEach | RepeatPlayers$ OpponentsOtherThanDefendingPlayer | ChangeZoneTable$ True";
final String copyStr = "DB$ CopyPermanent | Defined$ Self | Tapped$ True | Optional$ True | TokenAttacking$ Remembered"
final String copyStr = "DB$ CopyPermanent | Defined$ Self | TokenTapped$ True | Optional$ True | TokenAttacking$ Remembered"
+ " | ChoosePlayerOrPlaneswalker$ True | ImprintTokens$ True";
final String delTrigStr = "DB$ DelayedTrigger | Mode$ Phase | Phase$ EndCombat | RememberObjects$ Imprinted"
@@ -4693,8 +4685,10 @@ public class CardFactoryUtil {
}
altCostSA.setRestrictions(restriction);
final String costDescription = params.containsKey("Description") ? params.get("Description")
: TextUtil.concatWithSpace("You may", abCost.toStringAlt(),"rather than pay", TextUtil.addSuffix(card.getName(),"'s mana cost."));
String costDescription = TextUtil.fastReplace(params.get("Description"),"CARDNAME", card.getName());
if (costDescription.isEmpty()) {
costDescription = TextUtil.concatWithSpace("You may", abCost.toStringAlt(), "rather than pay", TextUtil.addSuffix(card.getName(), "'s mana cost."));
}
altCostSA.setDescription(costDescription);
if (params.containsKey("References")) {

View File

@@ -48,7 +48,7 @@ import io.sentry.event.BreadcrumbBuilder;
public class CardState extends GameObject {
private String name = "";
private CardType type = new CardType();
private CardType type = new CardType(false);
private ManaCost manaCost = ManaCost.NO_COST;
private byte color = MagicColor.COLORLESS;
private int basePower = 0;
@@ -116,6 +116,11 @@ public class CardState extends GameObject {
view.updateType(this);
}
}
public final void addType(Iterable<String> type0) {
if (type.addAll(type0)) {
view.updateType(this);
}
}
public final void setType(final CardType type0) {
if (type0 == type) {
// Logic below would incorrectly clear the type if it's the same object.

View File

@@ -327,7 +327,7 @@ public final class CardUtil {
}
public static CardState getFaceDownCharacteristic(Card c) {
final CardType type = new CardType();
final CardType type = new CardType(false);
type.add("Creature");
final CardState ret = new CardState(c, CardStateName.FaceDown);
@@ -337,7 +337,8 @@ public final class CardUtil {
ret.setName("");
ret.setType(type);
ret.setImageKey(ImageKeys.getTokenKey(ImageKeys.MORPH_IMAGE));
//show hidden if exiled facedown
ret.setImageKey(ImageKeys.getTokenKey(c.isInZone(ZoneType.Exile) ? ImageKeys.HIDDEN_CARD : ImageKeys.MORPH_IMAGE));
return ret;
}

View File

@@ -134,6 +134,18 @@ public class CardView extends GameEntityView {
return get(TrackableProperty.SplitCard);
}
public boolean isDoubleFacedCard() {
return get(TrackableProperty.DoubleFaced);
}
public boolean isAdventureCard() {
return get(TrackableProperty.Adventure);
}
public boolean isModalCard() {
return get(TrackableProperty.Modal);
}
/*
public boolean isTransformed() {
return getCurrentState().getState() == CardStateName.Transformed;
@@ -678,6 +690,12 @@ public class CardView extends GameEntityView {
public CardStateView getAlternateState() {
return get(TrackableProperty.AlternateState);
}
public boolean hasBackSide() {
return get(TrackableProperty.HasBackSide);
}
public String getBackSideName() { return get(TrackableProperty.BackSideName); }
CardStateView createAlternateState(final CardStateName state0) {
return new CardStateView(getId(), state0, tracker);
}
@@ -685,6 +703,10 @@ public class CardView extends GameEntityView {
public CardStateView getState(final boolean alternate0) {
return alternate0 ? getAlternateState() : getCurrentState();
}
void updateBackSide(String stateName, boolean hasBackSide) {
set(TrackableProperty.HasBackSide, hasBackSide);
set(TrackableProperty.BackSideName, stateName);
}
void updateState(Card c) {
updateName(c);
updateDamage(c);
@@ -694,6 +716,13 @@ public class CardView extends GameEntityView {
set(TrackableProperty.SplitCard, isSplitCard);
set(TrackableProperty.FlipCard, c.isFlipCard());
set(TrackableProperty.Facedown, c.isFaceDown());
set(TrackableProperty.Adventure, c.isAdventureCard());
set(TrackableProperty.DoubleFaced, c.isDoubleFaced());
set(TrackableProperty.Modal, c.isModal());
//backside
if (c.getAlternateState()!=null)
updateBackSide(c.getAlternateState().getName(), c.hasBackSide());
final Card cloner = c.getCloner();
@@ -733,6 +762,9 @@ public class CardView extends GameEntityView {
alternateState = c.getState(CardStateName.Original);
}
if (c.hasBackSide() && isFaceDown()) //fixes facedown cards with backside...
alternateState = c.getState(CardStateName.Original);
if (alternateState == null) {
set(TrackableProperty.AlternateState, null);
}

View File

@@ -706,7 +706,7 @@ public class Cost implements Serializable {
boolean first = true;
if (bFlag) {
cost.append("As an additional cost to cast CARDNAME, ");
cost.append("As an additional cost to cast this spell, ");
} else {
// usually no additional mana cost for spells
// only three Alliances cards have additional mana costs, but they
@@ -724,7 +724,11 @@ public class Cost implements Serializable {
if (!first) {
cost.append(" and ");
}
cost.append(part.toString());
if (bFlag) {
cost.append(StringUtils.uncapitalize(part.toString()));
} else {
cost.append(part.toString());
}
first = false;
}

View File

@@ -46,7 +46,7 @@ public enum Keyword {
DOUBLE_STRIKE("Double Strike", SimpleKeyword.class, true, "This creature deals both first-strike and regular combat damage."),
DREDGE("Dredge", KeywordWithAmount.class, false, "If you would draw a card, instead you may put exactly {%d:card} from the top of your library into your graveyard. If you do, return this card from your graveyard to your hand. Otherwise, draw a card."),
ECHO("Echo", KeywordWithCost.class, false, "At the beginning of your upkeep, if this permanent came under your control since the beginning of your last upkeep, sacrifice it unless you pay %s."),
EMBALM("Embalm", KeywordWithCost.class, false, "Create a token that's a copy of this card, except it's white, it has no mana cost, and it's a Zombie in addition to its other types. Embalm only as a sorcery."),
EMBALM("Embalm", KeywordWithCost.class, false, "%s, Exile this card from your graveyard: Create a token that's a copy of this card, except it's white, it has no mana cost, and it's a Zombie in addition to its other types. Embalm only as a sorcery."),
EMERGE("Emerge", KeywordWithCost.class, false, "You may cast this spell by sacrificing a creature and paying the emerge cost reduced by that creature's converted mana cost."),
ENCHANT("Enchant", KeywordWithType.class, false, "Target a %s as you cast this. This card enters the battlefield attached to that %s."),
ENTWINE("Entwine", KeywordWithCost.class, true, "You may choose all modes of this spell instead of just one. If you do, you pay an additional %s."),
@@ -54,7 +54,7 @@ public enum Keyword {
EQUIP("Equip", Equip.class, false, "%s: Attach to target %s you control. Equip only as a sorcery."),
ESCAPE("Escape", KeywordWithCost.class, false, "You may cast this card from your graveyard for its escape cost."),
ESCALATE("Escalate", KeywordWithCost.class, true, "Pay this cost for each mode chosen beyond the first."),
ETERNALIZE("Eternalize", KeywordWithCost.class, false, "Create a token that's a copy of this card, except it's black, it's 4/4, it has no mana cost, and it's a Zombie in addition to its other types. Eternalize only as a sorcery."),
ETERNALIZE("Eternalize", KeywordWithCost.class, false, "%s, Exile this card from your graveyard: Create a token that's a copy of this card, except it's black, it's 4/4, it has no mana cost, and it's a Zombie in addition to its other types. Eternalize only as a sorcery."),
EVOKE("Evoke", KeywordWithCost.class, false, "You may cast this spell for its evoke cost. If you do, it's sacrificed when it enters the battlefield."),
EVOLVE("Evolve", SimpleKeyword.class, false, "Whenever a creature enters the battlefield under your control, if that creature has greater power or toughness than this creature, put a +1/+1 counter on this creature."),
EXALTED("Exalted", SimpleKeyword.class, false, "Whenever a creature you control attacks alone, that creature gets +1/+1 until end of turn."),
@@ -109,7 +109,7 @@ public enum Keyword {
PERSIST("Persist", SimpleKeyword.class, false, "When this creature dies, if it had no -1/-1 counters on it, return it to the battlefield under its owner's control with a -1/-1 counter on it."),
PHASING("Phasing", SimpleKeyword.class, true, "This phases in or out before you untap during each of your untap steps. While it's phased out, it's treated as though it doesn't exist."),
POISONOUS("Poisonous", KeywordWithAmount.class, false, "Whenever this creature deals combat damage to a player, that player gets {%d:poison counter}."),
PRESENCE("Presence", KeywordWithType.class, false, "As an additional cost to cast CARDNAME, you may reveal a %s card from your hand."),
PRESENCE("Presence", KeywordWithType.class, false, "As an additional cost to cast this spell, you may reveal a %s card from your hand."),
PROTECTION("Protection", Protection.class, false, "This creature can't be blocked, targeted, dealt damage, or equipped/enchanted by %s."),
PROVOKE("Provoke", SimpleKeyword.class, false, "Whenever this creature attacks, you may have target creature defending player controls untap and block it if able."),
PROWESS("Prowess", SimpleKeyword.class, false, "Whenever you cast a noncreature spell, this creature gets +1/+1 until end of turn."),

View File

@@ -501,9 +501,7 @@ public class PhaseHandler implements java.io.Serializable {
bPreventCombatDamageThisTurn = false;
if (!bRepeatCleanup) {
// only call onCleanupPhase when Cleanup is not repeated
for (Player player : game.getPlayers()) {
player.onCleanupPhase();
}
game.onCleanupPhase();
setPlayerTurn(handleNextTurn());
// "Trigger" for begin turn to get around a phase skipping
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();

View File

@@ -114,8 +114,6 @@ public class Player extends GameEntity implements Comparable<Player> {
private CardCollection sacrificedThisTurn = new CardCollection();
private Map<CounterType, Integer> countersAddedtoPermThisTurn = Maps.newHashMap();
/** A list of tokens not in play, but on their way.
* This list is kept in order to not break ETB-replacement
* on tokens. */
@@ -2119,7 +2117,7 @@ public class Player extends GameEntity implements Comparable<Player> {
}
public final boolean hasProwl(final String type) {
if (prowl.contains("AllCreatureTypes")) {
if (prowl.contains(CardType.AllCreatureTypes)) {
return true;
}
return prowl.contains(type);
@@ -2289,20 +2287,6 @@ public class Player extends GameEntity implements Comparable<Player> {
sacrificedThisTurn.clear();
}
public final void addCounterToPermThisTurn(final CounterType type, final int x) {
countersAddedtoPermThisTurn.put(type, getCounterToPermThisTurn(type) + x);
}
public final Integer getCounterToPermThisTurn(final CounterType type) {
if (countersAddedtoPermThisTurn.containsKey(type))
return countersAddedtoPermThisTurn.get(type);
return 0;
}
public final void resetCounterToPermThisTurn() {
countersAddedtoPermThisTurn.clear();
}
public final int getSpellsCastThisTurn() {
return spellsCastThisTurn;
}
@@ -2521,7 +2505,6 @@ public class Player extends GameEntity implements Comparable<Player> {
resetSurveilThisTurn();
resetCycledThisTurn();
resetSacrificedThisTurn();
resetCounterToPermThisTurn();
clearAssignedDamage();
resetAttackersDeclaredThisTurn();
resetAttackedOpponentsThisTurn();

View File

@@ -213,7 +213,7 @@ public abstract class PlayerController {
public abstract boolean chooseFlipResult(SpellAbility sa, Player flipper, boolean[] results, boolean call);
public abstract Card chooseProtectionShield(GameEntity entityBeingDamaged, List<String> options, Map<String, Card> choiceMap);
public abstract List<AbilitySub> chooseModeForAbility(SpellAbility sa, int min, int num, boolean allowRepeat);
public abstract List<AbilitySub> chooseModeForAbility(SpellAbility sa, List<AbilitySub> possible, int min, int num, boolean allowRepeat);
public abstract byte chooseColor(String message, SpellAbility sa, ColorSet colors);
public abstract byte chooseColorAllowColorless(String message, Card c, ColorSet colors);

View File

@@ -17,6 +17,7 @@
*/
package forge.game.replacement;
import forge.game.GameEntity;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
@@ -47,6 +48,7 @@ public class ReplaceDamage extends ReplacementEffect {
*/
@Override
public boolean canReplace(Map<AbilityKey, Object> runParams) {
if (!(runParams.containsKey(AbilityKey.Prevention) == (hasParam("PreventionEffect") || hasParam("Prevent")))) {
return false;
}
@@ -117,10 +119,15 @@ public class ReplaceDamage extends ReplacementEffect {
return false;
}
// check for DamageRedirection, the Thing where the damage is redirected to must be a creature or planeswalker or a player
if (hasParam("DamageTarget")) {
//Lava Burst and Whippoorwill check
SpellAbility cause = (SpellAbility) runParams.get(AbilityKey.Cause);
GameEntity affected = (GameEntity) runParams.get(AbilityKey.Affected);
if (((cause != null) && (cause.hasParam("NoRedirection")) || (affected.hasKeyword("Damage that would be dealt to CARDNAME can't be redirected.")))) {
return false;
}
// check for DamageRedirection, the Thing where the damage is redirected to must be a creature or planeswalker or a player
String def = getParam("DamageTarget");
for (Player p : AbilityUtils.getDefinedPlayers(hostCard, def, null)) {
if (!p.getGame().getPlayers().contains(p)) {
return false;

View File

@@ -216,6 +216,12 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
if (desc.contains("CARDNAME")) {
desc = TextUtil.fastReplace(desc, "CARDNAME", getHostCard().toString());
}
if (desc.contains("EFFECTSOURCE")) {
desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", getHostCard().getEffectSource().toString());
}
if (desc.contains("NICKNAME")) {
desc = TextUtil.fastReplace(desc, "NICKNAME", getHostCard().toString().split(",")[0]);
}
return desc;
} else {
return "";

View File

@@ -699,6 +699,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
String desc = node.getDescription();
if (node.getHostCard() != null) {
desc = TextUtil.fastReplace(desc, "CARDNAME", node.getHostCard().getName());
desc = TextUtil.fastReplace(desc,"NICKNAME",node.getHostCard().getName().split(",")[0]);
if (node.getOriginalHost() != null) {
desc = TextUtil.fastReplace(desc, "ORIGINALHOST", node.getOriginalHost().getName());
}

View File

@@ -216,6 +216,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
if (hasParam("Description") && !this.isSuppressed()) {
String desc = getParam("Description");
desc = TextUtil.fastReplace(desc, "CARDNAME", this.hostCard.getName());
desc = TextUtil.fastReplace(desc, "NICKNAME", this.hostCard.getName().split(",")[0]);
return desc;
} else {

View File

@@ -111,11 +111,9 @@ public abstract class Trigger extends TriggerReplacementBase {
if (hasParam("TriggerDescription") && !this.isSuppressed()) {
StringBuilder sb = new StringBuilder();
String desc = getParam("TriggerDescription");
if(active)
desc = TextUtil.fastReplace(desc, "CARDNAME", getHostCard().toString());
else
desc = TextUtil.fastReplace(desc, "CARDNAME", getHostCard().getName());
String currentName = (getHostCard().getName());
String desc1 = TextUtil.fastReplace(getParam("TriggerDescription"),"CARDNAME", currentName);
String desc = TextUtil.fastReplace(desc1,"NICKNAME", currentName.split(",")[0]);
if (getHostCard().getEffectSource() != null) {
if(active)
desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", getHostCard().getEffectSource().toString());

View File

@@ -575,6 +575,8 @@ public class TriggerHandler {
sa.setStackDescription(sa.toString());
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
// need to be set for demonic pact to look for chosen modes
sa.setTrigger(regtrig);
if (!CharmEffect.makeChoices(sa)) {
// 603.3c If no mode is chosen, the ability is removed from the stack.
return;

View File

@@ -246,9 +246,9 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
}
}
if (sp.getApi() == ApiType.Charm && sp.hasParam("RememberChoice")) {
if (sp.getApi() == ApiType.Charm && sp.hasParam("ChoiceRestriction")) {
// Remember the Choice here for later handling
source.addRemembered(sp.getSubAbility());
source.addChosenModes(sp, sp.getSubAbility().getDescription());
}
//cancel auto-pass for all opponents of activating player

View File

@@ -25,6 +25,9 @@ public enum TrackableProperty {
Flipped(TrackableTypes.BooleanType),
Facedown(TrackableTypes.BooleanType),
Modal(TrackableTypes.BooleanType),
Adventure(TrackableTypes.BooleanType),
DoubleFaced(TrackableTypes.BooleanType),
//TODO?
Cloner(TrackableTypes.StringType),
@@ -164,6 +167,10 @@ public enum TrackableProperty {
CanPlay(TrackableTypes.BooleanType),
PromptIfOnlyPossibleAbility(TrackableTypes.BooleanType),
//HasBackSide
BackSideName(TrackableTypes.StringType),
HasBackSide(TrackableTypes.BooleanType),
//StackItem
Key(TrackableTypes.StringType),
SourceTrigger(TrackableTypes.IntegerType),

View File

@@ -1,5 +1,6 @@
package forge.app;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.PendingIntent;
@@ -56,9 +57,15 @@ public class Main extends AndroidApplication {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//get total device RAM in mb
ActivityManager actManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
actManager.getMemoryInfo(memInfo);
int totalMemory = Math.round(memInfo.totalMem / 1024f / 1024f);
boolean permissiongranted = checkPermission();
Gadapter = new AndroidAdapter(this.getContext());
initForge(Gadapter, permissiongranted);
initForge(Gadapter, permissiongranted, totalMemory);
//permission
if(!permissiongranted){
@@ -185,7 +192,7 @@ public class Main extends AndroidApplication {
builder.show();
}
private void initForge(AndroidAdapter adapter, boolean permissiongranted){
private void initForge(AndroidAdapter adapter, boolean permissiongranted, int totalRAM){
boolean isPortrait;
if (permissiongranted){
//establish assets directory
@@ -225,12 +232,12 @@ public class Main extends AndroidApplication {
Main.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
initialize(Forge.getApp(new AndroidClipboard(), adapter, assetsDir, propertyConfig, isPortrait));
initialize(Forge.getApp(new AndroidClipboard(), adapter, assetsDir, propertyConfig, isPortrait, totalRAM));
} else {
isPortrait = true;
//set current orientation
Main.this.setRequestedOrientation(Main.this.getResources().getConfiguration().orientation);
initialize(Forge.getApp(new AndroidClipboard(), adapter, "", false, isPortrait));
initialize(Forge.getApp(new AndroidClipboard(), adapter, "", false, isPortrait, totalRAM));
}
}

View File

@@ -7,6 +7,7 @@ import com.google.common.primitives.Ints;
import forge.gui.framework.ICDoc;
import forge.model.FModel;
import forge.quest.data.QuestPreferences;
import forge.quest.data.QuestPreferences.QPref;
import forge.screens.home.quest.VSubmenuQuestPrefs.PrefInput;
import forge.util.Localizer;
@@ -50,16 +51,29 @@ public enum CSubmenuQuestPrefs implements ICDoc {
public static void validateAndSave(final PrefInput i0) {
if (i0.getText().equals(i0.getPreviousText())) { return; }
final QuestPreferences prefs = FModel.getQuestPreferences();
final Integer val = Ints.tryParse(i0.getText());
resetErrors();
String validationError = null;
final Localizer localizer = Localizer.getInstance();
final String validationError = val == null ? localizer.getMessage("lblEnteraNumber") : prefs.validatePreference(i0.getQPref(), val.intValue());
resetErrors();
if(QPref.UNLOCK_DISTANCE_MULTIPLIER.equals(i0.getQPref())
|| QPref.WILD_OPPONENTS_MULTIPLIER.equals(i0.getQPref())) {
Double val = null;
try {
val = new Double(i0.getText());
} catch (Exception e) {
}
validationError = val == null ? localizer.getMessage("lblEnteraDecimal") : null;
} else {
final Integer val = Ints.tryParse(i0.getText());
validationError = val == null ? localizer.getMessage("lblEnteraNumber") : prefs.validatePreference(i0.getQPref(), val.intValue());
}
if (validationError != null) {
showError(i0, validationError);
return;
}
}
prefs.setPref(i0.getQPref(), i0.getText());
prefs.save();
i0.setPreviousText(i0.getText());

View File

@@ -292,6 +292,10 @@ public enum VSubmenuQuestPrefs implements IVSubmenu<CSubmenuQuestPrefs> {
pnlDifficulty.add(new PrefInput(QPref.PENALTY_LOSS, QuestPreferencesErrType.DIFFICULTY), fieldConstraints + ", wrap");
pnlDifficulty.add(new FLabel.Builder().text(localizer.getMessage("lblMoreDuelChoices")).fontAlign(SwingConstants.RIGHT).build(), labelConstraints);
pnlDifficulty.add(new PrefInput(QPref.MORE_DUEL_CHOICES, QuestPreferencesErrType.DIFFICULTY), fieldConstraints + ", wrap");
pnlDifficulty.add(new FLabel.Builder().text(localizer.getMessage("lblWildOpponentMultiplier")).fontAlign(SwingConstants.RIGHT).build(), labelConstraints);
pnlDifficulty.add(new PrefInput(QPref.WILD_OPPONENTS_MULTIPLIER, QuestPreferencesErrType.DIFFICULTY), fieldConstraints + ", wrap");
pnlDifficulty.add(new FLabel.Builder().text(localizer.getMessage("lblWildOpponentNumber")).fontAlign(SwingConstants.RIGHT).build(), labelConstraints);
pnlDifficulty.add(new PrefInput(QPref.WILD_OPPONENTS_NUMBER, QuestPreferencesErrType.DIFFICULTY), fieldConstraints + ", wrap");
}
private void populateBooster() {
pnlBooster.setOpaque(false);

View File

@@ -202,7 +202,12 @@ public class SimulateMatch {
System.out.println(l);
}
System.out.println(String.format("\nGame %d ended in %d ms. %s has won!\n", 1+iGame, sw.getTime(), g1.getOutcome().getWinningLobbyPlayer().getName()));
// If both players life totals to 0 in a single turn, the game should end in a draw
if(g1.getOutcome().isDraw()){
System.out.println(String.format("Game %d ended in a Draw! Took %d ms.", 1+iGame, sw.getTime()));
} else {
System.out.println(String.format("\nGame %d ended in %d ms. %s has won!\n", 1+iGame, sw.getTime(), g1.getOutcome().getWinningLobbyPlayer().getName()));
}
}
private static void simulateTournament(Map<String, List<String>> params, GameRules rules, boolean outputGamelog) {

View File

@@ -446,7 +446,7 @@ public class PlayerControllerForTests extends PlayerController {
}
@Override
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, int min, int num, boolean allowRepeat) {
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, List<AbilitySub> possible, int min, int num, boolean allowRepeat) {
throw new IllegalStateException("Erring on the side of caution here...");
}

View File

@@ -32,8 +32,8 @@ public class Main extends IOSApplication.Delegate {
config.useAccelerometer = false;
config.useCompass = false;
ForgePreferences prefs = FModel.getPreferences();
boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_NETPLAY_COMPAT);
final ApplicationListener app = Forge.getApp(new IOSClipboard(), new IOSAdapter(), assetsDir, propertyConfig, false);
boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_NETPLAY_COMPAT);//todo get totalRAM
final ApplicationListener app = Forge.getApp(new IOSClipboard(), new IOSAdapter(), assetsDir, propertyConfig, false, 0);
final IOSApplication iosApp = new IOSApplication(app, config);
return iosApp;
}

View File

@@ -96,8 +96,8 @@ public class Main {
ForgePreferences prefs = FModel.getPreferences();
boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_NETPLAY_COMPAT);
new LwjglApplication(Forge.getApp(new LwjglClipboard(), new DesktopAdapter(switchOrientationFile),
desktopMode ? desktopModeAssetsDir : assetsDir, propertyConfig, false), config);
new LwjglApplication(Forge.getApp(new LwjglClipboard(), new DesktopAdapter(switchOrientationFile),//todo get totalRAM
desktopMode ? desktopModeAssetsDir : assetsDir, propertyConfig, false, 0), config);
}
private static class DesktopAdapter implements IDeviceAdapter {

View File

@@ -27,8 +27,7 @@ public abstract class CachedCardImage implements ImageFetcher.Callback {
}
public void fetch() {
Texture image = ImageCache.getImage(key, false);
if (image == null) {
if (!ImageCache.imageKeyFileExists(key)) {
fetcher.fetchImage(key, this);
}
}

View File

@@ -37,7 +37,6 @@ import forge.util.Localizer;
import forge.util.Utils;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
@@ -71,14 +70,21 @@ public class Forge implements ApplicationListener {
public static boolean hdbuttons = false;
public static boolean hdstart = false;
public static boolean isPortraitMode = false;
public static boolean gameInProgress = false;
public static int cacheSize = 400;
public static int totalDeviceRAM = 0;
public static ApplicationListener getApp(Clipboard clipboard0, IDeviceAdapter deviceAdapter0, String assetDir0, boolean value, boolean androidOrientation) {
public static ApplicationListener getApp(Clipboard clipboard0, IDeviceAdapter deviceAdapter0, String assetDir0, boolean value, boolean androidOrientation, int totalRAM) {
if (GuiBase.getInterface() == null) {
clipboard = clipboard0;
deviceAdapter = deviceAdapter0;
GuiBase.setInterface(new GuiMobile(assetDir0));
GuiBase.enablePropertyConfig(value);
isPortraitMode = androidOrientation;
totalDeviceRAM = totalRAM;
//increase cacheSize for devices with RAM more than 5GB, default is 400. Some phones have more than 10GB RAM (Mi 10, OnePlus 8, S20, etc..)
if (totalDeviceRAM>5000) //devices with more than 10GB RAM will have 1000 Cache size, 700 Cache size for morethan 5GB RAM
cacheSize = totalDeviceRAM>10000 ? 1000: 700;
}
return app;
}
@@ -142,8 +148,18 @@ public class Forge implements ApplicationListener {
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblFinishingStartup"));
//add reminder to preload
if (enablePreloadExtendedArt)
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblPreloadExtendedArt"));
if (enablePreloadExtendedArt) {
if(totalDeviceRAM>0)
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblPreloadExtendedArt")+"\nDetected RAM: " +totalDeviceRAM+"MB. Cache size: "+cacheSize);
else
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblPreloadExtendedArt"));
} else {
if(totalDeviceRAM>0)
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblFinishingStartup")+"\nDetected RAM: " +totalDeviceRAM+"MB. Cache size: "+cacheSize);
else
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblFinishingStartup"));
}
Gdx.app.postRunnable(new Runnable() {
@Override
public void run() {
@@ -160,25 +176,17 @@ public class Forge implements ApplicationListener {
private void preloadExtendedArt() {
if (!enablePreloadExtendedArt)
return;
List<String> keys = new ArrayList<>();
File[] directories = new File(ForgeConstants.CACHE_CARD_PICS_DIR).listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
if (!file.getName().startsWith("MPS_"))
return false;
return file.isDirectory();
}
});
for (File folder : directories) {
File[] files = new File(folder.toString()).listFiles();
for (File file : files) {
if (file.isFile()) {
keys.add(folder.getName() + "/" +file.getName().replace(".jpg","").replace(".png",""));
}
}
List<String> borderlessCardlistkeys = FileUtil.readFile(ForgeConstants.BORDERLESS_CARD_LIST_FILE);
if(borderlessCardlistkeys.isEmpty())
return;
List<String> filteredkeys = new ArrayList<>();
for (String cardname : borderlessCardlistkeys){
File image = new File(ForgeConstants.CACHE_CARD_PICS_DIR+ForgeConstants.PATH_SEPARATOR+cardname+".jpg");
if (image.exists())
filteredkeys.add(cardname);
}
if (!keys.isEmpty())
ImageCache.preloadCache((Iterable<String>)keys);
if (!filteredkeys.isEmpty())
ImageCache.preloadCache(filteredkeys);
}
private void afterDbLoaded() {
@@ -402,11 +410,17 @@ public class Forge implements ApplicationListener {
}
private static void setCurrentScreen(FScreen screen0) {
String toNewScreen = screen0 != null ? screen0.toString() : "";
String previousScreen = currentScreen != null ? currentScreen.toString() : "";
gameInProgress = toNewScreen.toLowerCase().contains("match") || previousScreen.toLowerCase().contains("match");
boolean dispose = toNewScreen.toLowerCase().contains("homescreen");
try {
endKeyInput(); //end key input before switching screens
ForgeAnimation.endAll(); //end all active animations before switching screens
try {
ImageCache.disposeTexture();
if(dispose)
ImageCache.disposeTexture();
}
catch (Exception ex)
{

View File

@@ -566,17 +566,25 @@ public class Graphics {
}
public float getfloatAlphaComposite() { return alphaComposite; }
public void drawBorderImage(FImage image, Color color, float x, float y, float w, float h, boolean tint) {
image.draw(this, x, y, w, h);
public void drawBorderImage(FImage image, Color borderColor, Color tintColor, float x, float y, float w, float h, boolean tint) {
float oldalpha = alphaComposite;
if(tint){
float oldalpha = alphaComposite;
setAlphaComposite(0.8f);
drawRoundRect(2f, Color.WHITE, x, y, w, h, (h-w)/12);
setAlphaComposite(1f);
fillRoundRect(color, x, y, w, h, (h-w)/12);
setAlphaComposite(oldalpha);
drawRoundRect(2f, borderLining(borderColor.toString()), x, y, w, h, (h-w)/12);
fillRoundRect(tintColor, x, y, w, h, (h-w)/12);
} else {
image.draw(this, x, y, w, h);
fillRoundRect(borderColor, x, y, w, h, (h-w)/10);//show corners edges
}
setAlphaComposite(oldalpha);
}
public void drawborderImage(Color borderColor, float x, float y, float w, float h) {
float oldalpha = alphaComposite;
fillRoundRect(borderColor, x, y, w, h, (h-w)/12);
setAlphaComposite(oldalpha);
}
public void drawImage(FImage image, Color borderColor, float x, float y, float w, float h) {
image.draw(this, x, y, w, h);
fillRoundRect(borderColor, x+1, y+1, w-1.5f, h-1.5f, (h-w)/10);//used by zoom let some edges show...
}
public void drawImage(FImage image, float x, float y, float w, float h) {
drawImage(image, x, y, w, h, false);
@@ -733,4 +741,13 @@ public class Graphics {
public float adjustY(float y, float height) {
return regionHeight - y - bounds.y - height; //flip y-axis
}
public Color borderLining(String c){
if (c == null || c == "")
return Color.valueOf("#fffffd");
int c_r = Integer.parseInt(c.substring(0,2),16);
int c_g = Integer.parseInt(c.substring(2,4),16);
int c_b = Integer.parseInt(c.substring(4,6),16);
int brightness = ((c_r * 299) + (c_g * 587) + (c_b * 114)) / 1000;
return brightness > 155 ? Color.valueOf("#171717") : Color.valueOf("#fffffd");
}
}

View File

@@ -27,10 +27,10 @@ import forge.screens.SplashScreen;
import forge.toolbox.FProgressBar;
public class FSkin {
private static final Map<FSkinProp, FSkinImage> images = new HashMap<>();
private static final Map<Integer, TextureRegion> avatars = new HashMap<>();
private static final Map<Integer, TextureRegion> sleeves = new HashMap<>();
private static final Map<Integer, TextureRegion> borders = new HashMap<>();
private static final Map<FSkinProp, FSkinImage> images = new HashMap<>(512);
private static final Map<Integer, TextureRegion> avatars = new HashMap<>(150);
private static final Map<Integer, TextureRegion> sleeves = new HashMap<>(64);
private static final Map<Integer, TextureRegion> borders = new HashMap<>(2);
private static Array<String> allSkins;
private static FileHandle preferredDir;
@@ -275,6 +275,20 @@ public class FSkin {
}
}
pxPreferredAvatars.dispose();
} else if (!FSkin.preferredName.isEmpty()){
//workaround bug crash fix if missing sprite avatar on preferred theme for quest tournament...
//i really don't know why it needs to populate the avatars twice.... needs investigation
final int pw = pxDefaultAvatars.getWidth();
final int ph = pxDefaultAvatars.getHeight();
for (int j = 0; j < ph; j += 100) {
for (int i = 0; i < pw; i += 100) {
if (i == 0 && j == 0) { continue; }
pxTest = new Color(pxDefaultAvatars.getPixel(i + 50, j + 50));
if (pxTest.a == 0) { continue; }
FSkin.avatars.put(counter++, new TextureRegion(txDefaultAvatars, i, j, 100, 100));
}
}
}
final int aw = pxDefaultAvatars.getWidth();

View File

@@ -27,23 +27,27 @@ import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalCause;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import forge.Forge;
import forge.ImageKeys;
import forge.card.CardEdition;
import forge.card.CardRenderer;
import forge.deck.Deck;
import forge.game.card.CardView;
import forge.game.player.IHasIcon;
import forge.item.IPaperCard;
import forge.item.InventoryItem;
import forge.item.PaperCard;
import forge.model.FModel;
import forge.properties.ForgeConstants;
import forge.util.ImageUtil;
import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
@@ -65,7 +69,7 @@ public class ImageCache {
private static final Set<String> missingIconKeys = new HashSet<>();
private static final LoadingCache<String, Texture> cache = CacheBuilder.newBuilder()
.maximumSize(400)
.maximumSize(Forge.cacheSize)
.expireAfterAccess(15, TimeUnit.MINUTES)
.removalListener(new RemovalListener<String, Texture>() {
@Override
@@ -73,13 +77,14 @@ public class ImageCache {
if(removalNotification.wasEvicted()||removalNotification.getCause() == RemovalCause.EXPIRED) {
removalNotification.getValue().dispose();
}
CardRenderer.clearcardArtCache();
}
})
.build(new ImageLoader());
public static final Texture defaultImage;
public static FImage BlackBorder = FSkinImage.IMG_BORDER_BLACK;
public static FImage WhiteBorder = FSkinImage.IMG_BORDER_WHITE;
private static final Map<Texture, Boolean> Borders = new HashMap<>();
private static final Map<String, Pair<String, Boolean>> imageBorder = new HashMap<>(1024);
private static boolean imageLoaded, delayLoadRequested;
public static void allowSingleLoad() {
@@ -102,13 +107,13 @@ public class ImageCache {
cache.invalidateAll();
cache.cleanUp();
missingIconKeys.clear();
Borders.clear();
}
public static void disposeTexture(){
for (Texture t: cache.asMap().values()) {
if (!t.toString().contains("pics/icons")) //fixes quest avatars black texture. todo: filter textures that are safe to dispose...
t.dispose();
if(!t.toString().contains("@")) //generated texture don't need to be disposed manually
t.dispose();
}
CardRenderer.clearcardArtCache();
clear();
@@ -132,6 +137,39 @@ public class ImageCache {
return new FTextureImage(icon);
}
/**
* checks the card image exists from the disk.
*/
public static boolean imageKeyFileExists(String imageKey) {
if (StringUtils.isEmpty(imageKey))
return false;
if (imageKey.length() < 2)
return false;
final String prefix = imageKey.substring(0, 2);
if (prefix.equals(ImageKeys.CARD_PREFIX)) {
PaperCard paperCard = ImageUtil.getPaperCardFromImageKey(imageKey);
if (paperCard == null)
return false;
final boolean backFace = imageKey.endsWith(ImageKeys.BACKFACE_POSTFIX);
final String cardfilename = ImageUtil.getImageKey(paperCard, backFace, true);
if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + cardfilename + ".jpg").exists())
if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + cardfilename + ".png").exists())
if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + TextUtil.fastReplace(cardfilename,".full", ".fullborder") + ".jpg").exists())
return false;
} else if (prefix.equals(ImageKeys.TOKEN_PREFIX)) {
final String tokenfilename = imageKey.substring(2) + ".jpg";
if (!new File(ForgeConstants.CACHE_TOKEN_PICS_DIR, tokenfilename).exists())
return false;
}
return true;
}
/**
* This requests the original unscaled image from the cache for the given key.
* If the image does not exist then it can return a default image if desired.
@@ -185,19 +223,27 @@ public class ImageCache {
if (useDefaultIfNotFound) {
image = defaultImage;
cache.put(imageKey, defaultImage);
if (Borders.get(image) == null)
Borders.put(image, false); //black border
if (imageBorder.get(image.toString()) == null)
imageBorder.put(image.toString(), Pair.of(Color.valueOf("#171717").toString(), false)); //black border
}
}
return image;
}
public static void preloadCache(Iterable<String> keys) {
try {
cache.getAll(keys);
} catch (ExecutionException e) {
e.printStackTrace();
for (String imageKey : keys){
if(getImage(imageKey, false) == null)
System.err.println("could not load card image:"+imageKey);
}
}
public static void preloadCache(Deck deck) {
if(deck == null||!Forge.enablePreloadExtendedArt)
return;
for (PaperCard p : deck.getAllCardsInASinglePool().toFlatList()) {
if (getImage(p.getImageKey(false),false) == null)
System.err.println("could not load card image:"+p.toString());
}
}
public static TextureRegion croppedBorderImage(Texture image) {
if (!image.toString().contains(".fullborder."))
return new TextureRegion(image);
@@ -208,24 +254,10 @@ public class ImageCache {
int ry = Math.round((image.getHeight() - rh)/2f)-2;
return new TextureRegion(image, rx, ry, rw, rh);
}
public static Color borderColor(IPaperCard c) {
if (c == null)
public static Color borderColor(Texture t) {
if (t == null)
return Color.valueOf("#171717");
CardEdition ed = FModel.getMagicDb().getEditions().get(c.getEdition());
if (ed != null && ed.isWhiteBorder())
return Color.valueOf("#fffffd");
return Color.valueOf("#171717");
}
public static Color borderColor(CardView c) {
if (c == null)
return Color.valueOf("#171717");
CardView.CardStateView state = c.getCurrentState();
CardEdition ed = FModel.getMagicDb().getEditions().get(state.getSetCode());
if (ed != null && ed.isWhiteBorder() && state.getFoilIndex() == 0)
return Color.valueOf("#fffffd");
return Color.valueOf("#171717");
return Color.valueOf(imageBorder.get(t.toString()).getLeft());
}
public static int getFSkinBorders(CardView c) {
if (c == null)
@@ -240,21 +272,21 @@ public class ImageCache {
public static boolean isBorderlessCardArt(Texture t) {
return ImageLoader.isBorderless(t);
}
public static void updateBorders(Texture t, boolean val){
Borders.put(t, val);
public static void updateBorders(String textureString, Pair<String, Boolean> colorPair){
imageBorder.put(textureString, colorPair);
}
public static FImage getBorder(Texture t) {
if (Borders.get(t) == null)
public static FImage getBorder(String textureString) {
if (imageBorder.get(textureString) == null)
return BlackBorder;
return Borders.get(t) ? WhiteBorder : BlackBorder;
return imageBorder.get(textureString).getRight() ? WhiteBorder : BlackBorder;
}
public static FImage getBorderImage(Texture t, boolean canshow) {
public static FImage getBorderImage(String textureString, boolean canshow) {
if (!canshow)
return BlackBorder;
return getBorder(t);
return getBorder(textureString);
}
public static FImage getBorderImage(Texture t) {
return getBorder(t);
public static FImage getBorderImage(String textureString) {
return getBorder(textureString);
}
public static Color getTint(CardView c) {
if (c == null)

View File

@@ -1,7 +1,7 @@
package forge.assets;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
@@ -14,142 +14,15 @@ import forge.FThreads;
import forge.Forge;
import forge.ImageKeys;
import forge.properties.ForgeConstants;
import forge.util.FileUtil;
import forge.util.TextUtil;
import org.apache.commons.lang3.tuple.Pair;
import static forge.assets.ImageCache.croppedBorderImage;
final class ImageLoader extends CacheLoader<String, Texture> {
private static ArrayList<String> borderlessCardlistKey = new ArrayList<String>() {
{//TODO: load the values from text list instead of hardcoded
add("2XM/Academy Ruins2.fullborder");
add("2XM/Atraxa, Praetors' Voice2.fullborder");
add("2XM/Avacyn, Angel of Hope2.fullborder");
add("2XM/Batterskull2.fullborder");
add("2XM/Blightsteel Colossus2.fullborder");
add("2XM/Blood Moon2.fullborder");
add("2XM/Brainstorm2.fullborder");
add("2XM/Chrome Mox2.fullborder");
add("2XM/Council's Judgment2.fullborder");
add("2XM/Crop Rotation2.fullborder");
add("2XM/Cyclonic Rift2.fullborder");
add("2XM/Dark Confidant2.fullborder");
add("2XM/Doubling Season2.fullborder");
add("2XM/Expedition Map2.fullborder");
add("2XM/Exploration2.fullborder");
add("2XM/Fatal Push2.fullborder");
add("2XM/Force of Will2.fullborder");
add("2XM/Goblin Guide2.fullborder");
add("2XM/Jace, the Mind Sculptor2.fullborder");
add("2XM/Kaalia of the Vast2.fullborder");
add("2XM/Karn Liberated2.fullborder");
add("2XM/Lightning Greaves2.fullborder");
add("2XM/Mana Crypt2.fullborder");
add("2XM/Meddling Mage2.fullborder");
add("2XM/Mox Opal2.fullborder");
add("2XM/Noble Hierarch2.fullborder");
add("2XM/Phyrexian Metamorph2.fullborder");
add("2XM/Sneak Attack2.fullborder");
add("2XM/Stoneforge Mystic2.fullborder");
add("2XM/Sword of Body and Mind2.fullborder");
add("2XM/Sword of Feast and Famine2.fullborder");
add("2XM/Sword of Fire and Ice2.fullborder");
add("2XM/Sword of Light and Shadow2.fullborder");
add("2XM/Sword of War and Peace2.fullborder");
add("2XM/Thoughtseize2.fullborder");
add("2XM/Toxic Deluge2.fullborder");
add("2XM/Urza's Mine2.fullborder");
add("2XM/Urza's Power Plant2.fullborder");
add("2XM/Urza's Tower2.fullborder");
add("2XM/Wurmcoil Engine2.fullborder");
add("ELD/Garruk, Cursed Huntsman2.fullborder");
add("ELD/Oko, Thief of Crowns2.fullborder");
add("ELD/The Royal Scions2.fullborder");
add("IKO/Brokkos, Apex of Forever2.fullborder");
add("IKO/Brokkos, Apex of Forever3.fullborder");
add("IKO/Crystalline Giant3.fullborder");
add("IKO/Cubwarden2.fullborder");
add("IKO/Dirge Bat2.fullborder");
add("IKO/Dirge Bat3.fullborder");
add("IKO/Everquill Phoenix2.fullborder");
add("IKO/Everquill Phoenix3.fullborder");
add("IKO/Gemrazer2.fullborder");
add("IKO/Gemrazer3.fullborder");
add("IKO/Gyruda, Doom of Depths3.fullborder");
add("IKO/Huntmaster Liger2.fullborder");
add("IKO/Huntmaster Liger3.fullborder");
add("IKO/Illuna, Apex of Wishes2.fullborder");
add("IKO/Illuna, Apex of Wishes3.fullborder");
add("IKO/Indatha Triome2.fullborder");
add("IKO/Ketria Triome2.fullborder");
add("IKO/Lukka, Coppercoat Outcast2.fullborder");
add("IKO/Luminous Broodmoth3.fullborder");
add("IKO/Mysterious Egg2.fullborder");
add("IKO/Narset of the Ancient Way2.fullborder");
add("IKO/Nethroi, Apex of Death2.fullborder");
add("IKO/Nethroi, Apex of Death3.fullborder");
add("IKO/Pollywog Symbiote2.fullborder");
add("IKO/Raugrin Triome2.fullborder");
add("IKO/Savai Triome2.fullborder");
add("IKO/Sea-Dasher Octopus2.fullborder");
add("IKO/Snapdax, Apex of the Hunt2.fullborder");
add("IKO/Snapdax, Apex of the Hunt3.fullborder");
add("IKO/Sprite Dragon3.fullborder");
add("IKO/Titanoth Rex2.fullborder");
add("IKO/Vadrok, Apex of Thunder2.fullborder");
add("IKO/Vadrok, Apex of Thunder3.fullborder");
add("IKO/Vivien, Monsters' Advocate2.fullborder");
add("IKO/Void Beckoner2.fullborder");
add("IKO/Yidaro, Wandering Monster3.fullborder");
add("IKO/Zagoth Triome2.fullborder");
add("IKO/Zilortha, Strength Incarnate.fullborder");
add("M21/Basri Ket2.fullborder");
add("M21/Chandra, Heart of Fire2.fullborder");
add("M21/Containment Priest2.fullborder");
add("M21/Cultivate2.fullborder");
add("M21/Garruk, Unleashed2.fullborder");
add("M21/Grim Tutor2.fullborder");
add("M21/Liliana, Waker of the Dead2.fullborder");
add("M21/Massacre Wurm2.fullborder");
add("M21/Scavenging Ooze2.fullborder");
add("M21/Solemn Simulacrum2.fullborder");
add("M21/Teferi, Master of Time2.fullborder");
add("M21/Ugin, the Spirit Dragon2.fullborder");
add("M21/Ugin, the Spirit Dragon3.fullborder");
add("PLGS/Hangarback Walker.fullborder");
add("SLD/Acidic Slime.fullborder");
add("SLD/Captain Sisay.fullborder");
add("SLD/Meren of Clan Nel Toth.fullborder");
add("SLD/Narset, Enlightened Master.fullborder");
add("SLD/Necrotic Ooze.fullborder");
add("SLD/Oona, Queen of the Fae.fullborder");
add("SLD/Saskia the Unyielding.fullborder");
add("SLD/The Mimeoplasm.fullborder");
add("SLD/Voidslime.fullborder");
add("THB/Ashiok, Nightmare Muse2.fullborder");
add("THB/Calix, Destiny's Hand2.fullborder");
add("THB/Elspeth, Sun's Nemesis2.fullborder");
add("UST/Forest.fullborder");
add("UST/Island.fullborder");
add("UST/Mountain.fullborder");
add("UST/Plains.fullborder");
add("UST/Swamp.fullborder");
add("ZNR/Boulderloft Pathway2.fullborder");
add("ZNR/Branchloft Pathway2.fullborder");
add("ZNR/Brightclimb Pathway2.fullborder");
add("ZNR/Clearwater Pathway2.fullborder");
add("ZNR/Cragcrown Pathway2.fullborder");
add("ZNR/Grimclimb Pathway2.fullborder");
add("ZNR/Jace, Mirror Mage2.fullborder");
add("ZNR/Lavaglide Pathway2.fullborder");
add("ZNR/Murkwater Pathway2.fullborder");
add("ZNR/Nahiri, Heir of the Ancients2.fullborder");
add("ZNR/Needleverge Pathway2.fullborder");
add("ZNR/Nissa of Shadowed Boughs2.fullborder");
add("ZNR/Pillarverge Pathway2.fullborder");
add("ZNR/Riverglide Pathway2.fullborder");
add("ZNR/Timbercrown Pathway2.fullborder");
}
};
private static List<String> borderlessCardlistKey = FileUtil.readFile(ForgeConstants.BORDERLESS_CARD_LIST_FILE);
Texture n;
@Override
@@ -162,7 +35,7 @@ final class ImageLoader extends CacheLoader<String, Texture> {
try {
Texture t = new Texture(fh, textureFilter);
//update
ImageCache.updateBorders(t, extendedArt ? false: isCloserToWhite(getpixelColor(t)));
ImageCache.updateBorders(t.toString(), extendedArt ? Pair.of(Color.valueOf("#171717").toString(), false): isCloserToWhite(getpixelColor(t)));
if (textureFilter)
t.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
if (extendedArt)
@@ -240,6 +113,8 @@ final class ImageLoader extends CacheLoader<String, Texture> {
}
public boolean isBorderless(String imagekey) {
if(borderlessCardlistKey.isEmpty())
return false;
if (imagekey.length() > 7) {
if ((!imagekey.substring(0, 7).contains("MPS_KLD"))&&(imagekey.substring(0, 4).contains("MPS_"))) //MPS_ sets except MPD_KLD
return true;
@@ -248,6 +123,8 @@ final class ImageLoader extends CacheLoader<String, Texture> {
}
public static boolean isBorderless(Texture t) {
if(borderlessCardlistKey.isEmpty())
return false;
//generated texture/pixmap?
if (t.toString().contains("com.badlogic.gdx.graphics.Texture@"))
return true;
@@ -269,13 +146,13 @@ final class ImageLoader extends CacheLoader<String, Texture> {
pixmap.dispose();
return color.toString();
}
public static boolean isCloserToWhite(String c){
public static Pair<String, Boolean> isCloserToWhite(String c){
if (c == null || c == "")
return false;
return Pair.of(Color.valueOf("#171717").toString(), false);
int c_r = Integer.parseInt(c.substring(0,2),16);
int c_g = Integer.parseInt(c.substring(2,4),16);
int c_b = Integer.parseInt(c.substring(4,6),16);
int brightness = ((c_r * 299) + (c_g * 587) + (c_b * 114)) / 1000;
return brightness > 155;
return Pair.of(c,brightness > 155);
}
}

View File

@@ -16,7 +16,7 @@ import forge.util.TextBounds;
//Encodes text for drawing with symbols and reminder text
public class TextRenderer {
private static final Map<String, FSkinImage> symbolLookup = new HashMap<>();
private static final Map<String, FSkinImage> symbolLookup = new HashMap<>(64);
static {
symbolLookup.put("C", FSkinImage.MANA_COLORLESS);
symbolLookup.put("W", FSkinImage.MANA_W);

View File

@@ -29,7 +29,7 @@ import java.util.StringTokenizer;
public class CardFaceSymbols {
public static final float FONT_SIZE_FACTOR = 0.85f;
private static final Map<String, FSkinImage> MANA_IMAGES = new HashMap<>();
private static final Map<String, FSkinImage> MANA_IMAGES = new HashMap<>(128);
public static void loadImages() {
for (int i = 0; i <= 20; i++) {

View File

@@ -53,7 +53,7 @@ public class CardImage implements FImage {
g.drawImage(image, x, y, w, h);
else {
float radius = (h - w)/8;
g.drawfillBorder(3, ImageCache.borderColor(card), x, y, w, h, radius);
g.drawborderImage(ImageCache.borderColor(image), x, y, w, h);
g.drawImage(ImageCache.croppedBorderImage(image), x+radius/2.2f, y+radius/2, w*0.96f, h*0.96f);
}
}

View File

@@ -73,9 +73,12 @@ public class CardImageRenderer {
prevImageHeight = h;
}
public static void drawFaceDownCard(Graphics g, float x, float y, float w, float h) {
// TODO: improve the way a face-down card back is represented so it doesn't look as ugly
drawArt(g, x, y, w, h);
public static void drawFaceDownCard(CardView card, Graphics g, float x, float y, float w, float h) {
//try to draw the card sleeves first
if (FSkin.getSleeves().get(card.getOwner()) != null)
g.drawImage(FSkin.getSleeves().get(card.getOwner()), x, y, w, h);
else
drawArt(g, x, y, w, h);
}
public static void drawCardImage(Graphics g, CardView card, boolean altState, float x, float y, float w, float h, CardStackPosition pos) {
@@ -92,7 +95,7 @@ public class CardImageRenderer {
final boolean canShow = MatchController.instance.mayView(card);
if (!canShow) {
drawFaceDownCard(g, x, y, w, h);
drawFaceDownCard(card, g, x, y, w, h);
return;
}
@@ -333,7 +336,17 @@ public class CardImageRenderer {
public static void drawZoom(Graphics g, CardView card, GameView gameView, boolean altState, float x, float y, float w, float h, float dispW, float dispH, boolean isCurrentCard) {
boolean canshow = MatchController.instance.mayView(card);
final Texture image = ImageCache.getImage(card.getState(altState).getImageKey(), true);
Texture image = null;
try {
image = ImageCache.getImage(card.getState(altState).getImageKey(), true);
} catch (Exception ex) {
//System.err.println(card.toString()+" : " +ex.getMessage());
//TODO: don't know why this is needed, needs further investigation...
if (!card.hasAlternateState()) {
altState = false;
image = ImageCache.getImage(card.getState(altState).getImageKey(), true);
}
}
FImage sleeves = MatchController.getPlayerSleeve(card.getOwner());
if (image == null) { //draw details if can't draw zoom
drawDetails(g, card, gameView, altState, x, y, w, h);
@@ -390,7 +403,7 @@ public class CardImageRenderer {
if (ImageCache.isBorderlessCardArt(image))
g.drawImage(image, x, y, w, h);
else {
g.drawImage(ImageCache.getBorderImage(image, canshow), x, y, w, h);
g.drawImage(ImageCache.getBorderImage(image.toString()), ImageCache.borderColor(image), x, y, w, h);
g.drawImage(ImageCache.croppedBorderImage(image), x + radius / 2.4f-minusxy, y + radius / 2-minusxy, w * croppedArea, h * croppedArea);
}
} else {

View File

@@ -55,6 +55,7 @@ import forge.util.TextBounds;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -192,7 +193,7 @@ public class CardRenderer {
return Math.round(MANA_SYMBOL_SIZE + FSkinFont.get(12).getLineHeight() + 3 * FList.PADDING + 1);
}
private static final Map<String, FImageComplex> cardArtCache = new HashMap<>();
private static final Map<String, FImageComplex> cardArtCache = new HashMap<>(1024);
public static final float CARD_ART_RATIO = 1.302f;
public static final float CARD_ART_HEIGHT_PERCENTAGE = 0.43f;
@@ -463,7 +464,7 @@ public class CardRenderer {
if (ImageCache.isBorderlessCardArt(image))
g.drawImage(image, x, y, w, h);
else {
g.drawImage(ImageCache.getBorderImage(image), x, y, w, h);
g.drawImage(ImageCache.getBorderImage(image.toString()), x, y, w, h);
g.drawImage(ImageCache.croppedBorderImage(image), x + radius / 2.4f-minusxy, y + radius / 2-minusxy, w * croppedArea, h * croppedArea);
}
} else
@@ -484,8 +485,12 @@ public class CardRenderer {
}
}
public static void drawCard(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos, boolean rotate) {
drawCard(g, card, x, y, w, h, pos, rotate, false);
}
public static void drawCard(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos, boolean rotate, boolean showAltState) {
boolean canshow = MatchController.instance.mayView(card);
Texture image = new RendererCachedCardImage(card, false).getImage(card.getCurrentState().getImageKey());
boolean showsleeves = card.isFaceDown() && card.isInZone(EnumSet.of(ZoneType.Exile)); //fix facedown card image ie gonti lord of luxury
Texture image = new RendererCachedCardImage(card, false).getImage( showAltState ? card.getAlternateState().getImageKey() : card.getCurrentState().getImageKey());
FImage sleeves = MatchController.getPlayerSleeve(card.getOwner());
float radius = (h - w)/8;
float croppedArea = isModernFrame(card) ? CROP_MULTIPLIER : 0.97f;
@@ -497,6 +502,8 @@ public class CardRenderer {
if (image != null) {
if (image == ImageCache.defaultImage) {
CardImageRenderer.drawCardImage(g, card, false, x, y, w, h, pos);
} else if (showsleeves) {
g.drawImage(sleeves, x, y, w, h);
} else {
if(FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ROTATE_PLANE_OR_PHENOMENON)
&& (card.getCurrentState().isPhenomenon() || card.getCurrentState().isPlane()) && rotate){
@@ -515,7 +522,7 @@ public class CardRenderer {
g.drawImage(image, x, y, w, h);
else {
boolean t = (card.getCurrentState().getOriginalColors() != card.getCurrentState().getColors()) || card.getCurrentState().hasChangeColors();
g.drawBorderImage(ImageCache.getBorderImage(image, canshow), ImageCache.getTint(card), x, y, w, h, t); //tint check for changed colors
g.drawBorderImage(ImageCache.getBorderImage(image.toString(), canshow), ImageCache.borderColor(image), ImageCache.getTint(card), x, y, w, h, t); //tint check for changed colors
g.drawImage(ImageCache.croppedBorderImage(image), x + radius / 2.4f-minusxy, y + radius / 2-minusxy, w * croppedArea, h * croppedArea);
}
} else {
@@ -536,15 +543,15 @@ public class CardRenderer {
}
public static void drawCardWithOverlays(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos) {
drawCardWithOverlays(g, card, x, y, w, h, pos, false);
drawCardWithOverlays(g, card, x, y, w, h, pos, false, false);
}
public static void drawCardWithOverlays(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos, boolean stackview) {
public static void drawCardWithOverlays(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos, boolean stackview, boolean showAltState) {
boolean canShow = MatchController.instance.mayView(card);
float oldAlpha = g.getfloatAlphaComposite();
boolean unselectable = !MatchController.instance.isSelectable(card) && MatchController.instance.isSelecting();
float cx, cy, cw, ch;
cx = x; cy = y; cw = w; ch = h;
drawCard(g, card, x, y, w, h, pos, false);
drawCard(g, card, x, y, w, h, pos, false, showAltState);
float padding = w * PADDING_MULTIPLIER; //adjust for card border
x += padding;
@@ -553,7 +560,7 @@ public class CardRenderer {
h -= 2 * padding;
// TODO: A hacky workaround is currently used to make the game not leak the color information for Morph cards.
final CardStateView details = card.getCurrentState();
final CardStateView details = showAltState ? card.getAlternateState() : card.getCurrentState();
final boolean isFaceDown = card.isFaceDown();
final DetailColors borderColor = isFaceDown ? CardDetailUtil.DetailColors.FACE_DOWN : CardDetailUtil.getBorderColor(details, canShow); // canShow doesn't work here for face down Morphs
Color color = FSkinColor.fromRGB(borderColor.r, borderColor.g, borderColor.b);
@@ -930,7 +937,7 @@ public class CardRenderer {
}
}
else {
drawManaCost(g, card.getCurrentState().getManaCost(), x - padding, y, w + 2 * padding, h, manaSymbolSize);
drawManaCost(g, showAltState ? card.getAlternateState().getManaCost() : card.getCurrentState().getManaCost(), x - padding, y, w + 2 * padding, h, manaSymbolSize);
}
}
}

View File

@@ -46,10 +46,15 @@ public class CardZoom extends FOverlay {
private static String currentActivateAction;
private static Rectangle flipIconBounds;
private static boolean showAltState;
private static boolean showBackSide = false;
public static void show(Object item) {
show(item, false);
}
public static void show(Object item, boolean showbackside) {
List<Object> items0 = new ArrayList<>();
items0.add(item);
showBackSide = showbackside; //reverse the displayed zoomed card for the choice list
show(items0, 0, null);
}
public static void show(FCollectionView<?> items0, int currentIndex0, ActivateHandler activateHandler0) {
@@ -158,10 +163,15 @@ public class CardZoom extends FOverlay {
@Override
public boolean tap(float x, float y, int count) {
if (flipIconBounds != null && flipIconBounds.contains(x, y)) {
showAltState = !showAltState;
if (!showBackSide)
showAltState = !showAltState;
else
showBackSide = !showBackSide;
return true;
}
hide();
showBackSide = false;
showAltState = false;
return true;
}
@@ -169,14 +179,20 @@ public class CardZoom extends FOverlay {
public boolean fling(float velocityX, float velocityY) {
if (Math.abs(velocityX) > Math.abs(velocityY)) {
incrementCard(velocityX > 0 ? -1 : 1);
showBackSide = false;
showAltState = false;
return true;
}
if (velocityY > 0) {
zoomMode = !zoomMode;
showBackSide = false;
showAltState = false;
return true;
}
if (currentActivateAction != null && activateHandler != null) {
hide();
showBackSide = false;
showAltState = false;
activateHandler.activate(currentIndex);
return true;
}
@@ -282,10 +298,9 @@ public class CardZoom extends FOverlay {
float x = (w - cardWidth) / 2;
y = (h - cardHeight) / 2;
if (zoomMode) {
CardImageRenderer.drawZoom(g, currentCard, gameView, showAltState, x, y, cardWidth, cardHeight, getWidth(), getHeight(), true);
}
else {
CardImageRenderer.drawDetails(g, currentCard, gameView, showAltState, x, y, cardWidth, cardHeight);
CardImageRenderer.drawZoom(g, currentCard, gameView, showBackSide? showBackSide : showAltState, x, y, cardWidth, cardHeight, getWidth(), getHeight(), true);
} else {
CardImageRenderer.drawDetails(g, currentCard, gameView, showBackSide? showBackSide : showAltState, x, y, cardWidth, cardHeight);
}
if (flipIconBounds != null) {
@@ -307,6 +322,8 @@ public class CardZoom extends FOverlay {
}
g.fillRect(FDialog.MSG_BACK_COLOR, 0, h - messageHeight, w, messageHeight);
g.drawText(zoomMode ? Localizer.getInstance().getMessage("lblSwipeDownDetailView") : Localizer.getInstance().getMessage("lblSwipeDownPictureView"), FDialog.MSG_FONT, FDialog.MSG_FORE_COLOR, 0, h - messageHeight, w, messageHeight, false, Align.center, true);
interrupt(false);
}
@Override
@@ -318,4 +335,15 @@ public class CardZoom extends FOverlay {
void setSelectedIndex(int index);
void activate(int index);
}
public void interrupt(boolean resume) {
if (MatchController.instance.hasLocalPlayers())
return;
if(resume && MatchController.instance.isGamePaused()) {
MatchController.instance.resumeMatch();
return;
}
if(!MatchController.instance.isGamePaused())
MatchController.instance.pauseMatch();
}
}

View File

@@ -3,6 +3,7 @@ package forge.deck;
import forge.FThreads;
import forge.Forge;
import forge.GuiBase;
import forge.assets.ImageCache;
import forge.deck.FDeckEditor.EditorType;
import forge.deck.io.DeckPreferences;
import forge.error.BugReporter;
@@ -472,6 +473,8 @@ public class FDeckChooser extends FScreen {
break;
}
needRefreshOnActivate = true;
/*preload deck to cache*/
ImageCache.preloadCache(deck.getDeck());
Forge.openScreen(new FDeckEditor(editorType, deck, true));
}
@@ -1147,7 +1150,7 @@ public class FDeckChooser extends FScreen {
});
}
});
chooser.show(null, true);
chooser.show(null, false); /*setting selectMax to true will select all available option*/
}
});
}

View File

@@ -5,6 +5,7 @@ import forge.assets.FImage;
import forge.assets.FSkin;
import forge.assets.FSkinImage;
import forge.assets.FTextureRegionImage;
import forge.assets.ImageCache;
import forge.item.PaperCard;
import forge.itemmanager.CardManager;
import forge.itemmanager.ItemManagerConfig;
@@ -112,6 +113,9 @@ public class FDeckViewer extends FScreen {
public static void show(final Deck deck0) {
if (deck0 == null) { return; }
/*preload deck to cache*/
ImageCache.preloadCache(deck0);
deckViewer = new FDeckViewer(deck0);
deckViewer.setRotate180(MatchController.getView() != null && MatchController.getView().isTopHumanPlayerActive());
Forge.openScreen(deckViewer);

View File

@@ -5,6 +5,7 @@ import java.util.ArrayList;
import java.util.List;
import forge.GuiBase;
import forge.assets.ImageCache;
import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deck.DeckSection;
@@ -625,6 +626,10 @@ public abstract class LobbyScreen extends LaunchScreen implements ILobbyView {
if (isNewPanel) {
panel.setVisible(true);
}
if (Forge.gameInProgress) {
/*preload deck to cache*/
ImageCache.preloadCache(decks[i]);
}
Gdx.graphics.requestRendering();
}
else if (hasPanel) {

View File

@@ -445,7 +445,6 @@ public class MatchController extends AbstractGuiGame {
@Override
public void afterGameEnd() {
Forge.back();
ImageCache.disposeTexture();
//view = null;
}

View File

@@ -355,6 +355,32 @@ public class MatchScreen extends FScreen {
final GameView game = MatchController.instance.getGameView();
if (game == null) { return; }
if(gameMenu!=null) {
if(gameMenu.getChildCount()>3){
if(viewWinLose == null) {
gameMenu.getChildAt(0).setEnabled(true);
gameMenu.getChildAt(1).setEnabled(true);
gameMenu.getChildAt(2).setEnabled(true);
gameMenu.getChildAt(3).setEnabled(true);
gameMenu.getChildAt(4).setEnabled(false);
} else {
gameMenu.getChildAt(0).setEnabled(false);
gameMenu.getChildAt(1).setEnabled(false);
gameMenu.getChildAt(2).setEnabled(false);
gameMenu.getChildAt(3).setEnabled(false);
gameMenu.getChildAt(4).setEnabled(true);
}
}
}
if(devMenu!=null) {
if(devMenu.isVisible()){
if(viewWinLose == null)
devMenu.setEnabled(true);
else
devMenu.setEnabled(false);
}
}
//draw arrows for paired cards
Set<CardView> pairedCards = new HashSet<>();
for (VPlayerPanel playerPanel : playerPanels.values()) {

View File

@@ -92,7 +92,7 @@ public class VGameMenu extends FDropDownMenu {
SettingsScreen.show(false);
}
}));
addItem(new FMenuItem(localizer.getMessage("lblShowWinLoseOverlay"), null, new FEventHandler() {
addItem(new FMenuItem(localizer.getMessage("lblShowWinLoseOverlay"), FSkinImage.ENDTURN, new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
MatchController.instance.showWinlose();

View File

@@ -382,7 +382,7 @@ public class VStack extends FDropDown {
x += PADDING;
y += PADDING;
CardRenderer.drawCardWithOverlays(g, stackInstance.getSourceCard(), x, y, CARD_WIDTH, CARD_HEIGHT, CardStackPosition.Top, true);
CardRenderer.drawCardWithOverlays(g, stackInstance.getSourceCard(), x, y, CARD_WIDTH, CARD_HEIGHT, CardStackPosition.Top, true, false);
x += CARD_WIDTH + PADDING;
w -= x + PADDING - BORDER_THICKNESS;

View File

@@ -1,7 +1,6 @@
package forge.screens.match.winlose;
import forge.Forge;
import forge.assets.ImageCache;
import forge.game.GameView;
import forge.game.player.PlayerView;
import forge.screens.match.MatchController;
@@ -85,7 +84,6 @@ public class ControlWinLose {
view.hide();
if(humancount == 0) {
Forge.back();
ImageCache.disposeTexture();
}
}

View File

@@ -10,6 +10,7 @@ import forge.Graphics;
import forge.assets.FImage;
import forge.assets.FSkinColor;
import forge.assets.FSkinFont;
import forge.assets.ImageCache;
import forge.card.CardFaceSymbols;
import forge.card.CardRenderer;
import forge.card.ColorSet;
@@ -76,6 +77,8 @@ public class ConquestCommandersScreen extends FScreen {
public void handleEvent(FEvent e) {
final ConquestCommander commander = lstCommanders.getSelectedItem();
if (commander != null) {
/*preload deck to cache*/
ImageCache.preloadCache(commander.getDeck());
preventRefreshOnActivate = true; //refresh not needed since deck changes won't affect commander display
Forge.openScreen(new ConquestDeckEditor(commander));
}

View File

@@ -4,6 +4,7 @@ import com.badlogic.gdx.utils.Align;
import forge.FThreads;
import forge.Forge;
import forge.assets.FSkinFont;
import forge.assets.ImageCache;
import forge.deck.DeckProxy;
import forge.deck.DeckgenUtil;
import forge.deck.FDeckChooser;
@@ -152,6 +153,9 @@ public class QuestDecksScreen extends FScreen {
final DeckProxy deck = lstDecks.getSelectedItem();
if (deck == null) { return; }
/*preload deck to cache*/
ImageCache.preloadCache(deck.getDeck());
needRefreshOnActivate = true;
Forge.openScreen(new QuestDeckEditor(deck));
}

View File

@@ -106,6 +106,10 @@ public class QuestPrefsScreen extends FScreen {
scroller.add(new PrefsOption(localizer.getMessage("lblColorBias"), QPref.STARTING_POOL_COLOR_BIAS, PrefsGroup.DIFFICULTY_ALL));
scroller.add(new PrefsOption(localizer.getMessage("lblPenaltyforLoss"), QPref.PENALTY_LOSS, PrefsGroup.DIFFICULTY_ALL));
//wild opponents addon
scroller.add(new PrefsOption(localizer.getMessage("lblWildOpponentMultiplier"), QPref.WILD_OPPONENTS_MULTIPLIER, PrefsGroup.DIFFICULTY_ALL));
scroller.add(new PrefsOption(localizer.getMessage("lblWildOpponentNumber"), QPref.WILD_OPPONENTS_NUMBER, PrefsGroup.DIFFICULTY_ALL));
//Difficulty Adjustments (Easy)
scroller.add(new PrefsHeader(localizer.getMessage("lblDifficultyAdjustmentsEasy"), FSkinImage.QUEST_NOTES, PrefsGroup.DIFFICULTY_EASY));
scroller.add(new PrefsOption(localizer.getMessage("lblWinsForBooster"), QPref.WINS_BOOSTER_EASY, PrefsGroup.DIFFICULTY_EASY));

View File

@@ -9,6 +9,7 @@ import forge.GuiBase;
import forge.assets.FSkinColor;
import forge.assets.FSkinFont;
import forge.assets.FSkinImage;
import forge.assets.ImageCache;
import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deck.DeckGroup;
@@ -25,6 +26,7 @@ import forge.quest.QuestEventDraft;
import forge.quest.QuestTournamentController;
import forge.quest.QuestDraftUtils.Mode;
import forge.quest.data.QuestEventDraftContainer;
import forge.screens.LoadingOverlay;
import forge.screens.limited.DraftingProcessScreen;
import forge.toolbox.FButton;
import forge.toolbox.FContainer;
@@ -227,7 +229,17 @@ public class QuestTournamentsScreen extends QuestLaunchScreen implements IQuestT
@Override
public void startDraft(BoosterDraft draft) {
Forge.openScreen(new DraftingProcessScreen(draft, EditorType.QuestDraft, controller));
FThreads.invokeInEdtLater(new Runnable() {
@Override
public void run() {
LoadingOverlay.show("Loading Quest Tournament", new Runnable() {
@Override
public void run() {
Forge.openScreen(new DraftingProcessScreen(draft, EditorType.QuestDraft, controller));
}
});
}
});
}
private Deck getDeck() {
@@ -241,6 +253,8 @@ public class QuestTournamentsScreen extends QuestLaunchScreen implements IQuestT
public void editDeck(boolean isExistingDeck) {
Deck deck = getDeck();
if (deck != null) {
/*preload deck to cache*/
ImageCache.preloadCache(deck);
if (isExistingDeck) {
Forge.openScreen(new QuestDraftDeckEditor(deck.getName()));
}

View File

@@ -60,6 +60,9 @@ public class FButton extends FDisplayObject implements IButton {
}
private void resetImg() {
imgL = FSkinImage.BTN_UP_LEFT;
imgM = FSkinImage.BTN_UP_CENTER;
imgR = FSkinImage.BTN_UP_RIGHT;
if (hdbuttonskin())
{
imgL = FSkinImage.HDBTN_UP_LEFT;
@@ -95,6 +98,9 @@ public class FButton extends FDisplayObject implements IButton {
resetImg();
}
else {
imgL = FSkinImage.BTN_DISABLED_LEFT;
imgM = FSkinImage.BTN_DISABLED_CENTER;
imgR = FSkinImage.BTN_DISABLED_RIGHT;
if (hdbuttonskin())
{
imgL = FSkinImage.HDBTN_DISABLED_LEFT;
@@ -108,9 +114,9 @@ public class FButton extends FDisplayObject implements IButton {
}
}
/**
/**
* Button toggle state, for a "permanently pressed" functionality, e.g. as a tab.
*
*
* @return boolean
*/
public boolean isToggled() {
@@ -121,6 +127,9 @@ public class FButton extends FDisplayObject implements IButton {
toggled = b0;
if (toggled) {
imgL = FSkinImage.BTN_TOGGLE_LEFT;
imgM = FSkinImage.BTN_TOGGLE_CENTER;
imgR = FSkinImage.BTN_TOGGLE_RIGHT;
if (hdbuttonskin())
{
imgL = FSkinImage.HDBTN_TOGGLE_LEFT;
@@ -136,6 +145,9 @@ public class FButton extends FDisplayObject implements IButton {
resetImg();
}
else {
imgL = FSkinImage.BTN_DISABLED_LEFT;
imgM = FSkinImage.BTN_DISABLED_CENTER;
imgR = FSkinImage.BTN_DISABLED_RIGHT;
if (hdbuttonskin())
{
imgL = FSkinImage.HDBTN_DISABLED_LEFT;
@@ -170,6 +182,9 @@ public class FButton extends FDisplayObject implements IButton {
@Override
public final boolean press(float x, float y) {
if (isToggled()) { return true; }
imgL = FSkinImage.BTN_DOWN_LEFT;
imgM = FSkinImage.BTN_DOWN_CENTER;
imgR = FSkinImage.BTN_DOWN_RIGHT;
if (hdbuttonskin())
{
@@ -227,51 +242,51 @@ public class FButton extends FDisplayObject implements IButton {
else {
//determine images to draw and text alignment based on which corner button is in (if any)
switch (corner) {
case None:
if (w > 2 * h) {
g.drawImage(imgL, 0, 0, h, h);
g.drawImage(imgM, h, 0, w - (2 * h), h);
g.drawImage(imgR, w - h, 0, h, h);
}
else {
g.drawImage(imgL, 0, 0, cornerButtonWidth, h);
g.drawImage(imgR, cornerButtonWidth, 0, w - cornerButtonWidth, h);
}
x += PADDING;
w -= 2 * PADDING;
break;
case BottomLeft:
g.startClip(x, y, w, h);
g.drawImage(imgM, 0, 0, cornerButtonWidth, cornerButtonHeight);
g.drawImage(imgR, cornerButtonWidth, 0, cornerButtonWidth, cornerButtonHeight);
g.endClip();
w -= cornerTextOffsetX;
y += cornerTextOffsetY;
h -= cornerTextOffsetY;
break;
case BottomRight:
g.startClip(x, y, w, h);
g.drawImage(imgL, 0, 0, cornerButtonWidth, cornerButtonHeight);
g.drawImage(imgM, cornerButtonWidth, 0, cornerButtonWidth, cornerButtonHeight);
g.endClip();
x += cornerTextOffsetX;
w -= cornerTextOffsetX;
y += cornerTextOffsetY;
h -= cornerTextOffsetY;
break;
case BottomMiddle:
g.startClip(x, y, w, h);
cornerButtonWidth = w / 3;
cornerTextOffsetX = cornerButtonWidth / 2;
g.drawImage(imgL, 0, 0, cornerButtonWidth, cornerButtonHeight);
g.drawImage(imgM, cornerButtonWidth, 0, w - 2 * cornerButtonWidth, cornerButtonHeight);
g.drawImage(imgR, w - cornerButtonWidth, 0, cornerButtonWidth, cornerButtonHeight);
g.endClip();
x += cornerTextOffsetX / 2;
w -= cornerTextOffsetX;
y += cornerTextOffsetY;
h -= cornerTextOffsetY;
break;
case None:
if (w > 2 * h) {
g.drawImage(imgL, 0, 0, h, h);
g.drawImage(imgM, h, 0, w - (2 * h), h);
g.drawImage(imgR, w - h, 0, h, h);
}
else {
g.drawImage(imgL, 0, 0, cornerButtonWidth, h);
g.drawImage(imgR, cornerButtonWidth, 0, w - cornerButtonWidth, h);
}
x += PADDING;
w -= 2 * PADDING;
break;
case BottomLeft:
g.startClip(x, y, w, h);
g.drawImage(imgM, 0, 0, cornerButtonWidth, cornerButtonHeight);
g.drawImage(imgR, cornerButtonWidth, 0, cornerButtonWidth, cornerButtonHeight);
g.endClip();
w -= cornerTextOffsetX;
y += cornerTextOffsetY;
h -= cornerTextOffsetY;
break;
case BottomRight:
g.startClip(x, y, w, h);
g.drawImage(imgL, 0, 0, cornerButtonWidth, cornerButtonHeight);
g.drawImage(imgM, cornerButtonWidth, 0, cornerButtonWidth, cornerButtonHeight);
g.endClip();
x += cornerTextOffsetX;
w -= cornerTextOffsetX;
y += cornerTextOffsetY;
h -= cornerTextOffsetY;
break;
case BottomMiddle:
g.startClip(x, y, w, h);
cornerButtonWidth = w / 3;
cornerTextOffsetX = cornerButtonWidth / 2;
g.drawImage(imgL, 0, 0, cornerButtonWidth, cornerButtonHeight);
g.drawImage(imgM, cornerButtonWidth, 0, w - 2 * cornerButtonWidth, cornerButtonHeight);
g.drawImage(imgR, w - cornerButtonWidth, 0, cornerButtonWidth, cornerButtonHeight);
g.endClip();
x += cornerTextOffsetX / 2;
w -= cornerTextOffsetX;
y += cornerTextOffsetY;
h -= cornerTextOffsetY;
break;
}
}
@@ -297,9 +312,9 @@ public class FButton extends FDisplayObject implements IButton {
@Override
public boolean keyDown(int keyCode) {
switch (keyCode) {
case Keys.ENTER:
case Keys.SPACE:
return trigger(); //trigger button on Enter or Space
case Keys.ENTER:
case Keys.SPACE:
return trigger(); //trigger button on Enter or Space
}
return false;
}
@@ -333,4 +348,4 @@ public class FButton extends FDisplayObject implements IButton {
public FSkinColor getForeColor() {
return foreColor;
}
}
}

View File

@@ -367,6 +367,24 @@ public class FChoiceList<T> extends FList<T> implements ActivateHandler {
g.drawText(getChoiceText(value), font, foreColor, x, y, w, h, false, Align.center, true);
}
}
//simple check for cardview needed on some special renderer for cards
private boolean showAlternate(CardView cardView, String value){
boolean showAlt = false;
if(cardView.hasAlternateState()){
if(cardView.hasBackSide())
showAlt = value.contains(cardView.getBackSideName());
else if (cardView.isAdventureCard())
showAlt = value.equals(cardView.getAlternateState().getAbilityText());
else if (cardView.isSplitCard()) {
//special case if aftermath cards can be cast from graveyard like yawgmoths will, you will have choices
if (cardView.getAlternateState().getOracleText().contains("Aftermath"))
showAlt = cardView.getAlternateState().getOracleText().contains(value);
else
showAlt = value.equals(cardView.getAlternateState().getAbilityText());
}
}
return showAlt;
}
//special renderer for cards
protected class PaperCardItemRenderer extends ItemRenderer {
@Override
@@ -464,7 +482,8 @@ public class FChoiceList<T> extends FList<T> implements ActivateHandler {
@Override
public boolean tap(Integer index, T value, float x, float y, int count) {
if (x <= VStack.CARD_WIDTH + 2 * FList.PADDING) {
CardZoom.show(((IHasCardView)value).getCardView());
CardView cv = ((IHasCardView)value).getCardView();
CardZoom.show(cv, showAlternate(cv, value.toString()));
return true;
}
return false;
@@ -472,13 +491,16 @@ public class FChoiceList<T> extends FList<T> implements ActivateHandler {
@Override
public boolean longPress(Integer index, T value, float x, float y) {
CardZoom.show(((IHasCardView)value).getCardView());
CardView cv = ((IHasCardView)value).getCardView();
CardZoom.show(cv, showAlternate(cv, value.toString()));
return true;
}
@Override
public void drawValue(Graphics g, T value, FSkinFont font, FSkinColor foreColor, boolean pressed, float x, float y, float w, float h) {
CardRenderer.drawCardWithOverlays(g, ((IHasCardView)value).getCardView(), x, y, VStack.CARD_WIDTH, VStack.CARD_HEIGHT, CardStackPosition.Top);
CardView cv = ((IHasCardView)value).getCardView();
boolean showAlternate = showAlternate(cv, value.toString());
CardRenderer.drawCardWithOverlays(g, cv, x, y, VStack.CARD_WIDTH, VStack.CARD_HEIGHT, CardStackPosition.Top, false, showAlternate);
float dx = VStack.CARD_WIDTH + FList.PADDING;
x += dx;

View File

@@ -1,12 +1,14 @@
Agetian
apantel
Austinio7116
CCTV-1
Churrufli
DrDev
Elwin
excessum
Flair
Gos
guytrash
Hanmac
Indigo Dragon
Jamin Collins
@@ -16,7 +18,9 @@ KrazyTheFox
leriomaggio
Luke
Marek14
Marvel
mcrawford620
medusa
Meerkov
Myrd
nefigah
@@ -28,8 +32,11 @@ Seravy
Sirspud
Sloth
slyfox7777777
Snoops
Sol
squee1968
Swordshine
Svaldan
tjtillman
tojammot
torridus

View File

@@ -6130,3 +6130,41 @@ Bloodchief's Thirst|ZNR|2
Roil Eruption|ZNR|2
Roiling Regrowth|ZNR|2
Kargan Warleader|ZNR|2
[ZNRModalDoubleFaceCards]
1 Agadeem's Awakening|ZNR
1 Emeria's Call|ZNR
1 Sea Gate Restoration|ZNR
1 Shatterskull Smashing|ZNR
1 Turntimber Symbiosis|ZNR
2 Branchloft Pathway|ZNR
2 Brightclimb Pathway|ZNR
2 Clearwater Pathway|ZNR
2 Cragcrown Pathway|ZNR
2 Glasspool Mimic|ZNR
2 Hagra Mauling|ZNR
2 Kazandu Mammoth|ZNR
2 Needleverge Pathway|ZNR
2 Ondu Inversion|ZNR
2 Riverglide Pathway|ZNR
2 Valakut Awakening|ZNR
6 Akoum Warrior|ZNR
6 Bala Ged Recovery|ZNR
6 Beyeen Veil|ZNR
6 Blackbloom Rogue|ZNR
6 Jwari Disruption|ZNR
6 Kabira Takedown|ZNR
6 Kazuul's Fury|ZNR
6 Khalni Ambush|ZNR
6 Makindi Stampede|ZNR
6 Malakir Rebirth|ZNR
6 Pelakka Predation|ZNR
6 Sejiri Shelter|ZNR
6 Silundi Vision|ZNR
6 Skyclave Cleric|ZNR
6 Song-Mad Treachery|ZNR
6 Spikefield Hazard|ZNR
6 Tangled Florahedron|ZNR
6 Umara Wizard|ZNR
6 Vastwood Fortification|ZNR
6 Zof Consumption|ZNR

View File

@@ -1,7 +1,7 @@
Name:Admiral's Order
ManaCost:1 U U
Types:Instant
SVar:AltCost:Cost$ U | CheckSVar$ X | References$ X | Description$ Raid — If you attacked this turn, you may pay {U} rather than pay this spell's mana cost.
A:SP$ Counter | Cost$ 1 U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | SpellDescription$ Counter target spell.
A:SP$ Counter | Cost$ U | CheckSVar$ X | References$ X | SVarCompare$ GE1 | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | SpellDescription$ Raid — If you attacked this turn, you may pay {U} rather than pay this spell's mana cost. Counter target spell.
SVar:X:Count$AttackersDeclared
Oracle:Raid — If you attacked this turn, you may pay {U} rather than pay this spell's mana cost.\nCounter target spell.

View File

@@ -2,9 +2,8 @@ Name:Aegis of Honor
ManaCost:W
Types:Enchantment
A:AB$ Effect | Cost$ 1 | ReplacementEffects$ SelflessDamage | SVars$ SelflessDmg,ExileEffect | References$ SelflessDamage,SelflessDmg,ExileEffect | AILogic$ RedirectSpellDamageFromPlayer | Stackable$ False | SpellDescription$ The next time an instant or sorcery spell would deal damage to you this turn, that spell deals that damage to its controller instead.
SVar:SelflessDamage:Event$ DamageDone | ValidTarget$ You | ValidSource$ Instant,Sorcery | ReplaceWith$ SelflessDmg | Description$ The next time a source of your choice would deal damage this turn, that damage is dealt to that source's controller instead.
SVar:SelflessDamage:Event$ DamageDone | ValidTarget$ You | ValidSource$ Instant,Sorcery | ReplaceWith$ SelflessDmg | DamageTarget$ ReplacedSourceController | Description$ The next time a source of your choice would deal damage this turn, that damage is dealt to that source's controller instead.
SVar:SelflessDmg:DB$ ReplaceEffect | VarName$ Affected | VarValue$ ReplacedSourceController | VarType$ Player | SubAbility$ ExileEffect
SVar:ExileEffect:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile
SVar:NonStackingEffect:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/aegis_of_honor.jpg
Oracle:{1}: The next time an instant or sorcery spell would deal damage to you this turn, that spell deals that damage to its controller instead.

View File

@@ -4,6 +4,5 @@ Types:Creature Elf Warrior
PT:1+*/1+*
S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ CARDNAME's power and toughness are each equal to 1 plus the number of lands you control.
SVar:X:Count$Valid Land.YouCtrl/Plus.1
SVar:AltCost:Cost$ ExileFromHand<2/Card.Green> | Description$ You may exile two green cards from your hand rather than pay CARDNAME's mana cost.
SVar:Picture:http://www.wizards.com/global/images/magic/general/allosaurus_rider.jpg
SVar:AltCost:Cost$ ExileFromHand<2/Card.Green> | Description$ You may exile two green cards from your hand rather than pay this spell's mana cost.
Oracle:You may exile two green cards from your hand rather than pay this spell's mana cost.\nAllosaurus Rider's power and toughness are each equal to 1 plus the number of lands you control.

View File

@@ -1,9 +1,8 @@
Name:Angelic Favor
ManaCost:3 W
Types:Instant
A:SP$ Token | Cost$ 3 W | TokenScript$ w_4_4_angel_flying | LegacyImage$ w 4 4 angel flying nms | AtEOT$ Exile | ActivationPhases$ BeginCombat->EndCombat | SpellDescription$ Cast CARDNAME only during combat. Create a 4/4 white Angel creature token with flying. Exile it at the beginning of the next end step.
SVar:AltCost:Cost$ tapXType<1/Creature> | IsPresent$ Plains.YouCtrl | Description$ If you control a Plains, you may tap an untapped creature you control rather than pay CARDNAME's mana cost.
SVar:AltCost:Cost$ tapXType<1/Creature> | IsPresent$ Plains.YouCtrl | Description$ If you control a Plains, you may tap an untapped creature you control rather than pay this spell's mana cost.
A:SP$ Token | Cost$ 3 W | TokenScript$ w_4_4_angel_flying | AtEOT$ Exile | ActivationPhases$ BeginCombat->EndCombat | StackDescription$ {p:You} creates a 4/4 white Angel creature token with flying. Exile it at the beginning of the next end step. | SpellDescription$ Cast this spell only during combat. Create a 4/4 white Angel creature token with flying. Exile it at the beginning of the next end step.
DeckHas:Ability$Token
AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/angelic_favor.jpg
Oracle:If you control a Plains, you may tap an untapped creature you control rather than pay this spell's mana cost.\nCast this spell only during combat.\nCreate a 4/4 white Angel creature token with flying. Exile it at the beginning of the next end step.

View File

@@ -1,7 +1,7 @@
Name:Archive Trap
ManaCost:3 U U
Types:Instant Trap
SVar:AltCost:Cost$ 0 | CheckSVar$ TrapTrigger | References$ TrapTrigger | Description$ If an opponent searched their library this turn, you may pay {0} rather than pay this spell's mana cost.
A:SP$ Mill | Cost$ 3 U U | NumCards$ 13 | ValidTgts$ Opponent | TgtPrompt$ Choose an opponent | SpellDescription$ Target opponent mills thirteen cards.
A:SP$ Mill | Cost$ 0 | CheckSVar$ TrapTrigger | NumCards$ 13 | ValidTgts$ Opponent | TgtPrompt$ Choose an opponent | CostDesc$ If an opponent searched their library this turn, you may pay {0} rather than pay CARDNAME's mana cost. | References$ TrapTrigger | SpellDescription$
SVar:TrapTrigger:Count$SearchedLibrary.Opponent
Oracle:If an opponent searched their library this turn, you may pay {0} rather than pay this spell's mana cost.\nTarget opponent mills thirteen cards.

View File

@@ -1,7 +1,6 @@
Name:Arrow Volley Trap
ManaCost:3 W W
Types:Instant Trap
SVar:AltCost:Cost$ 1 W | IsPresent$ Creature.attacking | PresentCompare$ GE4 | Description$ If four or more creatures are attacking, you may pay {1}{W} rather than pay this spell's mana cost.
A:SP$ DealDamage | Cost$ 3 W W | ValidTgts$ Creature.attacking | TgtPrompt$ Select target attacking creature to distribute damage to | NumDmg$ 5 | TargetMin$ 1 | TargetMax$ 5 | DividedAsYouChoose$ 5 | SpellDescription$ CARDNAME deals 5 damage divided as you choose among any number of target attacking creatures.
A:SP$ DealDamage | Cost$ 1 W | ValidTgts$ Creature.attacking | TgtPrompt$ Select target attacking creature to distribute damage to | NumDmg$ 5 | TargetMin$ 1 | TargetMax$ 5 | DividedAsYouChoose$ 5 | IsPresent$ Creature.attacking | PresentCompare$ GE4 | CostDesc$ If four or more creatures are attacking, you may pay {1}{W} rather than pay CARDNAME's mana cost. | SpellDescription$
SVar:Picture:http://www.wizards.com/global/images/magic/general/arrow_volley_trap.jpg
Oracle:If four or more creatures are attacking, you may pay {1}{W} rather than pay this spell's mana cost.\nArrow Volley Trap deals 5 damage divided as you choose among any number of target attacking creatures.

View File

@@ -1,8 +1,7 @@
Name:Baloth Cage Trap
ManaCost:3 G G
Types:Instant Trap
A:SP$ Token | Cost$ 3 G G | TokenAmount$ 1 | TokenScript$ g_4_4_beast | TokenOwner$ You | LegacyImage$ g 4 4 beast zen | SpellDescription$ Create a 4/4 green Beast creature token.
A:SP$ Token | Cost$ 1 G | CheckSVar$ ArtifactsEntered | TokenAmount$ 1 | TokenScript$ g_4_4_beast | TokenOwner$ You | LegacyImage$ g 4 4 beast zen | SpellDescription$ If an opponent had an artifact enter the battlefield under their control this turn, you may pay {1}{G} rather than pay Baloth Cage Trap's mana cost.
SVar:AltCost:Cost$ 1 G | CheckSVar$ ArtifactsEntered | References$ ArtifactsEntered | Description$ If an opponent had an artifact enter the battlefield under their control this turn, you may pay {1}{G} rather than pay this spell's mana cost.
A:SP$ Token | Cost$ 3 G G | TokenAmount$ 1 | TokenScript$ g_4_4_beast | TokenOwner$ You | StackDescription$ {p:You} creates a 4/4 green Beast creature token. | SpellDescription$ Create a 4/4 green Beast creature token.
SVar:ArtifactsEntered:Count$ThisTurnEntered_Battlefield_Artifact.OppCtrl
SVar:Picture:http://www.wizards.com/global/images/magic/general/baloth_cage_trap.jpg
Oracle:If an opponent had an artifact enter the battlefield under their control this turn, you may pay {1}{G} rather than pay Baloth Cage Trap's mana cost.\nCreate a 4/4 green Beast creature token.
Oracle:If an opponent had an artifact enter the battlefield under their control this turn, you may pay {1}{G} rather than pay this spell's mana cost.\nCreate a 4/4 green Beast creature token.

View File

@@ -0,0 +1,10 @@
Name:Baloth Packhunter
ManaCost:3 G
Types:Creature Beast
PT:3/3
K:Trample
T:Mode$ ChangesZone | ValidCard$ Card.Self | Destination$ Battlefield | Execute$ TrigPutCounters | TriggerDescription$ When CARDNAME enters the battlefield, put two +1/+1 counters on each other creature you control named Baloth Packhunter.
SVar:TrigPutCounters:DB$ PutCounterAll | ValidCards$ Creature.Other+namedBaloth Packhunter | CounterType$ P1P1 | CounterNum$ 2
DeckHints:Name$CARDNAME
DeckHas:Ability$Counters
Oracle:Trample\nWhen Baloth Packhunter enters the battlefield, put two +1/+1 counters on each other creature you control named Baloth Packhunter.

View File

@@ -1,10 +1,10 @@
Name:Blazing Shoal
ManaCost:X R R
Types:Instant Arcane
A:SP$ Pump | Cost$ X R R | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +X | References$ X | SpellDescription$ Target creature gets +X/+0 until end of turn.
A:SP$ Pump | Cost$ ExileFromHand<1/Card.Red> | CostDesc$ You may exile a red card from your hand rather than pay Blazing Shoal's mana cost. | ValidTgts$ Creature | NumAtt$ +Y | References$ Y | SpellDescription$ Target creature gets +X/+0 until end of turn, where X is the exiled card's converted mana cost.
SVar:AltCost:Cost$ ExileFromHand<1/Card.Red+Other/red card> | Description$ You may exile a red card with converted mana cost X from your hand rather than pay this spell's mana cost.
A:SP$ Pump | Cost$ X R R | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +Z | References$ X,Y,Z | SpellDescription$ Target creature gets +X/+0 until end of turn.
SVar:X:Count$xPaid
SVar:Y:Exiled$CardManaCost
SVar:Z:SVar$Y/Plus.X
AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/blazing_shoal.jpg
Oracle:You may exile a red card with converted mana cost X from your hand rather than pay this spell's mana cost.\nTarget creature gets +X/+0 until end of turn.

View File

@@ -2,8 +2,7 @@ Name:Blood of the Martyr
ManaCost:W W W
Types:Instant
A:SP$ Effect | Cost$ W W W | Name$ Blood of the Martyr Effect | ReplacementEffects$ MartyrDamage | SVars$ DmgYou | SpellDescription$ Until end of turn, if damage would be dealt to any creature, you may have that damage dealt to you instead.
SVar:Damage:Event$ DamageDone | ValidTarget$ Creature | Optional$ True | OptionalDecider$ You | ReplaceWith$ DmgYou | Description$ Until end of turn, if damage would be dealt to any creature, you may have that damage dealt to you instead.
SVar:Damage:Event$ DamageDone | ValidTarget$ Creature | Optional$ True | OptionalDecider$ You | DamageTarget$ You | ReplaceWith$ DmgYou | Description$ Until end of turn, if damage would be dealt to any creature, you may have that damage dealt to you instead.
SVar:DmgYou:DB$ ReplaceEffect | VarName$ Affected | VarValue$ You | VarType$ Player
AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/blood_of_the_martyr.jpg
Oracle:Until end of turn, if damage would be dealt to any creature, you may have that damage dealt to you instead.

View File

@@ -2,8 +2,7 @@ Name:Bounty of the Hunt
ManaCost:3 G G
Types:Instant
A:SP$ PutCounter | Cost$ 3 G G | ValidTgts$ Creature | TgtPrompt$ Select target creature to distribute counters to | CounterType$ P1P1 | CounterNum$ 3 | TargetMin$ 1 | TargetMax$ 3 | DividedAsYouChoose$ 3 | RemovePhase$ Cleanup | SpellDescription$ Distribute three +1/+1 counters among one, two, or three target creatures. For each +1/+1 counter you put on a creature this way, remove a +1/+1 counter from that creature at the beginning of the next cleanup step.
SVar:AltCost:Cost$ExileFromHand<1/Card.Green> | Description$ You may exile a green card from your hand rather than pay CARDNAME's mana cost.
SVar:AltCost:Cost$ExileFromHand<1/Card.Green+Other> | Description$ You may exile a green card from your hand rather than pay this spell's mana cost.
DeckHas:Ability$Counters
AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/bounty_of_the_hunt.jpg
Oracle:You may exile a green card from your hand rather than pay this spell's mana cost.\nDistribute three +1/+1 counters among one, two, or three target creatures. For each +1/+1 counter you put on a creature this way, remove a +1/+1 counter from that creature at the beginning of the next cleanup step.

View File

@@ -3,9 +3,8 @@ ManaCost:7 B B
Types:Creature Bringer
PT:5/5
K:Trample
SVar:AltCost:Cost$ W U B R G | Description$ You may pay {W}{U}{B}{R}{G} rather than pay CARDNAME's mana cost.
SVar:AltCost:Cost$ W U B R G | Description$ You may pay {W}{U}{B}{R}{G} rather than pay this spell's mana cost.
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigChange | TriggerDescription$ At the beginning of your upkeep, you may pay 2 life. If you do, search your library for a card, then shuffle your library and put that card on top of it.
SVar:TrigChange:AB$ChangeZone | Cost$ PayLife<2> | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Card | ChangeNum$ 1
SVar:TrigChange:AB$ ChangeZone | Cost$ PayLife<2> | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Card | ChangeNum$ 1
AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/bringer_of_the_black_dawn.jpg
Oracle:You may pay {W}{U}{B}{R}{G} rather than pay this spell's mana cost.\nTrample\nAt the beginning of your upkeep, you may pay 2 life. If you do, search your library for a card, then shuffle your library and put that card on top of it.

View File

@@ -3,8 +3,7 @@ ManaCost:7 U U
Types:Creature Bringer
PT:5/5
K:Trample
SVar:AltCost:Cost$ W U B R G | Description$ You may pay {W}{U}{B}{R}{G} rather than pay this spell's mana cost.
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigDraw | TriggerDescription$ At the beginning of your upkeep, you may draw two cards.
SVar:TrigDraw:DB$Draw | Defined$ You | NumCards$ 2
SVar:AltCost:Cost$ W U B R G | Description$ You may pay {W}{U}{B}{R}{G} rather than pay CARDNAME's mana cost.
SVar:Picture:http://www.wizards.com/global/images/magic/general/bringer_of_the_blue_dawn.jpg
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 2
Oracle:You may pay {W}{U}{B}{R}{G} rather than pay this spell's mana cost.\nTrample\nAt the beginning of your upkeep, you may draw two cards.

View File

@@ -3,8 +3,8 @@ ManaCost:7 G G
Types:Creature Bringer
PT:5/5
K:Trample
SVar:AltCost:Cost$ W U B R G | Description$ You may pay {W}{U}{B}{R}{G} rather than pay CARDNAME's mana cost.
SVar:AltCost:Cost$ W U B R G | Description$ You may pay {W}{U}{B}{R}{G} rather than pay this spell's mana cost.
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigToken | TriggerDescription$ At the beginning of your upkeep, you may create a 3/3 green Beast creature token.
SVar:TrigToken:DB$Token | TokenAmount$ 1 | TokenScript$ g_3_3_beast | TokenOwner$ You | LegacyImage$ g 3 3 beast 5dn
SVar:Picture:http://www.wizards.com/global/images/magic/general/bringer_of_the_green_dawn.jpg
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_3_3_beast | TokenOwner$ You
DeckHas:Ability$Token
Oracle:You may pay {W}{U}{B}{R}{G} rather than pay this spell's mana cost.\nTrample\nAt the beginning of your upkeep, you may create a 3/3 green Beast creature token.

View File

@@ -3,8 +3,7 @@ ManaCost:7 R R
Types:Creature Bringer
PT:5/5
K:Trample
SVar:AltCost:Cost$ W U B R G | Description$ You may pay {W}{U}{B}{R}{G} rather than pay CARDNAME's mana cost.
SVar:AltCost:Cost$ W U B R G | Description$ You may pay {W}{U}{B}{R}{G} rather than pay this spell's mana cost.
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigChange | TriggerDescription$ At the beginning of your upkeep, you may untap target creature and gain control of it until end of turn. That creature gains haste until end of turn.
SVar:TrigChange:DB$GainControl | ValidTgts$ Creature | TgtPrompt$ Select target creature | LoseControl$ EOT | Untap$ True | AddKWs$ Haste
SVar:Picture:http://www.wizards.com/global/images/magic/general/bringer_of_the_red_dawn.jpg
SVar:TrigChange:DB$ GainControl | ValidTgts$ Creature | TgtPrompt$ Select target creature | LoseControl$ EOT | Untap$ True | AddKWs$ Haste
Oracle:You may pay {W}{U}{B}{R}{G} rather than pay this spell's mana cost.\nTrample\nAt the beginning of your upkeep, you may untap target creature and gain control of it until end of turn. That creature gains haste until end of turn.

View File

@@ -3,8 +3,9 @@ ManaCost:7 W W
Types:Creature Bringer
PT:5/5
K:Trample
SVar:AltCost:Cost$ W U B R G | Description$ You may pay {W}{U}{B}{R}{G} rather than pay CARDNAME's mana cost.
SVar:AltCost:Cost$ W U B R G | Description$ You may pay {W}{U}{B}{R}{G} rather than pay this spell's mana cost.
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigChange | TriggerDescription$ At the beginning of your upkeep, you may return target artifact card from your graveyard to the battlefield.
SVar:TrigChange:AB$ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Artifact.YouCtrl | Cost$ 0
SVar:Picture:http://www.wizards.com/global/images/magic/general/bringer_of_the_white_dawn.jpg
SVar:TrigChange:AB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Artifact.YouCtrl | Cost$ 0
DeckHas:Ability$Graveyard
DeckNeeds:Type$Artifact
Oracle:You may pay {W}{U}{B}{R}{G} rather than pay this spell's mana cost.\nTrample\nAt the beginning of your upkeep, you may return target artifact card from your graveyard to the battlefield.

View File

@@ -1,9 +1,8 @@
Name:Burn at the Stake
ManaCost:2 R R R
Types:Sorcery
A:SP$ DealDamage | Cost$ 2 R R R tapXType<X/Creature> | CostDesc$ As an additional cost to cast CARDNAME, tap any number of untapped creatures you control. | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ BurnAtTheStakeDmg | References$ X,BurnAtTheStakeDmg | SpellDescription$ CARDNAME deals damage to any target equal to three times the number of creatures tapped this way.
A:SP$ DealDamage | Cost$ 2 R R R tapXType<X/Creature> | CostDesc$ As an additional cost to cast this spell, tap any number of untapped creatures you control. | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ BurnAtTheStakeDmg | References$ X,BurnAtTheStakeDmg | SpellDescription$ CARDNAME deals damage to any target equal to three times the number of creatures tapped this way.
SVar:X:XChoice
SVar:BurnAtTheStakeDmg:Number$3/Times.ChosenX
AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/burn_at_the_stake.jpg
Oracle:As an additional cost to cast this spell, tap any number of untapped creatures you control.\nBurn at the Stake deals damage to any target equal to three times the number of creatures tapped this way.

View File

@@ -1,8 +1,7 @@
Name:Burning Wish
ManaCost:1 R
Types:Sorcery
A:SP$ ChangeZone | Cost$ 1 R | Origin$ Sideboard | Destination$ Hand | ChangeType$ Sorcery.YouOwn | ChangeNum$ 1 | SubAbility$ DBChange | SpellDescription$ You may choose a sorcery card you own from outside the game, reveal that card, and put it into your hand. Exile CARDNAME.
A:SP$ ChangeZone | Cost$ 1 R | Reveal$ True | Origin$ Sideboard | Destination$ Hand | ChangeType$ Sorcery.YouOwn | ChangeTypeDesc$ sorcery card they own | ChangeNum$ 1 | SubAbility$ DBChange | Hidden$ True | SpellDescription$ You may reveal a sorcery card you own from outside the game and put it into your hand. Exile CARDNAME.
SVar:DBChange:DB$ ChangeZone | Origin$ Stack | Destination$ Exile
AI:RemoveDeck:Random
SVar:Picture:http://www.wizards.com/global/images/magic/general/burning_wish.jpg
Oracle:You may choose a sorcery card you own from outside the game, reveal that card, and put it into your hand. Exile Burning Wish.
Oracle:You may reveal a sorcery card you own from outside the game and put it into your hand. Exile Burning Wish.

View File

@@ -5,14 +5,11 @@ R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplaceWith$
SVar:DBChooseOpp:DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent | ChoiceTitle$ Choose an opponent to give control to: | AILogic$ Curse | SubAbility$ MoveToPlay
SVar:MoveToPlay:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Battlefield | Defined$ ReplacedCard | GainControl$ True | NewController$ ChosenPlayer | SubAbility$ ClearRemembered
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigCharm | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, ABILITY
SVar:TrigCharm:DB$ Charm | Choices$ LifePact,DiscardPact,ZombiesPact | ChoiceRestriction$ NotRemembered | RememberChoice$ True | CharmNum$ 1
SVar:LifePact:DB$ SetLife | Defined$ You | LifeAmount$ 4 | ChoiceName$ LifePact | SpellDescription$ Your life total becomes 4.
SVar:DiscardPact:DB$ Discard | Defined$ You | Mode$ Hand | ChoiceName$ DiscardPact | SpellDescription$ Discard your hand.
SVar:ZombiesPact:DB$ RepeatEach | RepeatPlayers$ Player.Opponent | RepeatSubAbility$ MakeZombies | ChoiceName$ ZombiesPact | ChangeZoneTable$ True | SpellDescription$ Each opponent creates five 2/2 black Zombie creature tokens.
SVar:TrigCharm:DB$ Charm | Choices$ LifePact,DiscardPact,ZombiesPact | ChoiceRestriction$ ThisGame | CharmNum$ 1
SVar:LifePact:DB$ SetLife | Defined$ You | LifeAmount$ 4 | SpellDescription$ Your life total becomes 4.
SVar:DiscardPact:DB$ Discard | Defined$ You | Mode$ Hand | SpellDescription$ Discard your hand.
SVar:ZombiesPact:DB$ RepeatEach | RepeatPlayers$ Player.Opponent | RepeatSubAbility$ MakeZombies | ChangeZoneTable$ True | SpellDescription$ Each opponent creates five 2/2 black Zombie creature tokens.
SVar:MakeZombies:DB$ Token | LegacyImage$ b 2 2 zombie rna | TokenAmount$ 5 | TokenScript$ b_2_2_zombie | TokenOwner$ Remembered | SpellDescription$ Each opponent creates five 2/2 black Zombie creature tokens.
# Clear RememberChoice just in case it's not getting cleared by Zone changes
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ ClearRemembered | Static$ True
SVar:ClearRemembered:DB$ Cleanup | ClearRemembered$ True | ClearChosenPlayer$ True
AI:RemoveDeck:Random
DeckHas:Ability$Token
Oracle:Captive Audience enters the battlefield under the control of an opponent of your choice.\nAt the beginning of your upkeep, choose one that hasn't been chosen —\n• Your life total becomes 4.\n• Discard your hand.\n• Each opponent creates five 2/2 black Zombie creature tokens.

View File

@@ -1,8 +1,7 @@
Name:Cathartic Reunion
ManaCost:1 R
Types:Sorcery
A:SP$ Draw | Cost$ 1 R Discard<2/Card> | CostDesc$ As an additional cost to cast CARDNAME, discard two cards. | NumCards$ 3 | Defined$ You | SpellDescription$ Draw three cards.
A:SP$ Draw | Cost$ 1 R Discard<2/Card/cards> | CostDesc$ As an additional cost to cast this spell, discard two cards. | NumCards$ 3 | Defined$ You | SpellDescription$ Draw three cards.
DeckHas:Ability$Discard
DeckHints:Keyword$Madness & Ability$Delirium
SVar:Picture:http://www.wizards.com/global/images/magic/general/cathartic_reunion.jpg
Oracle:As an additional cost to cast this spell, discard two cards.\nDraw three cards.
Oracle:As an additional cost to cast this spell, discard two cards.\nDraw three cards.

View File

@@ -1,7 +1,6 @@
Name:Cave-In
ManaCost:3 R R
Types:Sorcery
SVar:AltCost:Cost$ ExileFromHand<1/Card.Red+Other> | Description$ You may exile a red card from your hand rather than pay this spell's mana cost.
A:SP$ DamageAll | Cost$ 3 R R | NumDmg$ 2 | ValidCards$ Creature | ValidPlayers$ Player | ValidDescription$ each creature and each player. | SpellDescription$ CARDNAME deals 2 damage to each creature and each player.
SVar:AltCost:Cost$ ExileFromHand<1/Card.Red> | Description$ You may exile a red card from your hand rather than pay Cave-In's mana cost.
SVar:Picture:http://www.wizards.com/global/images/magic/general/cave_in.jpg
Oracle:You may exile a red card from your hand rather than pay this spell's mana cost.\nCave-In deals 2 damage to each creature and each player.

View File

@@ -1,7 +1,6 @@
Name:Coax from the Blind Eternities
ManaCost:2 U
Types:Sorcery
A:SP$ ChangeZone | Cost$ 2 U | Origin$ Sideboard,Exile | Destination$ Hand | ChangeType$ Card.Eldrazi+YouOwn | ChangeNum$ 1 | SpellDescription$ You may choose an Eldrazi card you own from outside the game or in exile, reveal that card, and put it into your hand.
A:SP$ ChangeZone | Cost$ 2 U | Origin$ Sideboard,Exile | Destination$ Hand | ChangeType$ Card.Eldrazi+YouOwn | ChangeNum$ 1 | Hidden$ True | Reveal$ True | StackDescription$ {p:You} may reveal an Eldrazi card they own from outside the game or in exile and put it into their hand. | SpellDescription$ You may reveal an Eldrazi card you own from outside the game or in exile and put it into your hand.
AI:RemoveDeck:Random
SVar:Picture:http://www.wizards.com/global/images/magic/general/coax_from_the_blind_eternities.jpg
Oracle:You may choose an Eldrazi card you own from outside the game or in exile, reveal that card, and put it into your hand.
Oracle:You may reveal an Eldrazi card you own from outside the game or in exile and put it into your hand.

View File

@@ -2,11 +2,10 @@ Name:Cobra Trap
ManaCost:4 G G
Types:Instant Trap
T:Mode$ Destroyed | ValidCauser$ Player.Opponent | ValidCard$ Permanent.nonCreature+YouCtrl | Execute$ TrackValidDestroy | Static$ True
SVar:TrackValidDestroy:DB$ StoreSVar | SVar$ SetTrap | Type$ CountSVar | Expression$ SetTrap/Plus.1 | References$ SetTrap
T:Mode$ Phase | Phase$ Cleanup | Execute$ TrigReset | Static$ True
SVar:TrigReset:DB$ StoreSVar | SVar$ SetTrap | Type$ Number | Expression$ 0 | References$ SetSVar
SVar:SetTrap:Number$0
SVar:AltCost:Cost$ G | CheckSVar$ SetTrap | SVarCompare$ GE1 | References$ SetTrap | Description$ If a noncreature permanent under your control was destroyed this turn by a spell or ability an opponent controlled, you may pay {G} rather than pay CARDNAME's mana cost.
A:SP$ Token | Cost$ 4 G G | TokenAmount$ 4 | TokenScript$ g_1_1_snake | TokenOwner$ You | LegacyImage$ g 1 1 snake zen | SpellDescription$ Create four 1/1 green Snake creature tokens.
SVar:Picture:http://www.wizards.com/global/images/magic/general/cobra_trap.jpg
SVar:TrackValidDestroy:DB$ Pump | RememberObjects$ TriggeredCard
T:Mode$ TurnBegin | Execute$ TrigReset | Static$ True
SVar:TrigReset:DB$ Cleanup | ClearRemembered$ True
SVar:SetTrap:Remembered$Amount
SVar:AltCost:Cost$ G | CheckSVar$ SetTrap | References$ SetTrap | Description$ If a noncreature permanent under your control was destroyed this turn by a spell or ability an opponent controlled, you may pay {G} rather than pay this spell's mana cost.
A:SP$ Token | Cost$ 4 G G | TokenAmount$ 4 | TokenScript$ g_1_1_snake | TokenOwner$ You | StackDescription$ {p:You} creates four 1/1 green Snake creature tokens. | SpellDescription$ Create four 1/1 green Snake creature tokens.
Oracle:If a noncreature permanent under your control was destroyed this turn by a spell or ability an opponent controlled, you may pay {G} rather than pay this spell's mana cost.\nCreate four 1/1 green Snake creature tokens.

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