Merge remote-tracking branch 'upstream/master' into deck-importer-decks-file-format

This commit is contained in:
leriomaggio
2021-09-06 01:47:30 +01:00
139 changed files with 1456 additions and 271 deletions

View File

@@ -67,7 +67,7 @@ public final class CardRules implements ICardCharacteristics {
}
void reinitializeFromRules(CardRules newRules) {
if(!newRules.getName().equals(this.getName()))
if (!newRules.getName().equals(this.getName()))
throw new UnsupportedOperationException("You cannot rename the card using the same CardRules object");
splitType = newRules.splitType;
@@ -91,7 +91,7 @@ public final class CardRules implements ICardCharacteristics {
}
}
int len = oracleText.length();
for(int i = 0; i < len; i++) {
for (int i = 0; i < len; i++) {
char c = oracleText.charAt(i); // This is to avoid needless allocations performed by toCharArray()
switch(c) {
case('('): isReminder = i > 0; break; // if oracle has only reminder, consider it valid rules (basic and true lands need this)
@@ -99,7 +99,7 @@ public final class CardRules implements ICardCharacteristics {
case('{'): isSymbol = true; break;
case('}'): isSymbol = false; break;
default:
if(isSymbol && !isReminder) {
if (isSymbol && !isReminder) {
switch(c) {
case('W'): res |= MagicColor.WHITE; break;
case('U'): res |= MagicColor.BLUE; break;
@@ -317,7 +317,7 @@ public final class CardRules implements ICardCharacteristics {
/** Instantiates class, reads a card. For batch operations better create you own reader instance. */
public static CardRules fromScript(Iterable<String> script) {
Reader crr = new Reader();
for(String line : script) {
for (String line : script) {
crr.parseLine(line);
}
return crr.getCard();

View File

@@ -270,7 +270,6 @@ public final class CardRulesPredicates {
return new PredicateSuperType(type, isEqual);
}
/**
* Checks for color.
*

View File

@@ -174,7 +174,7 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
artIndex = Math.max(artIndex0, IPaperCard.DEFAULT_ART_INDEX);
foil = foil0;
rarity = rarity0;
artist = (artist0 != null ? artist0 : IPaperCard.NO_ARTIST_NAME);
artist = (artist0 != null ? TextUtil.normalizeText(artist0) : IPaperCard.NO_ARTIST_NAME);
collectorNumber = (collectorNumber0 != null) && (collectorNumber0.length() > 0) ? collectorNumber0 : IPaperCard.NO_COLLECTOR_NUMBER;
}

View File

@@ -122,7 +122,7 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
String formatEdition = null == edition || CardEdition.UNKNOWN == edition ? "" : "_" + edition.getCode().toLowerCase();
this.imageFileName.add(String.format("%s%s", imageFileName, formatEdition));
for(int idx = 2; idx <= this.artIndex; idx++) {
for (int idx = 2; idx <= this.artIndex; idx++) {
this.imageFileName.add(String.format("%s%d%s", imageFileName, idx, formatEdition));
}
}

View File

@@ -40,7 +40,7 @@ public class TokenDb implements ITokenDatabase {
}
public void preloadTokens() {
for(CardEdition edition : this.editions) {
for (CardEdition edition : this.editions) {
for (String name : edition.getTokens().keySet()) {
try {
getToken(name, edition.getCode());

View File

@@ -1,11 +1,13 @@
package forge.util;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import forge.item.IPaperCard;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
@@ -27,14 +29,19 @@ public class TextUtil {
.put(10, "X").put(9, "IX")
.put(5, "V").put(4, "IV").put(1, "I").build();
public final static String toRoman(int number) {
public static String toRoman(int number) {
if (number <= 0) {
return "";
}
int l = romanMap.floorKey(number);
return romanMap.get(l) + toRoman(number-l);
}
public static String normalizeText(String text) {
if (text == null)
return IPaperCard.NO_ARTIST_NAME;
return Normalizer.normalize(text, Normalizer.Form.NFD);
}
/**
* Safely converts an object to a String.
*

View File

@@ -175,7 +175,29 @@ public final class GameActionUtil {
for (final KeywordInterface inst : source.getKeywords()) {
final String keyword = inst.getOriginal();
if (keyword.startsWith("Escape")) {
if (keyword.startsWith("Disturb")) {
final String[] k = keyword.split(":");
final Cost disturbCost = new Cost(k[1], true);
final SpellAbility newSA = sa.copyWithManaCostReplaced(activator, disturbCost);
newSA.setActivatingPlayer(activator);
newSA.putParam("PrecostDesc", "Disturb —");
newSA.putParam("CostDesc", disturbCost.toString());
// makes new SpellDescription
final StringBuilder desc = new StringBuilder();
desc.append(newSA.getCostDescription());
desc.append("(").append(inst.getReminderText()).append(")");
newSA.setDescription(desc.toString());
newSA.putParam("AfterDescription", "(Disturbed)");
newSA.setAlternativeCost(AlternativeCost.Disturb);
newSA.getRestrictions().setZone(ZoneType.Graveyard);
newSA.setCardState(source.getAlternateState());
alternatives.add(newSA);
} else if (keyword.startsWith("Escape")) {
final String[] k = keyword.split(":");
final Cost escapeCost = new Cost(k[1], true);
@@ -211,6 +233,13 @@ public final class GameActionUtil {
if (keyword.contains(":")) {
final String[] k = keyword.split(":");
flashback.setPayCosts(new Cost(k[1], false));
String extra = k.length > 2 ? k[2] : "";
if (!extra.isEmpty()) {
String[] parts = extra.split("\\$");
String key = parts[0];
String value = parts[1];
flashback.putParam(key, value);
}
}
alternatives.add(flashback);
} else if (keyword.startsWith("Foretell")) {

View File

@@ -166,7 +166,6 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
Map<AbilityKey, Object> moveParams = Maps.newEnumMap(AbilityKey.class);
if (destination == ZoneType.Battlefield) {
if (sa.hasAdditionalAbility("AnimateSubAbility")) {
// need LKI before Animate does apply
moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c));

View File

@@ -1,6 +1,5 @@
package forge.game.ability.effects;
import java.util.List;
import forge.game.ability.AbilityUtils;
@@ -40,7 +39,7 @@ public class LifeGainEffect extends SpellAbilityEffect {
final int lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("LifeAmount"), sa);
List<Player> tgtPlayers = getDefinedPlayersOrTargeted(sa);
if( tgtPlayers.isEmpty() ) {
if (tgtPlayers.isEmpty()) {
tgtPlayers.add(sa.getActivatingPlayer());
}

View File

@@ -1943,7 +1943,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} else {
sbLong.append(parts[0]).append(" ").append(ManaCostParser.parse(parts[1])).append("\r\n");
}
} else if (keyword.startsWith("Morph") || keyword.startsWith("Megamorph") || keyword.startsWith("Escape") || keyword.startsWith("Foretell:")) {
} else if (keyword.startsWith("Morph") || keyword.startsWith("Megamorph")
|| keyword.startsWith("Escape") || keyword.startsWith("Foretell:")
|| keyword.startsWith("Disturb")) {
String[] k = keyword.split(":");
sbLong.append(k[0]);
if (k.length > 1) {
@@ -2033,7 +2035,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} else if (keyword.equals("Provoke") || keyword.equals("Ingest") || keyword.equals("Unleash")
|| keyword.equals("Soulbond") || keyword.equals("Partner") || keyword.equals("Retrace")
|| keyword.equals("Living Weapon") || keyword.equals("Myriad") || keyword.equals("Exploit")
|| keyword.equals("Changeling") || keyword.equals("Delve")
|| keyword.equals("Changeling") || keyword.equals("Delve") || keyword.equals("Decayed")
|| keyword.equals("Split second") || keyword.equals("Sunburst")
|| keyword.equals("Suspend") // for the ones without amount
|| keyword.equals("Foretell") // for the ones without cost
@@ -2544,7 +2546,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
sbBefore.append("\r\n");
} else if (keyword.startsWith("Entwine") || keyword.startsWith("Madness")
|| keyword.startsWith("Miracle") || keyword.startsWith("Recover")
|| keyword.startsWith("Escape") || keyword.startsWith("Foretell:")) {
|| keyword.startsWith("Escape") || keyword.startsWith("Foretell:")
|| keyword.startsWith("Disturb")) {
final String[] k = keyword.split(":");
final Cost cost = new Cost(k[1], false);

View File

@@ -968,6 +968,26 @@ public class CardFactoryUtil {
trigger.setOverridingAbility(AbilityFactory.getAbility(effect, card));
inst.addTrigger(trigger);
} else if (keyword.equals("Decayed")) {
final String attackTrig = "Mode$ Attacks | ValidCard$ Card.Self | Secondary$ True | TriggerDescription$ " +
"When a creature with decayed attacks, sacrifice it at end of combat.";
final String delayTrigStg = "DB$ DelayedTrigger | Mode$ Phase | Phase$ EndCombat | ValidPlayer$ Player | " +
"TriggerDescription$ At end of combat, sacrifice CARDNAME.";
final String trigSacStg = "DB$ SacrificeAll | Defined$ Self | Controller$ You";
SpellAbility delayTrigSA = AbilityFactory.getAbility(delayTrigStg, card);
AbilitySub sacSA = (AbilitySub) AbilityFactory.getAbility(trigSacStg, card);
delayTrigSA.setAdditionalAbility("Execute", sacSA);
final Trigger parsedTrigger = TriggerHandler.parseTrigger(attackTrig, card, intrinsic);
delayTrigSA.setIntrinsic(intrinsic);
parsedTrigger.setOverridingAbility(delayTrigSA);
inst.addTrigger(parsedTrigger);
} else if (keyword.equals("Demonstrate")) {
final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | TriggerDescription$ Demonstrate (" + inst.getReminderText() + ")";
final String youCopyStr = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | MayChooseTarget$ True | Optional$ True | RememberCopies$ True";
@@ -2150,16 +2170,20 @@ public class CardFactoryUtil {
sb.append("Event$ Moved | ValidCard$ Card.Self | Origin$ Stack | ExcludeDestination$ Exile ");
sb.append("| ValidStackSa$ Spell.Flashback | Description$ Flashback");
if (keyword.contains(":")) {
if (keyword.contains(":")) { // K:Flashback:Cost:ExtraParam(Key$Value):ExtraDescription
final String[] k = keyword.split(":");
final Cost cost = new Cost(k[1], false);
sb.append( cost.isOnlyManaCost() ? " " : "");
sb.append(cost.isOnlyManaCost() ? " " : "");
sb.append(cost.toSimpleString());
if (!cost.isOnlyManaCost()) {
sb.append(".");
}
String extraDesc = k.length > 3 ? k[3] : "";
if (!extraDesc.isEmpty()) {
sb.append(". ").append(extraDesc);
}
}
sb.append(" (");
@@ -3400,6 +3424,10 @@ public class CardFactoryUtil {
}
} else if (keyword.startsWith("Dash")) {
effect = "Mode$ Continuous | Affected$ Card.Self+dashed | AddKeyword$ Haste";
} else if (keyword.equals("Decayed")) {
effect = "Mode$ Continuous | Affected$ Card.Self | AddHiddenKeyword$ CARDNAME can't block. | " +
"Secondary$ True";
svars.put("SacrificeEndCombat", "True");
} else if (keyword.equals("Defender")) {
effect = "Mode$ CantAttack | ValidCard$ Card.Self | DefenderKeyword$ True | Secondary$ True";
} else if (keyword.equals("Devoid")) {

View File

@@ -832,7 +832,7 @@ public class CardProperty {
return Iterables.any(CardUtil.getThisTurnCast("Card", source, spellAbility), CardPredicates.sharesNameWith(card));
} else if (restriction.equals("MovedToGrave")) {
if (!(spellAbility instanceof SpellAbility)) {
final SpellAbility root = ((SpellAbility)spellAbility).getRootAbility();
final SpellAbility root = ((SpellAbility) spellAbility).getRootAbility();
if (root != null && (root.getPaidList("MovedToGrave") != null)
&& !root.getPaidList("MovedToGrave").isEmpty()) {
final CardCollectionView cards = root.getPaidList("MovedToGrave");
@@ -855,7 +855,7 @@ public class CardProperty {
if (!(spellAbility instanceof SpellAbility)) {
System.out.println("Looking at TriggeredCard but no SA?");
} else {
Card triggeredCard = ((Card)((SpellAbility)spellAbility).getTriggeringObject(AbilityKey.Card));
Card triggeredCard = ((Card) ((SpellAbility) spellAbility).getTriggeringObject(AbilityKey.Card));
if (triggeredCard != null && card.sharesNameWith(triggeredCard)) {
return true;
}
@@ -926,8 +926,7 @@ public class CardProperty {
final List<Card> cards = CardUtil.getThisTurnCast("Card", source, spellAbility);
if (cards.size() < 2) {
return false;
}
else if (cards.get(1) != card) {
} else if (cards.get(1) != card) {
return false;
}
} else if (property.equals("ThisTurnCast")) {
@@ -1232,8 +1231,7 @@ public class CardProperty {
if (!cards.contains(card)) {
return false;
}
}
else if (property.startsWith("lowestCMC")) {
} else if (property.startsWith("lowestCMC")) {
final CardCollectionView cards = game.getCardsIn(ZoneType.Battlefield);
for (final Card crd : cards) {
if (!crd.isLand() && !crd.isImmutable()) {
@@ -1369,9 +1367,7 @@ public class CardProperty {
if (!Expressions.compare(y, property, x)) {
return false;
}
}
else if (property.startsWith("ManaCost")) {
} else if (property.startsWith("ManaCost")) {
if (!card.getManaCost().getShortString().equals(property.substring(8))) {
return false;
}
@@ -1424,10 +1420,10 @@ public class CardProperty {
GameEntity defender = combat.getDefenderByAttacker(card);
if (defender instanceof Card) {
// attack on a planeswalker that was removed from combat
if (!((Card)defender).isPlaneswalker()) {
if (!((Card) defender).isPlaneswalker()) {
return false;
}
defender = ((Card)defender).getController();
defender = ((Card) defender).getController();
}
if (!sourceController.equals(defender)) {
return false;
@@ -1471,12 +1467,17 @@ public class CardProperty {
if (combat.isBlocking(card, c)) {
return true;
}
};
}
;
return false;
}
} else if (property.startsWith("sharesBlockingAssignmentWith")) {
if (null == combat) { return false; }
if (null == combat.getAttackersBlockedBy(source) || null == combat.getAttackersBlockedBy(card)) { return false; }
if (null == combat) {
return false;
}
if (null == combat.getAttackersBlockedBy(source) || null == combat.getAttackersBlockedBy(card)) {
return false;
}
CardCollection sourceBlocking = new CardCollection(combat.getAttackersBlockedBy(source));
CardCollection thisBlocking = new CardCollection(combat.getAttackersBlockedBy(card));
@@ -1503,16 +1504,17 @@ public class CardProperty {
return false;
}
String valid = property.split(" ")[1];
for(Card c : blocked) {
for (Card c : blocked) {
if (c.isValid(valid, card.getController(), source, spellAbility)) {
return true;
}
}
for(Card c : AbilityUtils.getDefinedCards(source, valid, spellAbility)) {
for (Card c : AbilityUtils.getDefinedCards(source, valid, spellAbility)) {
if (blocked.contains(c)) {
return true;
}
};
}
;
return false;
} else if (property.startsWith("blockedByValidThisTurn ")) {
CardCollectionView blocked = card.getBlockedByThisTurn();
@@ -1527,7 +1529,8 @@ public class CardProperty {
if (blocked.contains(c)) {
return true;
}
};
}
;
return false;
} else if (property.startsWith("blockedBySourceThisTurn")) {
return source.getBlockedByThisTurn().contains(card);

View File

@@ -6,6 +6,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import forge.game.spellability.SpellAbility;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Predicate;
@@ -821,7 +822,7 @@ public class CardView extends GameEntityView {
updateZoneText(c);
updateDamage(c);
if (getBackup() == null && !c.isFaceDown() && c.hasBackSide()) {
if (getBackup() == null && !c.isFaceDown() && (c.hasBackSide()||c.isFlipCard()||c.isAdventureCard())) {
set(TrackableProperty.PaperCardBackup, c.getPaperCard());
}
@@ -1300,6 +1301,37 @@ public class CardView extends GameEntityView {
public boolean hasLandwalk() {
return get(TrackableProperty.HasLandwalk);
}
public boolean hasHasAftermath() {
return get(TrackableProperty.HasAftermath);
}
public boolean origProduceAnyMana() {
return get(TrackableProperty.OrigProduceAnyMana);
}
public boolean origProduceManaR() {
return get(TrackableProperty.OrigProduceManaR);
}
public boolean origProduceManaG() {
return get(TrackableProperty.OrigProduceManaG);
}
public boolean origProduceManaB() {
return get(TrackableProperty.OrigProduceManaB);
}
public boolean origProduceManaU() {
return get(TrackableProperty.OrigProduceManaU);
}
public boolean origProduceManaW() {
return get(TrackableProperty.OrigProduceManaW);
}
public boolean origProduceManaC() {
return get(TrackableProperty.OrigProduceManaC);
}
public int origCanProduceColoredMana() {
return get(TrackableProperty.CountOrigProduceColoredMana);
}
public int countBasicLandTypes() {
return get(TrackableProperty.CountBasicLandTypes);
}
public String getAbilityText() {
return get(TrackableProperty.AbilityText);
@@ -1333,6 +1365,7 @@ public class CardView extends GameEntityView {
set(TrackableProperty.HasInfect, c.hasKeyword(Keyword.INFECT, state));
set(TrackableProperty.HasStorm, c.hasKeyword(Keyword.STORM, state));
set(TrackableProperty.HasLandwalk, c.hasKeyword(Keyword.LANDWALK, state));
set(TrackableProperty.HasAftermath, c.hasKeyword(Keyword.AFTERMATH, state));
updateAbilityText(c, state);
//set protectionKey for Icons
set(TrackableProperty.ProtectionKey, c.getProtectionKey());
@@ -1340,6 +1373,84 @@ public class CardView extends GameEntityView {
set(TrackableProperty.HexproofKey, c.getHexproofKey());
//keywordkey
set(TrackableProperty.KeywordKey, c.getKeywordKey());
//update Trackable Mana Color for BG Colors
updateManaColorBG(state);
}
void updateManaColorBG(CardState state) {
boolean anyMana = false;
boolean rMana = false;
boolean gMana = false;
boolean bMana = false;
boolean uMana = false;
boolean wMana = false;
boolean cMana = false;
int count = 0;
int basicLandTypes = 0;
if (!state.getManaAbilities().isEmpty()) {
for (SpellAbility sa : state.getManaAbilities()) {
if (sa == null || sa.getManaPart() == null)
continue;
if (sa.getManaPart().isAnyMana()) {
anyMana = true;
}
switch (sa.getManaPart().getOrigProduced()) {
case "R":
if (!rMana) {
count += 1;
rMana = true;
}
break;
case "G":
if (!gMana) {
count += 1;
gMana = true;
}
break;
case "B":
if (!bMana) {
count += 1;
bMana = true;
}
break;
case "U":
if (!uMana) {
count += 1;
uMana = true;
}
break;
case "W":
if (!wMana) {
count += 1;
wMana = true;
}
break;
case "C":
if (!cMana) {
cMana = true;
}
break;
}
}
}
if (isForest())
basicLandTypes += 1;
if (isMountain())
basicLandTypes += 1;
if (isSwamp())
basicLandTypes += 1;
if (isPlains())
basicLandTypes += 1;
if (isIsland())
basicLandTypes += 1;
set(TrackableProperty.CountBasicLandTypes, basicLandTypes);
set(TrackableProperty.OrigProduceManaR, rMana);
set(TrackableProperty.OrigProduceManaG, gMana);
set(TrackableProperty.OrigProduceManaB, bMana);
set(TrackableProperty.OrigProduceManaU, uMana);
set(TrackableProperty.OrigProduceManaW, wMana);
set(TrackableProperty.OrigProduceManaC, cMana);
set(TrackableProperty.CountOrigProduceColoredMana, count);
set(TrackableProperty.OrigProduceAnyMana, anyMana);
}
public boolean isBasicLand() {
@@ -1375,6 +1486,12 @@ public class CardView extends GameEntityView {
public boolean isIsland() {
return getType().hasSubtype("Island");
}
public boolean isVehicle() {
return getType().hasSubtype("Vehicle");
}
public boolean isArtifact() {
return getType().isArtifact();
}
}
//special methods for updating card and player properties as needed and returning the new collection

View File

@@ -89,7 +89,6 @@ public class TokenCreateTable extends ForwardingTable<Player, Card, Integer> {
result += c.getValue();
}
return result;
}
}

View File

@@ -46,12 +46,14 @@ public enum Keyword {
CYCLING("Cycling", KeywordWithCost.class, false, "%s, Discard this card: Draw a card."), //Typecycling reminder text handled by Cycling class
DASH("Dash", KeywordWithCost.class, false, "You may cast this spell for its dash cost. If you do, it gains haste, and it's returned from the battlefield to its owner's hand at the beginning of the next end step."),
DEATHTOUCH("Deathtouch", SimpleKeyword.class, true, "Any amount of damage this deals to a creature is enough to destroy it."),
DECAYED("Decayed", SimpleKeyword.class, true, "This creature can't block. When it attacks, sacrifice it at end of combat."),
DEFENDER("Defender", SimpleKeyword.class, true, "This creature can't attack."),
DELVE("Delve", SimpleKeyword.class, true, "As an additional cost to cast this spell, you may exile any number of cards from your graveyard. Each card exiled this way reduces the cost to cast this spell by {1}."),
DEMONSTRATE("Demonstrate", SimpleKeyword.class, false, "When you cast this spell, you may copy it. If you do, choose an opponent to also copy it. Players may choose new targets for their copies."),
DETHRONE("Dethrone", SimpleKeyword.class, false, "Whenever this creature attacks the player with the most life or tied for the most life, put a +1/+1 counter on it."),
DEVOUR("Devour", KeywordWithAmount.class, false, "As this creature enters the battlefield, you may sacrifice any number of creatures. This creature enters the battlefield with {%d:+1/+1 counter} on it for each creature sacrificed this way."),
DEVOID("Devoid", SimpleKeyword.class, true, "This card has no color."),
DISTURB("Disturb", KeywordWithCost.class, false, "You may cast this card from your graveyard transformed for its disturb cost."),
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."),
@@ -77,7 +79,7 @@ public enum Keyword {
FIRST_STRIKE("First Strike", SimpleKeyword.class, true, "This creature deals combat damage before creatures without first strike."),
FLANKING("Flanking", SimpleKeyword.class, false, "Whenever this creature becomes blocked by a creature without flanking, the blocking creature gets -1/-1 until end of turn."),
FLASH("Flash", SimpleKeyword.class, true, "You may cast this spell any time you could cast an instant."),
FLASHBACK("Flashback", KeywordWithCost.class, false, "You may cast this card from your graveyard by paying %s rather than paying its mana cost. If you do, exile it as it resolves."),
FLASHBACK("Flashback", KeywordWithCost.class, false, "You may cast this card from your graveyard for its flashback cost. Then exile it."),
FLYING("Flying", SimpleKeyword.class, true, "This creature can't be blocked except by creatures with flying or reach."),
FORETELL("Foretell", KeywordWithCost.class, false, "During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost."),
FORTIFY("Fortify", KeywordWithCost.class, false, "%s: Attach to target land you control. Fortify only as a sorcery."),

View File

@@ -5,6 +5,7 @@ public enum AlternativeCost {
Bestow,
Cycling, // ActivatedAbility
Dash,
Disturb,
Emerge,
Escape,
Evoke,

View File

@@ -1370,6 +1370,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return isAlternativeCost(AlternativeCost.Dash);
}
public final boolean isDisturb() {
return isAlternativeCost(AlternativeCost.Disturb);
}
public final boolean isEscape() {
return isAlternativeCost(AlternativeCost.Escape);
}

View File

@@ -111,6 +111,18 @@ public enum TrackableProperty {
HasChangedColors(TrackableTypes.BooleanType),
ChangedTypes(TrackableTypes.StringMapType),
//check produce mana for BG
OrigProduceManaR(TrackableTypes.BooleanType),
OrigProduceManaG(TrackableTypes.BooleanType),
OrigProduceManaB(TrackableTypes.BooleanType),
OrigProduceManaU(TrackableTypes.BooleanType),
OrigProduceManaW(TrackableTypes.BooleanType),
OrigProduceManaC(TrackableTypes.BooleanType),
OrigProduceAnyMana(TrackableTypes.BooleanType),
CountOrigProduceColoredMana(TrackableTypes.IntegerType),
//number of basic landtypes
CountBasicLandTypes(TrackableTypes.IntegerType),
KeywordKey(TrackableTypes.StringType),
HasDeathtouch(TrackableTypes.BooleanType),
HasDevoid(TrackableTypes.BooleanType),
@@ -132,6 +144,7 @@ public enum TrackableProperty {
HasTrample(TrackableTypes.BooleanType),
HasVigilance(TrackableTypes.BooleanType),
HasLandwalk(TrackableTypes.BooleanType),
HasAftermath(TrackableTypes.BooleanType),
//protectionkey
ProtectionKey(TrackableTypes.StringType),
//hexproofkey

View File

@@ -472,6 +472,12 @@ public class Graphics {
batch.begin();
}
public void drawRectLines(float thickness, Color color, float x, float y, float w, float h) {
drawLine(thickness, color, x, y, x+w, y);
drawLine(thickness, color, x+thickness/2f, y+thickness/2f, x+thickness/2f, y+h-thickness/2f);
drawLine(thickness, color, x, y+h, x+w, y+h);
drawLine(thickness, color, x+w-thickness/2f, y+thickness/2f, x+w-thickness/2f, y+h-thickness/2f);
}
public void fillRect(FSkinColor skinColor, float x, float y, float w, float h) {
fillRect(skinColor.getColor(), x, y, w, h);
@@ -850,6 +856,18 @@ public class Graphics {
//use nifty trick with multiple text renders to draw outlined text
public void drawOutlinedText(String text, FSkinFont skinFont, Color textColor, Color outlineColor, float x, float y, float w, float h, boolean wrap, int horzAlignment, boolean centerVertically) {
drawOutlinedText(text, skinFont, textColor, outlineColor, x, y, w, h, wrap, horzAlignment, centerVertically, false);
}
public void drawOutlinedText(String text, FSkinFont skinFont, Color textColor, Color outlineColor, float x, float y, float w, float h, boolean wrap, int horzAlignment, boolean centerVertically, boolean shadow) {
if (shadow) {
float oldAlpha = alphaComposite;
alphaComposite = 0.4f;
drawText(text, skinFont, outlineColor, x - 1.5f, y + 1.5f, w, h, wrap, horzAlignment, centerVertically);
drawText(text, skinFont, outlineColor, x + 1.5f, y + 1.5f, w, h, wrap, horzAlignment, centerVertically);
drawText(text, skinFont, outlineColor, x + 1.5f, y - 1.5f, w, h, wrap, horzAlignment, centerVertically);
drawText(text, skinFont, outlineColor, x - 1.5f, y - 1.5f, w, h, wrap, horzAlignment, centerVertically);
alphaComposite = oldAlpha;
}
drawText(text, skinFont, outlineColor, x - 1, y, w, h, wrap, horzAlignment, centerVertically);
drawText(text, skinFont, outlineColor, x, y - 1, w, h, wrap, horzAlignment, centerVertically);
drawText(text, skinFont, outlineColor, x - 1, y - 1, w, h, wrap, horzAlignment, centerVertically);

View File

@@ -4,6 +4,7 @@ import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.math.Matrix4;
@@ -44,8 +45,30 @@ public abstract class FBufferedImage extends FImageComplex {
return 0;
}
@Override
public TextureRegion getTextureRegion() {
return new TextureRegion(checkFrameBuffer().getColorBufferTexture());
}
@Override
public Texture getTexture() {
return checkFrameBuffer().getColorBufferTexture();
}
public void clear() {
final FrameBuffer fb = frameBuffer;
if (fb != null) {
frameBuffer = null;
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override
public void run() {
fb.dispose(); //must be disposed on EDT thread
}
});
}
}
public FrameBuffer checkFrameBuffer() {
if (frameBuffer == null) {
Gdx.gl.glDisable(GL20.GL_SCISSOR_TEST); //prevent buffered image being clipped
@@ -69,20 +92,7 @@ public abstract class FBufferedImage extends FImageComplex {
Gdx.gl.glEnable(GL20.GL_SCISSOR_TEST);
}
return frameBuffer.getColorBufferTexture();
}
public void clear() {
final FrameBuffer fb = frameBuffer;
if (fb != null) {
frameBuffer = null;
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override
public void run() {
fb.dispose(); //must be disposed on EDT thread
}
});
}
return frameBuffer;
}
protected abstract void draw(Graphics g, float w, float h);

View File

@@ -3,6 +3,7 @@ package forge.assets;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import forge.Graphics;
//Special wrapper for a texture to be loaded later when it's needed
@@ -32,6 +33,14 @@ public class FDelayLoadImage extends FImageComplex {
return texture;
}
@Override
public TextureRegion getTextureRegion() {
if (texture == null) {
texture = new Texture(Gdx.files.absolute(filename));
}
return new TextureRegion(texture);
}
@Override
public int getRegionX() {
return 0;

View File

@@ -1,9 +1,11 @@
package forge.assets;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
public abstract class FImageComplex implements FImage {
public abstract Texture getTexture();
public abstract TextureRegion getTextureRegion();
public abstract int getRegionX();
public abstract int getRegionY();
}

View File

@@ -2,6 +2,7 @@ package forge.assets;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import forge.Graphics;
public class FRotatedImage extends FImageComplex {
@@ -33,6 +34,11 @@ public class FRotatedImage extends FImageComplex {
return texture;
}
@Override
public TextureRegion getTextureRegion() {
return new TextureRegion(texture);
}
@Override
public int getRegionX() {
return srcX;

View File

@@ -38,6 +38,7 @@ public class FSkin {
private static String preferredName;
private static boolean loaded = false;
public static Texture hdLogo = null;
public static Texture overlay_alpha = null;
public static void changeSkin(final String skinName) {
final ForgePreferences prefs = FModel.getPreferences();
@@ -124,10 +125,27 @@ public class FSkin {
FSkinTexture.BG_TEXTURE.load(); //load background texture early for splash screen
//load theme logo while changing skins
final FileHandle theme_logo = getSkinFile("hd_logo.png");
if (theme_logo.exists()) {
Texture txOverlay = new Texture(theme_logo, true);
txOverlay.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
hdLogo = txOverlay;
} else {
hdLogo = null;
}
final FileHandle duals_overlay = getDefaultSkinFile("overlay_alpha.png");
if (duals_overlay.exists()) {
Texture txAlphaLines = new Texture(duals_overlay, true);
txAlphaLines.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
overlay_alpha = txAlphaLines;
} else {
overlay_alpha = null;
}
if (splashScreen != null) {
final FileHandle f = getSkinFile("bg_splash.png");
final FileHandle f2 = getSkinFile("bg_splash_hd.png"); //HD Splashscreen
final FileHandle f3 = getSkinFile("hd_logo.png");
if (!f.exists()) {
if (!skinName.equals("default")) {
@@ -149,14 +167,6 @@ public class FSkin {
splashScreen.setBackground(new TextureRegion(txSplash, 0, 0, w, h - 100));
}
if (f3.exists()) {
Texture txOverlay = new Texture(f3, true);
txOverlay.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
hdLogo = txOverlay;
} else {
hdLogo = null;
}
Pixmap pxSplash = new Pixmap(f);
FProgressBar.BACK_COLOR = new Color(pxSplash.getPixel(25, h - 75));
FProgressBar.FORE_COLOR = new Color(pxSplash.getPixel(75, h - 75));
@@ -217,9 +227,12 @@ public class FSkin {
final FileHandle f11 = getSkinFile(ForgeConstants.SPRITE_BUTTONS_FILE);
final FileHandle f12 = getSkinFile(ForgeConstants.SPRITE_START_FILE);
final FileHandle f13 = getDefaultSkinFile(ForgeConstants.SPRITE_DECKBOX_FILE);
/*TODO Themeable
final FileHandle f14 = getDefaultSkinFile(ForgeConstants.SPRITE_SETLOGO_FILE);
final FileHandle f15 = getSkinFile(ForgeConstants.SPRITE_SETLOGO_FILE);
final FileHandle f16 = getDefaultSkinFile(ForgeConstants.SPRITE_WATERMARK_FILE);
*/
try {
textures.put(f1.path(), new Texture(f1));

View File

@@ -115,6 +115,28 @@ public enum FSkinImage implements FImage {
WATERMARK_W (FSkinProp.IMG_WATERMARK_W, SourceFile.WATERMARKS),
WATERMARK_C (FSkinProp.IMG_WATERMARK_C, SourceFile.WATERMARKS),
//CardBG
CARDBG_A (FSkinProp.IMG_CARDBG_A, SourceFile.CARDBG),
CARDBG_B (FSkinProp.IMG_CARDBG_B, SourceFile.CARDBG),
CARDBG_BG (FSkinProp.IMG_CARDBG_BG, SourceFile.CARDBG),
CARDBG_BR (FSkinProp.IMG_CARDBG_BR, SourceFile.CARDBG),
CARDBG_C (FSkinProp.IMG_CARDBG_C, SourceFile.CARDBG),
CARDBG_G (FSkinProp.IMG_CARDBG_G, SourceFile.CARDBG),
CARDBG_L (FSkinProp.IMG_CARDBG_L, SourceFile.CARDBG),
CARDBG_M (FSkinProp.IMG_CARDBG_M, SourceFile.CARDBG),
CARDBG_R (FSkinProp.IMG_CARDBG_R, SourceFile.CARDBG),
CARDBG_RG (FSkinProp.IMG_CARDBG_RG, SourceFile.CARDBG),
CARDBG_U (FSkinProp.IMG_CARDBG_U, SourceFile.CARDBG),
CARDBG_UB (FSkinProp.IMG_CARDBG_UB, SourceFile.CARDBG),
CARDBG_UG (FSkinProp.IMG_CARDBG_UG, SourceFile.CARDBG),
CARDBG_UR (FSkinProp.IMG_CARDBG_UR, SourceFile.CARDBG),
CARDBG_V (FSkinProp.IMG_CARDBG_V, SourceFile.CARDBG),
CARDBG_W (FSkinProp.IMG_CARDBG_W, SourceFile.CARDBG),
CARDBG_WB (FSkinProp.IMG_CARDBG_WB, SourceFile.CARDBG),
CARDBG_WG (FSkinProp.IMG_CARDBG_WG, SourceFile.CARDBG),
CARDBG_WR (FSkinProp.IMG_CARDBG_WR, SourceFile.CARDBG),
CARDBG_WU (FSkinProp.IMG_CARDBG_WU, SourceFile.CARDBG),
//Gameplay
TAP (FSkinProp.IMG_TAP, SourceFile.MANAICONS),
UNTAP (FSkinProp.IMG_UNTAP, SourceFile.MANAICONS),
@@ -439,6 +461,7 @@ public enum FSkinImage implements FImage {
MANAICONS(ForgeConstants.SPRITE_MANAICONS_FILE),
SETLOGOS(ForgeConstants.SPRITE_SETLOGO_FILE),
WATERMARKS(ForgeConstants.SPRITE_WATERMARK_FILE),
CARDBG(ForgeConstants.SPRITE_CARDBG_FILE),
PLANAR_CONQUEST(ForgeConstants.SPRITE_PLANAR_CONQUEST_FILE);
private final String filename;

View File

@@ -2,6 +2,7 @@ package forge.assets;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import forge.Graphics;
public class FTextureImage extends FImageComplex {
@@ -26,6 +27,11 @@ public class FTextureImage extends FImageComplex {
return texture;
}
@Override
public TextureRegion getTextureRegion() {
return new TextureRegion(texture);
}
@Override
public int getRegionX() {
return 0;

View File

@@ -27,6 +27,11 @@ public class FTextureRegionImage extends FImageComplex {
return textureRegion.getTexture();
}
@Override
public TextureRegion getTextureRegion() {
return textureRegion;
}
@Override
public int getRegionX() {
return textureRegion.getRegionX();

View File

@@ -32,7 +32,7 @@ public class CardAvatarImage implements FImage {
@Override
public void draw(Graphics g, float x, float y, float w, float h) {
//force to get the avatar since the the cardartcache & loadingcache is always cleared on screen change or the battle bar will display black
image = CardRenderer.getCardArt(imageKey, false, false, false);
image = CardRenderer.getCardArt(imageKey, false, false, false, false, false, false, false, false, true);
if (image == null) {
return; //can't draw anything if can't be loaded yet
}

View File

@@ -38,14 +38,14 @@ public class CardImage implements FImage {
image = ImageCache.getImage(card);
if (image == null) {
if (!Forge.enableUIMask.equals("Off")) //render this if mask is still loading
CardImageRenderer.drawCardImage(g, CardView.getCardForUi(card), false, x, y, w, h, CardStackPosition.Top);
CardImageRenderer.drawCardImage(g, CardView.getCardForUi(card), false, x, y, w, h, CardStackPosition.Top, Forge.enableUIMask.equals("Art"), true);
return; //can't draw anything if can't be loaded yet
}
}
if (image == ImageCache.defaultImage) {
CardImageRenderer.drawCardImage(g, CardView.getCardForUi(card), false, x, y, w, h, CardStackPosition.Top);
if (image == ImageCache.defaultImage || Forge.enableUIMask.equals("Art")) {
CardImageRenderer.drawCardImage(g, CardView.getCardForUi(card), false, x, y, w, h, CardStackPosition.Top, true, true);
}
else {
if (Forge.enableUIMask.equals("Full")) {

View File

@@ -6,6 +6,10 @@ import static forge.card.CardRenderer.isModernFrame;
import java.util.ArrayList;
import java.util.List;
import forge.ImageKeys;
import forge.assets.*;
import forge.item.PaperCard;
import forge.util.ImageUtil;
import org.apache.commons.lang3.StringUtils;
import com.badlogic.gdx.graphics.Color;
@@ -15,15 +19,6 @@ import com.google.common.collect.ImmutableList;
import forge.Forge;
import forge.Graphics;
import forge.assets.FBufferedImage;
import forge.assets.FImage;
import forge.assets.FSkin;
import forge.assets.FSkinColor;
import forge.assets.FSkinFont;
import forge.assets.FSkinImage;
import forge.assets.FSkinTexture;
import forge.assets.ImageCache;
import forge.assets.TextRenderer;
import forge.card.CardRenderer.CardStackPosition;
import forge.card.mana.ManaCost;
import forge.game.GameView;
@@ -48,6 +43,10 @@ public class CardImageRenderer {
private static float prevImageWidth, prevImageHeight;
private static final float BLACK_BORDER_THICKNESS_RATIO = 0.021f;
private static Color fromDetailColor(DetailColors detailColor) {
return FSkinColor.fromRGB(detailColor.r, detailColor.g, detailColor.b);
}
public static void forceStaticFieldUpdate() {
//force static fields to be updated the next time a card image is rendered
prevImageWidth = 0;
@@ -78,13 +77,17 @@ public class CardImageRenderer {
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);
FImage sleeves = MatchController.getPlayerSleeve(card.getOwner());
if (sleeves != null)
g.drawImage(sleeves, x, y, w, h);
else
drawArt(g, x, y, w, h);
drawArt(null, g, x, y, w, h, false, true);
}
public static void drawCardImage(Graphics g, CardView card, boolean altState, float x, float y, float w, float h, CardStackPosition pos) {
public static void drawCardImage(Graphics g, CardView card, boolean altState, float x, float y, float w, float h, CardStackPosition pos, boolean useCardBGTexture, boolean showArtist) {
drawCardImage(g, card, altState, x, y, w, h, pos, useCardBGTexture, false, false, showArtist);
}
public static void drawCardImage(Graphics g, CardView card, boolean altState, float x, float y, float w, float h, CardStackPosition pos, boolean useCardBGTexture, boolean noText, boolean isChoiceList, boolean showArtist) {
updateStaticFields(w, h);
float blackBorderThickness = w * BLACK_BORDER_THICKNESS_RATIO;
@@ -94,8 +97,16 @@ public class CardImageRenderer {
w -= 2 * blackBorderThickness;
h -= 2 * blackBorderThickness;
final CardStateView state = card.getState(altState);
CardStateView state = altState ? card.getAlternateState() : isChoiceList && card.isSplitCard() ? card.getLeftSplitState() : card.getCurrentState();
final boolean isFaceDown = card.isFaceDown();
final boolean canShow = MatchController.instance.mayView(card);
//override
if (isFaceDown && altState && card.isSplitCard())
state = card.getLeftSplitState();
boolean isSaga = state.getType().hasSubtype("Saga");
boolean isClass = state.getType().hasSubtype("Class");
boolean isDungeon = state.getType().isDungeon();
boolean drawDungeon = isDungeon && CardRenderer.getCardArt(card) != null;
if (!canShow) {
drawFaceDownCard(card, g, x, y, w, h);
@@ -104,14 +115,12 @@ public class CardImageRenderer {
//determine colors for borders
final List<DetailColors> borderColors;
final boolean isFaceDown = card.isFaceDown();
if (isFaceDown) {
borderColors = ImmutableList.of(DetailColors.FACE_DOWN);
}
else {
borderColors = !altState ? ImmutableList.of(DetailColors.FACE_DOWN) : !useCardBGTexture ? ImmutableList.of(DetailColors.FACE_DOWN) : CardDetailUtil.getBorderColors(state, canShow);
} else {
borderColors = CardDetailUtil.getBorderColors(state, canShow);
}
Color[] colors = fillColorBackground(g, borderColors, x, y, w, h);
Color[] colors = useCardBGTexture ? drawCardBackgroundTexture(state, g, borderColors, x, y, w, h) : fillColorBackground(g, borderColors, x, y, w, h);
float artInset = blackBorderThickness * 0.5f;
float outerBorderThickness = 2 * blackBorderThickness - artInset;
@@ -122,7 +131,7 @@ public class CardImageRenderer {
//draw header containing name and mana cost
Color[] headerColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.NAME_BOX_TINT);
drawHeader(g, card, state, headerColors, x, y, w, headerHeight);
drawHeader(g, card, state, headerColors, x, y, w, headerHeight, isFaceDown && !altState, false);
if (pos == CardStackPosition.BehindVert) { return; } //remaining rendering not needed if card is behind another card in a vertical stack
boolean onTop = (pos == CardStackPosition.Top);
@@ -134,14 +143,16 @@ public class CardImageRenderer {
float typeBoxHeight = 2 * TYPE_FONT.getCapHeight();
float ptBoxHeight = 0;
float textBoxHeight = h - headerHeight - artHeight - typeBoxHeight - outerBorderThickness - artInset;
if (state.isCreature() || state.isPlaneswalker() || state.getType().hasSubtype("Vehicle")) {
//if P/T box needed, make room for it
ptBoxHeight = 2 * PT_FONT.getCapHeight();
textBoxHeight -= ptBoxHeight;
}
else {
textBoxHeight -= 2 * artInset;
}
//space for artist
textBoxHeight -= 2 * PT_FONT.getCapHeight();
PaperCard paperCard = ImageUtil.getPaperCardFromImageKey(state.getImageKey());
String artist = "WOTC";
if (paperCard != null && !paperCard.getArtist().isEmpty())
artist = paperCard.getArtist();
float minTextBoxHeight = 2 * headerHeight;
if (textBoxHeight < minTextBoxHeight) {
if (textBoxHeight < minTextBoxHeight) {
@@ -156,52 +167,102 @@ public class CardImageRenderer {
//draw art box with Forge icon
if (artHeight > 0) {
drawArt(g, x + artInset, y, artWidth, artHeight);
if (isSaga)
drawArt(card, g, x + artInset+(artWidth/2), y, artWidth/2, artHeight+textBoxHeight, altState, isFaceDown);
else if (isClass)
drawArt(card, g, x + artInset, y, artWidth/2, artHeight+textBoxHeight, altState, isFaceDown);
else if (isDungeon) {
if (drawDungeon) {
drawArt(card, g, x + artInset, y, artWidth, artHeight+textBoxHeight, altState, isFaceDown);
y += textBoxHeight;
}
}
else
drawArt(card, g, x + artInset, y, artWidth, artHeight, altState, isFaceDown);
y += artHeight;
}
if (isSaga) {
//draw text box
Color[] textBoxColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.TEXT_BOX_TINT);
drawTextBox(g, card, state, textBoxColors, x + artInset, y-artHeight, (w - 2 * artInset)/2, textBoxHeight+artHeight, onTop, useCardBGTexture, noText, altState, isFaceDown, canShow, isChoiceList);
y += textBoxHeight;
//draw type line
drawTypeLine(g, card, state, canShow, headerColors, x, y, w, typeBoxHeight);
drawTypeLine(g, state, canShow, headerColors, x, y, w, typeBoxHeight, noText, false, false);
y += typeBoxHeight;
} else if (isClass) {
//draw text box
Color[] textBoxColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.TEXT_BOX_TINT);
drawTextBox(g, card, state, textBoxColors, x + artInset+(artWidth/2), y-artHeight, (w - 2 * artInset)/2, textBoxHeight+artHeight, onTop, useCardBGTexture, noText, altState, isFaceDown, canShow, isChoiceList);
y += textBoxHeight;
//draw type line
drawTypeLine(g, state, canShow, headerColors, x, y, w, typeBoxHeight, noText, false, false);
y += typeBoxHeight;
} else if (isDungeon) {
if (!drawDungeon) {
//draw textbox
Color[] textBoxColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.TEXT_BOX_TINT);
drawTextBox(g, card, state, textBoxColors, x + artInset, y-artHeight, (w - 2 * artInset), textBoxHeight+artHeight, onTop, useCardBGTexture, noText, altState, isFaceDown, canShow, isChoiceList);
y += textBoxHeight;
}
drawTypeLine(g, state, canShow, headerColors, x, y, w, typeBoxHeight, noText, false, false);
y += typeBoxHeight;
} else {
//draw type line
drawTypeLine(g, state, canShow, headerColors, x, y, w, typeBoxHeight, noText, false, false);
y += typeBoxHeight;
//draw text box
Color[] textBoxColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.TEXT_BOX_TINT);
drawTextBox(g, card, state, textBoxColors, x + artInset, y, w - 2 * artInset, textBoxHeight, onTop);
drawTextBox(g, card, state, textBoxColors, x + artInset, y, w - 2 * artInset, textBoxHeight, onTop, useCardBGTexture, noText, altState, isFaceDown, canShow, isChoiceList);
y += textBoxHeight;
}
//draw P/T box
if (onTop && ptBoxHeight > 0) {
//only needed if on top since otherwise P/T will be hidden
Color[] ptColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.PT_BOX_TINT);
drawPtBox(g, card, state, ptColors, x, y - 2 * artInset, w, ptBoxHeight);
drawPtBox(g, card, state, ptColors, x, y - 2 * artInset, w, ptBoxHeight, noText);
}
//draw artist
if (showArtist)
g.drawOutlinedText(artist, TEXT_FONT, Color.WHITE, Color.DARK_GRAY, x+(TYPE_FONT.getCapHeight()/2), y+(TYPE_FONT.getCapHeight()/2), w, h, false, Align.left, false);
}
private static void drawHeader(Graphics g, CardView card, CardStateView state, Color[] colors, float x, float y, float w, float h) {
private static void drawHeader(Graphics g, CardView card, CardStateView state, Color[] colors, float x, float y, float w, float h, boolean noText, boolean isAdventure) {
float oldAlpha = g.getfloatAlphaComposite();
if (isAdventure)
g.setAlphaComposite(0.8f);
fillColorBackground(g, colors, x, y, w, h);
g.setAlphaComposite(oldAlpha);
g.drawRect(BORDER_THICKNESS, Color.BLACK, x, y, w, h);
float padding = h / 8;
//draw mana cost for card
float manaCostWidth = 0;
float manaSymbolSize = isAdventure ? MANA_SYMBOL_SIZE * 0.75f : MANA_SYMBOL_SIZE;
if (!noText) {
//draw mana cost for card
ManaCost mainManaCost = state.getManaCost();
if (card.isSplitCard() && card.getAlternateState() != null) {
//handle rendering both parts of split card
mainManaCost = card.getLeftSplitState().getManaCost();
ManaCost otherManaCost = card.getAlternateState().getManaCost();
manaCostWidth = CardFaceSymbols.getWidth(otherManaCost, MANA_SYMBOL_SIZE) + HEADER_PADDING;
CardFaceSymbols.drawManaCost(g, otherManaCost, x + w - manaCostWidth, y + (h - MANA_SYMBOL_SIZE) / 2, MANA_SYMBOL_SIZE);
ManaCost otherManaCost = card.getRightSplitState().getManaCost();
manaCostWidth = CardFaceSymbols.getWidth(otherManaCost, manaSymbolSize) + HEADER_PADDING;
CardFaceSymbols.drawManaCost(g, otherManaCost, x + w - manaCostWidth, y + (h - manaSymbolSize) / 2, manaSymbolSize);
//draw "//" between two parts of mana cost
manaCostWidth += NAME_FONT.getBounds("//").width + HEADER_PADDING;
g.drawText("//", NAME_FONT, Color.BLACK, x + w - manaCostWidth, y, w, h, false, Align.left, true);
}
manaCostWidth += CardFaceSymbols.getWidth(mainManaCost, MANA_SYMBOL_SIZE) + HEADER_PADDING;
CardFaceSymbols.drawManaCost(g, mainManaCost, x + w - manaCostWidth, y + (h - MANA_SYMBOL_SIZE) / 2, MANA_SYMBOL_SIZE);
manaCostWidth += CardFaceSymbols.getWidth(mainManaCost, manaSymbolSize) + HEADER_PADDING;
CardFaceSymbols.drawManaCost(g, mainManaCost, x + w - manaCostWidth, y + (h - manaSymbolSize) / 2, manaSymbolSize);
}
//draw name for card
x += padding;
w -= 2 * padding;
if (!noText)
g.drawText(CardTranslation.getTranslatedName(state.getName()), NAME_FONT, Color.BLACK, x, y, w - manaCostWidth - padding, h, false, Align.left, true);
}
@@ -221,18 +282,106 @@ public class CardImageRenderer {
};
}
private static void drawArt(Graphics g, float x, float y, float w, float h) {
private static void drawArt(CardView cv, Graphics g, float x, float y, float w, float h, boolean altState, boolean isFaceDown) {
if (cv == null) {
if (isFaceDown) {
Texture cardBack = ImageCache.getImage(ImageKeys.getTokenKey(ImageKeys.HIDDEN_CARD), false);
if (cardBack != null) {
g.drawImage(cardBack, x, y, w, h);
return;
}
}
//fallback
g.drawImage(forgeArt, x, y, w, h);
g.drawRect(BORDER_THICKNESS, Color.BLACK, x, y, w, h);
return;
}
private static void drawTypeLine(Graphics g, CardView card, CardStateView state, boolean canShow, Color[] colors, float x, float y, float w, float h) {
if (Forge.enableUIMask.equals("Art")) {
FImageComplex cardArt = CardRenderer.getCardArt(cv);
FImageComplex altArt = cardArt;
boolean isHidden = (cv.getCurrentState().getImageKey().equals(ImageKeys.getTokenKey(ImageKeys.HIDDEN_CARD))
|| cv.getCurrentState().getImageKey().equals(ImageKeys.getTokenKey(ImageKeys.FORETELL_IMAGE)));
if (cardArt != null) {
if (isHidden && !altState) {
g.drawImage(forgeArt, x, y, w, h);
} else if (cv.getCurrentState().getImageKey().equals(ImageKeys.getTokenKey(ImageKeys.MANIFEST_IMAGE)) && !altState) {
altArt = CardRenderer.getAlternateCardArt(ImageKeys.getTokenKey(ImageKeys.MANIFEST_IMAGE), false);
g.drawImage(altArt, x, y, w, h);
} else if (cv.getCurrentState().getImageKey().equals(ImageKeys.getTokenKey(ImageKeys.MORPH_IMAGE)) && !altState) {
altArt = CardRenderer.getAlternateCardArt(ImageKeys.getTokenKey(ImageKeys.MORPH_IMAGE), false);
g.drawImage(altArt, x, y, w, h);
} else {
if (cv.hasAlternateState()) {
if (altState) {
if (cv.getAlternateState().isPlaneswalker())
altArt = CardRenderer.getAlternateCardArt(cv.getAlternateState().getImageKey(), cv.getAlternateState().isPlaneswalker());
else {
altArt = CardRenderer.getCardArt(cv.getAlternateState().getImageKey(), cv.isSplitCard(), cv.getAlternateState().isPlane() || cv.getAlternateState().isPhenomenon(), cv.getText().contains("Aftermath"),
cv.getAlternateState().getType().hasSubtype("Saga"), cv.getAlternateState().getType().hasSubtype("Class"), cv.getAlternateState().getType().isDungeon(), cv.isFlipCard(), cv.getAlternateState().isPlaneswalker(), CardRenderer.isModernFrame(cv));
}
}
}
if (cv.isSplitCard()) {
drawSplitCard(cv, altArt, g, x, y, w, h, altState, isFaceDown);
} else if (cv.isFlipCard()) {
drawFlipCard(isFaceDown ? altArt : cardArt, g, x, y, w, h, altState);
} else {
g.drawImage(altArt, x, y, w, h);
}
}
} else {
g.drawImage(forgeArt, x, y, w, h);
}
} else {
g.drawImage(forgeArt, x, y, w, h);
}
g.drawRect(BORDER_THICKNESS, Color.BLACK, x, y, w, h);
}
private static void drawSplitCard(CardView card, FImageComplex cardArt, Graphics g, float x, float y, float w, float h, boolean altState, boolean isFaceDown) {
CardView alt = card.getBackup();
if (alt == null)
alt = card.getAlternateState().getCard();
CardView cv = altState && isFaceDown ? alt : card;
boolean isAftermath = altState ? cv.getAlternateState().hasHasAftermath(): cv.getRightSplitState().hasHasAftermath();
if (!isAftermath) {
CardEdition ed = FModel.getMagicDb().getEditions().get(cv.getCurrentState().getSetCode());
boolean isOldFrame = ed != null && !ed.isModern();
float modH = isOldFrame ? cardArt.getHeight()/12f : 0f;
float modW = !isOldFrame ? cardArt.getWidth()/12f : 0f;
float modW2 = !isOldFrame ? cardArt.getWidth()/6f : 0f;
float srcY = cardArt.getHeight() * 13f / 354f;
float srcHeight = cardArt.getHeight() * 190f / 354f;
float dh = srcHeight * (1 - cardArt.getWidth() / srcHeight / CardRenderer.CARD_ART_RATIO);
srcHeight -= dh;
srcY += dh / 2;
g.drawRotatedImage(cardArt.getTexture(), x, y, h+modH, w / 2, x + w / 2, y + w / 2, cardArt.getRegionX()+(int)modW, (int)srcY, (int)(cardArt.getWidth()-modW2), (int)srcHeight, -90);
g.drawRotatedImage(cardArt.getTexture(), x, y + w / 2, h+modH, w / 2, x + w / 2, y + w / 2, cardArt.getRegionX()+(int)modW, (int)cardArt.getHeight() - (int)(srcY + srcHeight), (int)(cardArt.getWidth()-modW2), (int)srcHeight, -90);
g.drawLine(BORDER_THICKNESS, Color.BLACK, x+w/2, y, x+w/2, y+h);
} else {
FImageComplex secondArt = CardRenderer.getAftermathSecondCardArt(cv.getCurrentState().getImageKey());
g.drawRotatedImage(cardArt.getTexture(), x, y, w, h / 2, x + w, y + h / 2, cardArt.getRegionX(), cardArt.getRegionY(), (int)cardArt.getWidth(), (int)cardArt.getHeight() /2, 0);
g.drawRotatedImage(secondArt.getTexture(), x - h / 2 , y + h / 2, h /2, w, x, y + h / 2, secondArt.getRegionX(), secondArt.getRegionY(), (int)secondArt.getWidth(), (int)secondArt.getHeight(), 90);
g.drawLine(BORDER_THICKNESS, Color.BLACK, x, y+h/2, x+w, y+h/2);
}
}
private static void drawFlipCard(FImageComplex cardArt, Graphics g, float x, float y, float w, float h, boolean altState) {
if (altState)
g.drawRotatedImage(cardArt.getTextureRegion(), x, y, w, h, x + w / 2, y + h / 2, 180);
else
g.drawImage(cardArt, x, y, w, h);
}
private static void drawTypeLine(Graphics g, CardStateView state, boolean canShow, Color[] colors, float x, float y, float w, float h, boolean noText, boolean noRarity, boolean isAdventure) {
float oldAlpha = g.getfloatAlphaComposite();
if (isAdventure)
g.setAlphaComposite(0.6f);
fillColorBackground(g, colors, x, y, w, h);
g.setAlphaComposite(oldAlpha);
g.drawRect(BORDER_THICKNESS, Color.BLACK, x, y, w, h);
float padding = h / 8;
//draw square icon for rarity
if (!noRarity) {
float iconSize = h * 0.9f;
float iconPadding = (h - iconSize) / 2;
w -= iconSize + iconPadding * 2;
@@ -250,8 +399,11 @@ public class CardImageRenderer {
} else {
g.drawImage(FSkinImage.SET_COMMON, x + w + iconPadding, y + (h - iconSize) / 2, iconSize, iconSize);
}
}
//draw type
if (noText)
return;
x += padding;
g.drawText(CardDetailUtil.formatCardType(state, canShow), TYPE_FONT, Color.BLACK, x, y, w, h, false, Align.left, true);
}
@@ -259,7 +411,28 @@ public class CardImageRenderer {
//use text renderer to handle mana symbols and reminder text
private static final TextRenderer cardTextRenderer = new TextRenderer(true);
private static void drawTextBox(Graphics g, CardView card, CardStateView state, Color[] colors, float x, float y, float w, float h, boolean onTop) {
private static void drawTextBox(Graphics g, CardView card, CardStateView state, Color[] colors, float x, float y, float w, float h, boolean onTop, boolean useCardBGTexture, boolean noText, boolean altstate, boolean isFacedown, boolean canShow, boolean isChoiceList) {
if (card.isAdventureCard()) {
if ((isFacedown && !altstate) || card.getZone() == ZoneType.Stack || isChoiceList || altstate) {
setTextBox(g, card, state, colors, x, y, w, h, onTop, useCardBGTexture, noText, 0f, 0f, false, altstate, isFacedown);
} else {
//left
//float headerHeight = Math.max(MANA_SYMBOL_SIZE + 2 * HEADER_PADDING, 2 * TYPE_FONT.getCapHeight()) + 2;
float typeBoxHeight = 2 * TYPE_FONT.getCapHeight();
drawHeader(g, card, card.getState(true), colors, x, y, w - (w / 2), typeBoxHeight, noText, true);
drawTypeLine(g, card.getState(true), canShow, colors, x, y + typeBoxHeight, w - (w / 2), typeBoxHeight, noText, true, true);
float mod = (typeBoxHeight + typeBoxHeight);
setTextBox(g, card, state, colors, x, y + mod, w - (w / 2), h - mod, onTop, useCardBGTexture, noText, typeBoxHeight, typeBoxHeight, true, altstate, isFacedown);
//right
setTextBox(g, card, state, colors, x + w / 2, y, w - (w / 2), h, onTop, useCardBGTexture, noText, 0f, 0f, false, altstate, isFacedown);
}
} else {
setTextBox(g, card, state, colors, x, y, w, h, onTop, useCardBGTexture, noText, 0f, 0f, false, altstate, isFacedown);
}
}
private static void setTextBox(Graphics g, CardView card, CardStateView state, Color[] colors, float x, float y, float w, float h, boolean onTop, boolean useCardBGTexture, boolean noText, float adventureHeaderHeight, float adventureTypeHeight, boolean drawAdventure, boolean altstate, boolean isFaceDown) {
boolean fakeDuals = false;
//update land bg colors
if (state.isLand()) {
DetailColors modColors = DetailColors.WHITE;
if (state.isBasicLand()) {
@@ -273,53 +446,165 @@ public class CardImageRenderer {
modColors = DetailColors.BLACK;
else if (state.isPlains())
modColors = DetailColors.LAND;
}
Color bgColor = FSkinColor.fromRGB(modColors.r, modColors.g, modColors.b);
bgColor = FSkinColor.tintColor(Color.WHITE, bgColor, CardRenderer.NAME_BOX_TINT);
g.fillRect(bgColor, x, y, w, h);
} if (state.origCanProduceColoredMana() == 2) {
//dual colors
Color[] colorPairs = new Color[2];
//init Color
colorPairs[0] = fromDetailColor(DetailColors.WHITE);
colorPairs[1] = fromDetailColor(DetailColors.WHITE);
//override
if (state.origProduceAnyMana()) {
colorPairs[0] = fromDetailColor(DetailColors.MULTICOLOR);
colorPairs[1] = fromDetailColor(DetailColors.MULTICOLOR);
} else {
fakeDuals = true;
if (state.origProduceManaW() && state.origProduceManaU()) {
colorPairs[0] = fromDetailColor(DetailColors.LAND);
colorPairs[1] = fromDetailColor(DetailColors.BLUE);
} else if (state.origProduceManaW() && state.origProduceManaB()) {
colorPairs[0] = fromDetailColor(DetailColors.LAND);
colorPairs[1] = fromDetailColor(DetailColors.BLACK);
} else if (state.origProduceManaW() && state.origProduceManaR()) {
colorPairs[0] = fromDetailColor(DetailColors.LAND);
colorPairs[1] = fromDetailColor(DetailColors.RED);
} else if (state.origProduceManaW() && state.origProduceManaG()) {
colorPairs[0] = fromDetailColor(DetailColors.LAND);
colorPairs[1] = fromDetailColor(DetailColors.GREEN);
} else if (state.origProduceManaU() && state.origProduceManaB()) {
colorPairs[0] = fromDetailColor(DetailColors.BLUE);
colorPairs[1] = fromDetailColor(DetailColors.BLACK);
} else if (state.origProduceManaU() && state.origProduceManaR()) {
colorPairs[0] = fromDetailColor(DetailColors.BLUE);
colorPairs[1] = fromDetailColor(DetailColors.RED);
} else if (state.origProduceManaU() && state.origProduceManaG()) {
colorPairs[0] = fromDetailColor(DetailColors.BLUE);
colorPairs[1] = fromDetailColor(DetailColors.GREEN);
} else if (state.origProduceManaB() && state.origProduceManaR()) {
colorPairs[0] = fromDetailColor(DetailColors.BLACK);
colorPairs[1] = fromDetailColor(DetailColors.RED);
} else if (state.origProduceManaB() && state.origProduceManaG()) {
colorPairs[0] = fromDetailColor(DetailColors.BLACK);
colorPairs[1] = fromDetailColor(DetailColors.GREEN);
} else if (state.origProduceManaR() && state.origProduceManaG()) {
colorPairs[0] = fromDetailColor(DetailColors.RED);
colorPairs[1] = fromDetailColor(DetailColors.GREEN);
}
}
colorPairs = FSkinColor.tintColors(Color.WHITE, colorPairs, 0.3f);
float oldAlpha = g.getfloatAlphaComposite();
if (!useCardBGTexture)
fillColorBackground(g, colorPairs, x, y, w, h);
else {
g.setAlphaComposite(0.95f);
fillColorBackground(g, colorPairs, x, y, w, h);
if (fakeDuals && state.countBasicLandTypes() == 2) {
g.setAlphaComposite(0.1f);
drawAlphaLines(g, x, y, w, h);
}
g.setAlphaComposite(oldAlpha);
}
} else {
//override bg color
if (state.origCanProduceColoredMana() > 2 || state.origProduceAnyMana()) {
modColors = DetailColors.MULTICOLOR;
} else if (state.origCanProduceColoredMana() == 1) {
if (state.origProduceManaW())
modColors = DetailColors.LAND;
else if (state.origProduceManaB())
modColors = DetailColors.BLACK;
else if (state.origProduceManaG())
modColors = DetailColors.GREEN;
else if (state.origProduceManaR())
modColors = DetailColors.RED;
else if (state.origProduceManaU())
modColors = DetailColors.BLUE;
}
Color bgColor = fromDetailColor(modColors);
bgColor = FSkinColor.tintColor(Color.WHITE, bgColor, CardRenderer.NAME_BOX_TINT);
float oldAlpha = g.getfloatAlphaComposite();
if (!useCardBGTexture)
g.fillRect(bgColor, x, y, w, h);
else {
g.setAlphaComposite(0.95f);
g.fillRect(bgColor, x, y, w, h);
g.setAlphaComposite(oldAlpha);
}
}
} else {
float oldAlpha = g.getfloatAlphaComposite();
if (!useCardBGTexture)
fillColorBackground(g, colors, x, y, w, h);
else {
g.setAlphaComposite(0.95f);
fillColorBackground(g, colors, x, y, w, h);
g.setAlphaComposite(oldAlpha);
}
}
g.drawRect(BORDER_THICKNESS, Color.BLACK, x, y, w, h);
if (!onTop) { return; } //remaining rendering only needed if card on top
if (state.isBasicLand()) {
//draw icons for basic lands
FSkinImage image;
switch (state.getName().replaceFirst("^Snow-Covered ", "")) {
case "Plains":
//draw watermark
FSkinImage image = null;
if (state.origCanProduceColoredMana() == 1 && !state.origProduceManaC()) {
if (state.isPlains())
image = FSkinImage.WATERMARK_W;
break;
case "Island":
else if (state.isIsland())
image = FSkinImage.WATERMARK_U;
break;
case "Swamp":
else if (state.isSwamp())
image = FSkinImage.WATERMARK_B;
break;
case "Mountain":
else if (state.isMountain())
image = FSkinImage.WATERMARK_R;
break;
case "Forest":
else if (state.isForest())
image = FSkinImage.WATERMARK_G;
break;
default:
} else if (state.origProduceManaC()) {
image = FSkinImage.WATERMARK_C;
break;
}
if (image != null) {
float iconSize = h * 0.75f;
g.drawImage(image, x + (w - iconSize) / 2, y + (h - iconSize) / 2, iconSize, iconSize);
}
else {
} else {
boolean needTranslation = true;
String text = "";
if (card.isToken()) {
if (card.getCloneOrigin() == null)
needTranslation = false;
}
final String text = !card.isSplitCard() ?
if (drawAdventure) {
// draw left textbox text
if (noText)
return;
if (card.isAdventureCard()) {
CardView cv = card.getBackup();
if (cv == null || isFaceDown)
cv = card;
text = cv.getText(cv.getState(true), needTranslation ? CardTranslation.getTranslationTexts(cv.getName(), "") : null);
} else {
text = !card.isSplitCard() ?
card.getText(state, needTranslation ? CardTranslation.getTranslationTexts(state.getName(), "") : null) :
card.getText(state, needTranslation ? CardTranslation.getTranslationTexts(card.getLeftSplitState().getName(), card.getRightSplitState().getName()) : null );
if (StringUtils.isEmpty(text)) { return; }
card.getText(state, needTranslation ? CardTranslation.getTranslationTexts(card.getLeftSplitState().getName(), card.getRightSplitState().getName()) : null);
}
} else {
if (noText)
return;
if (card.isAdventureCard()) {
CardView cv = card.getBackup();
if (cv == null || isFaceDown)
cv = card;
text = cv.getText(cv.getState(false), needTranslation ? CardTranslation.getTranslationTexts(cv.getName(), "") : null);
} else {
text = !card.isSplitCard() ?
card.getText(state, needTranslation ? CardTranslation.getTranslationTexts(state.getName(), "") : null) :
card.getText(state, needTranslation ? CardTranslation.getTranslationTexts(card.getLeftSplitState().getName(), card.getRightSplitState().getName()) : null);
}
}
if (StringUtils.isEmpty(text)) {
return;
}
float padding = TEXT_FONT.getCapHeight() * 0.75f;
x += padding;
@@ -329,8 +614,12 @@ public class CardImageRenderer {
cardTextRenderer.drawText(g, text, TEXT_FONT, Color.BLACK, x, y, w, h, y, h, true, Align.left, true);
}
}
private static void drawPtBox(Graphics g, CardView card, CardStateView state, Color[] colors, float x, float y, float w, float h) {
private static void drawAlphaLines(Graphics g, float x, float y, float w, float h) {
if (FSkin.overlay_alpha != null) {
g.drawImage(FSkin.overlay_alpha, x, y, w, h);
}
}
private static void drawPtBox(Graphics g, CardView card, CardStateView state, Color[] colors, float x, float y, float w, float h, boolean noText) {
List<String> pieces = new ArrayList<>();
if (state.isCreature()) {
pieces.add(String.valueOf(state.getPower()));
@@ -369,6 +658,8 @@ public class CardImageRenderer {
fillColorBackground(g, colors, x, y, w, h);
g.drawRect(BORDER_THICKNESS, Color.BLACK, x, y, w, h);
if (noText)
return;
x += (boxWidth - totalPieceWidth) / 2;
for (int i = 0; i < pieces.size(); i++) {
g.drawText(pieces.get(i), PT_FONT, Color.BLACK, x, y, w, h, false, Align.left, true);
@@ -399,8 +690,8 @@ public class CardImageRenderer {
return;
}
if (image == ImageCache.defaultImage) { //support drawing card image manually if card image not found
drawCardImage(g, card, altState, x, y, w, h, CardStackPosition.Top);
if (image == ImageCache.defaultImage || Forge.enableUIMask.equals("Art")) { //support drawing card image manually if card image not found
drawCardImage(g, card, altState, x, y, w, h, CardStackPosition.Top, true, true);
} else {
float radius = (h - w)/8;
float wh_Adj = ForgeConstants.isGdxPortLandscape && isCurrentCard ? 1.38f:1.0f;
@@ -517,11 +808,79 @@ public class CardImageRenderer {
Color[] colors = new Color[backColors.size()];
for (int i = 0; i < colors.length; i++) {
DetailColors dc = backColors.get(i);
colors[i] = FSkinColor.fromRGB(dc.r, dc.g, dc.b);
colors[i] = fromDetailColor(dc);
}
fillColorBackground(g, colors, x, y, w, h);
return colors;
}
public static Color[] drawCardBackgroundTexture(CardStateView state, Graphics g, List<DetailColors> backColors, float x, float y, float w, float h) {
boolean isHybrid = state.getManaCost().hasHybrid();
Color[] colors = new Color[backColors.size()];
for (int i = 0; i < colors.length; i++) {
DetailColors dc = backColors.get(i);
colors[i] = fromDetailColor(dc);
}
switch (backColors.size()) {
case 1:
if (backColors.get(0) == DetailColors.FACE_DOWN) {
g.drawImage(FSkinImage.CARDBG_C, x, y, w,h);
} else if (backColors.get(0) == DetailColors.LAND) {
g.drawImage(FSkinImage.CARDBG_L, x, y, w,h);
}else if (backColors.get(0) == DetailColors.MULTICOLOR) {
g.drawImage(FSkinImage.CARDBG_M, x, y, w,h);
} else if (backColors.get(0) == DetailColors.COLORLESS) {
if (state.isVehicle())
g.drawImage(FSkinImage.CARDBG_V, x, y, w,h);
else if (state.isArtifact())
g.drawImage(FSkinImage.CARDBG_A, x, y, w,h);
else
g.drawImage(FSkinImage.CARDBG_C, x, y, w,h);
} else if (backColors.get(0) == DetailColors.GREEN) {
g.drawImage(FSkinImage.CARDBG_G, x, y, w,h);
} else if (backColors.get(0) == DetailColors.RED) {
g.drawImage(FSkinImage.CARDBG_R, x, y, w,h);
} else if (backColors.get(0) == DetailColors.BLACK) {
g.drawImage(FSkinImage.CARDBG_B, x, y, w,h);
} else if (backColors.get(0) == DetailColors.BLUE) {
g.drawImage(FSkinImage.CARDBG_U, x, y, w,h);
} else if (backColors.get(0) == DetailColors.WHITE) {
g.drawImage(FSkinImage.CARDBG_W, x, y, w,h);
}
break;
case 2:
if (!isHybrid) {
g.drawImage(FSkinImage.CARDBG_M, x, y, w, h);
} else if (backColors.contains(DetailColors.WHITE) && backColors.contains(DetailColors.BLUE)) {
g.drawImage(FSkinImage.CARDBG_WU, x, y, w, h);
} else if (backColors.contains(DetailColors.WHITE) && backColors.contains(DetailColors.BLACK)) {
g.drawImage(FSkinImage.CARDBG_WB, x, y, w, h);
} else if (backColors.contains(DetailColors.WHITE) && backColors.contains(DetailColors.RED)) {
g.drawImage(FSkinImage.CARDBG_WR, x, y, w, h);
} else if (backColors.contains(DetailColors.WHITE) && backColors.contains(DetailColors.GREEN)) {
g.drawImage(FSkinImage.CARDBG_WG, x, y, w, h);
} else if (backColors.contains(DetailColors.BLUE) && backColors.contains(DetailColors.BLACK)) {
g.drawImage(FSkinImage.CARDBG_UB, x, y, w, h);
} else if (backColors.contains(DetailColors.BLUE) && backColors.contains(DetailColors.RED)) {
g.drawImage(FSkinImage.CARDBG_UR, x, y, w, h);
} else if (backColors.contains(DetailColors.BLUE) && backColors.contains(DetailColors.GREEN)) {
g.drawImage(FSkinImage.CARDBG_UG, x, y, w, h);
} else if (backColors.contains(DetailColors.BLACK) && backColors.contains(DetailColors.RED)) {
g.drawImage(FSkinImage.CARDBG_BR, x, y, w, h);
} else if (backColors.contains(DetailColors.BLACK) && backColors.contains(DetailColors.GREEN)) {
g.drawImage(FSkinImage.CARDBG_BG, x, y, w, h);
} else if (backColors.contains(DetailColors.RED) && backColors.contains(DetailColors.GREEN)) {
g.drawImage(FSkinImage.CARDBG_RG, x, y, w, h);
}
break;
case 3:
g.drawImage(FSkinImage.CARDBG_M, x, y, w, h);
break;
default:
g.drawImage(FSkinImage.CARDBG_C, x, y, w,h);
break;
}
return colors;
}
public static void fillColorBackground(Graphics g, Color[] colors, float x, float y, float w, float h) {
switch (colors.length) {
case 1:

View File

@@ -7,6 +7,9 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import forge.ImageKeys;
import forge.localinstance.properties.ForgeConstants;
import forge.util.FileUtil;
import org.apache.commons.lang3.StringUtils;
import com.badlogic.gdx.Gdx;
@@ -195,6 +198,7 @@ public class CardRenderer {
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;
private static List<String> classicModuleCardtoCrop = FileUtil.readFile(ForgeConstants.CLASSIC_MODULE_CARD_TO_CROP_FILE);
public static void clearcardArtCache(){
cardArtCache.clear();
@@ -207,16 +211,22 @@ public class CardRenderer {
public static FImageComplex getCardArt(IPaperCard pc, boolean backFace) {
CardType type = pc.getRules().getType();
return getCardArt(pc.getImageKey(backFace), pc.getRules().getSplitType() == CardSplitType.Split, type.isPlane() || type.isPhenomenon(),pc.getRules().getOracleText().contains("Aftermath"));
return getCardArt(pc.getImageKey(backFace), pc.getRules().getSplitType() == CardSplitType.Split,
type.isPlane() || type.isPhenomenon(),pc.getRules().getOracleText().contains("Aftermath"),
type.hasSubtype("Saga"), type.hasSubtype("Class"), type.isDungeon(), CardSplitType.Flip.equals(pc.getRules().getSplitType()),
type.isPlaneswalker(), isModernFrame(pc));
}
public static FImageComplex getCardArt(CardView card) {
CardTypeView type = card.getCurrentState().getType();
return getCardArt(card.getCurrentState().getImageKey(), card.isSplitCard(), type.isPlane() || type.isPhenomenon(),card.getText().contains("Aftermath"));
return getCardArt(card.getCurrentState().getImageKey(), card.isSplitCard(), type.isPlane() || type.isPhenomenon(),
card.getText().contains("Aftermath"), type.hasSubtype("Saga"), type.hasSubtype("Class"), type.isDungeon(),
card.isFlipCard(), type.isPlaneswalker(), isModernFrame(card));
}
public static FImageComplex getCardArt(String imageKey, boolean isSplitCard, boolean isHorizontalCard, boolean isAftermathCard) {
public static FImageComplex getCardArt(String imageKey, boolean isSplitCard, boolean isHorizontalCard, boolean isAftermathCard, boolean isSaga, boolean isClass, boolean isDungeon, boolean isFlipCard, boolean isPlanesWalker, boolean isModernFrame) {
FImageComplex cardArt = cardArtCache.get(imageKey);
boolean isClassicModule = classicModuleCardtoCrop.contains(imageKey.substring(ImageKeys.CARD_PREFIX.length()).replace(".jpg","").replace(".png", ""));
if (cardArt == null) {
Texture image = new RendererCachedCardImage(imageKey, true).getImage();
if (image != null) {
@@ -227,12 +237,41 @@ public class CardRenderer {
float x, y;
float w = image.getWidth();
float h = image.getHeight();
if (isSplitCard && !isAftermathCard) { //allow rotated image for split cards
if (isClassicModule) {
x = w * 0.09f;
y = h * 0.2f;
w -= 2f * x;
h -= 3f * y;
}else if (isPlanesWalker) {
x = w * 0.09f;
y = h * 0.11f;
w -= 2f * x;
h -= 5.71f * y;
} else if (isFlipCard) {
x = w * 0.09f;
y = h * 0.32f;
w -= 2f * x;
h -= 2.1f * y;
} else if (isDungeon) {
x = w * 0.09f;
y = h * 0.1f;
w -= 2f * x;
h -= 2.2f * y;
} else if (isClass) {
x = w * 0.09f;
y = h * 0.11f;
w -= 1.1f * x + w / 2;
h -= 2.45f * y;
} else if (isSaga) {
x = (w * 0.1f) + (w * 0.8f / 2);
y = h * 0.11f;
w -= 1.16f * x;
h -= 2.45f * y;
} else if (isSplitCard && !isAftermathCard) { //allow rotated image for split cards
x = w * 33f / 250f;
y = 0; //delay adjusting y and h until drawn
w *= 106f / 250f;
}
else if (isHorizontalCard) { //allow rotated image for horizontal cards
} else if (isHorizontalCard) { //allow rotated image for horizontal cards
float artX = 40f, artY = 40f;
float artW = 350f, artH = 156f;
float srcW = 430f, srcH = 300f;
@@ -241,8 +280,7 @@ public class CardRenderer {
y = h * 40f / srcH;
w *= artW / srcW;
h *= artH / srcH;
}
else { //rotate art clockwise if its not the correct orientation
} else { //rotate art clockwise if its not the correct orientation
x = w * artY / srcH;
y = h * (srcW - artW - artX) / srcW;
w *= artH / srcH;
@@ -251,19 +289,18 @@ public class CardRenderer {
cardArtCache.put(imageKey, cardArt);
return cardArt;
}
}
else {
x = w * 0.1f;
y = h * 0.11f;
w -= 2 * x;
} else {
//adjust smaller crop
x = isModernFrame ? w * 0.1f :w * 0.12f;
y = isModernFrame ? h * 0.12f : h * 0.11f;
w -= isModernFrame ? 2 * x : 2.1f * x;
h *= CARD_ART_HEIGHT_PERCENTAGE;
float ratioRatio = w / h / CARD_ART_RATIO;
if (ratioRatio > 1) { //if too wide, shrink width
float dw = w * (ratioRatio - 1);
w -= dw;
x += dw / 2;
}
else { //if too tall, shrink height
} else { //if too tall, shrink height
float dh = h * (1 - ratioRatio);
h -= dh;
y += dh / 2;
@@ -290,8 +327,7 @@ public class CardRenderer {
if (image != null) {
if (image == ImageCache.defaultImage) {
cardArt = CardImageRenderer.forgeArt;
}
else {
} else {
float x, y;
float w = image.getWidth();
float h = image.getHeight();
@@ -310,6 +346,52 @@ public class CardRenderer {
return cardArt;
}
public static FImageComplex getAlternateCardArt(final String imageKey, boolean isPlanesWalker) {
FImageComplex cardArt = cardArtCache.get("Alternate_"+imageKey);
if (cardArt == null) {
Texture image = new CachedCardImage(imageKey) {
@Override
public void onImageFetched() {
ImageCache.clear();
cardArtCache.remove("Alternate_" + imageKey);
}
}.getImage();
if (image != null) {
if (image == ImageCache.defaultImage) {
cardArt = CardImageRenderer.forgeArt;
} else {
float x, y;
float w = image.getWidth();
float h = image.getHeight();
if (isPlanesWalker) {
x = w * 0.09f;
y = h * 0.11f;
w -= 2f * x;
h -= 5.71f * y;
} else {
x = w * 0.1f;
y = h * 0.11f;
w -= 2 * x;
h *= CARD_ART_HEIGHT_PERCENTAGE;
float ratioRatio = w / h / CARD_ART_RATIO;
if (ratioRatio > 1) { //if too wide, shrink width
float dw = w * (ratioRatio - 1);
w -= dw;
x += dw / 2;
} else { //if too tall, shrink height
float dh = h * (1 - ratioRatio);
h -= dh;
y += dh / 2;
}
}
cardArt = new FTextureRegionImage(new TextureRegion(image, Math.round(x), Math.round(y), Math.round(w), Math.round(h)));
}
cardArtCache.put("Alternate_"+imageKey, cardArt);
}
}
return cardArt;
}
public static void drawCardListItem(Graphics g, FSkinFont font, FSkinColor foreColor, CardView card, int count, String suffix, float x, float y, float w, float h, boolean compactMode) {
final CardStateView state = card.getCurrentState();
if (card.getId() > 0) {
@@ -457,8 +539,8 @@ public class CardRenderer {
minusxy = 0.135f*radius;
}
if (image != null) {
if (image == ImageCache.defaultImage) {
CardImageRenderer.drawCardImage(g, CardView.getCardForUi(pc), false, x, y, w, h, pos);
if (image == ImageCache.defaultImage || Forge.enableUIMask.equals("Art")) {
CardImageRenderer.drawCardImage(g, CardView.getCardForUi(pc), false, x, y, w, h, pos, true, true);
} else {
if (Forge.enableUIMask.equals("Full")) {
if (ImageCache.isBorderlessCardArt(image))
@@ -482,13 +564,13 @@ public class CardRenderer {
}
} else {
//if card has invalid or no texture due to sudden changes in ImageCache, draw CardImageRenderer instead and wait for it to refresh automatically
CardImageRenderer.drawCardImage(g, CardView.getCardForUi(pc), false, x, y, w, h, pos);
CardImageRenderer.drawCardImage(g, CardView.getCardForUi(pc), false, x, y, w, h, pos, Forge.enableUIMask.equals("Art"), true);
}
}
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);
drawCard(g, card, x, y, w, h, pos, rotate, false, false);
}
public static void drawCard(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos, boolean rotate, boolean showAltState) {
public static void drawCard(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos, boolean rotate, boolean showAltState, boolean isChoiceList) {
boolean canshow = MatchController.instance.mayView(card);
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());
@@ -501,8 +583,12 @@ public class CardRenderer {
minusxy = 0.135f*radius;
}
if (image != null) {
if (image == ImageCache.defaultImage) {
CardImageRenderer.drawCardImage(g, card, false, x, y, w, h, pos);
if (image == ImageCache.defaultImage || Forge.enableUIMask.equals("Art")) {
float oldAlpha = g.getfloatAlphaComposite();
if (card.isPhasedOut())
g.setAlphaComposite(0.2f);
CardImageRenderer.drawCardImage(g, card, showAltState, x, y, w, h, pos, true, false, isChoiceList, !showCardIdOverlay(card));
g.setAlphaComposite(oldAlpha);
} else if (showsleeves) {
if (!card.isForeTold())
g.drawImage(sleeves, x, y, w, h);
@@ -544,7 +630,11 @@ public class CardRenderer {
drawFoilEffect(g, card, x, y, w, h, false);
} else {
//if card has invalid or no texture due to sudden changes in ImageCache, draw CardImageRenderer instead and wait for it to refresh automatically
CardImageRenderer.drawCardImage(g, card, false, x, y, w, h, pos);
float oldAlpha = g.getfloatAlphaComposite();
if (card.isPhasedOut())
g.setAlphaComposite(0.2f);
CardImageRenderer.drawCardImage(g, card, showAltState, x, y, w, h, pos, Forge.enableUIMask.equals("Art"), false, isChoiceList, !showCardIdOverlay(card));
g.setAlphaComposite(oldAlpha);
}
}
@@ -557,7 +647,7 @@ public class CardRenderer {
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, showAltState);
drawCard(g, card, x, y, w, h, pos, false, showAltState, isChoiceList);
float padding = w * PADDING_MULTIPLIER; //adjust for card border
x += padding;
@@ -684,7 +774,7 @@ public class CardRenderer {
multiplier = 0.150f;
break;
}
g.drawOutlinedText(CardTranslation.getTranslatedName(details.getName()), FSkinFont.forHeight(h * multiplier), Color.WHITE, Color.BLACK, x + padding -1f, y + padding, w - 2 * padding, h * 0.4f, true, Align.left, false);
g.drawOutlinedText(CardTranslation.getTranslatedName(details.getName()), FSkinFont.forHeight(h * multiplier), Color.WHITE, Color.BLACK, x + padding -1f, y + padding, w - 2 * padding, h * 0.4f, true, Align.left, false, true);
}
if (showCardManaCostOverlay(card)) {
float manaSymbolSize = w / 4.5f;

View File

@@ -181,7 +181,7 @@ public class CardZoom extends FOverlay {
}
if (flipIconBounds != null && flipIconBounds.contains(x, y)) {
if (currentCard.isFaceDown() && currentCard.getBackup() != null) {
if (currentCard.getBackup().hasBackSide()) {
if (currentCard.getBackup().hasBackSide() || currentCard.getBackup().isFlipCard() || currentCard.getBackup().isAdventureCard()) {
show(currentCard.getBackup());
return true;
}

View File

@@ -16,6 +16,7 @@ import com.badlogic.gdx.utils.Align;
import forge.Forge;
import forge.Forge.KeyInputAdapter;
import forge.Graphics;
import forge.ImageKeys;
import forge.assets.FImage;
import forge.assets.FImageComplex;
import forge.assets.FSkin;
@@ -24,17 +25,15 @@ import forge.assets.FSkinColor.Colors;
import forge.assets.FSkinFont;
import forge.assets.FSkinImage;
import forge.assets.ImageCache;
import forge.card.CardFaceSymbols;
import forge.card.CardRenderer;
import forge.card.*;
import forge.card.CardRenderer.CardStackPosition;
import forge.card.CardZoom;
import forge.card.ColorSet;
import forge.deck.ArchetypeDeckGenerator;
import forge.deck.CardThemedDeckGenerator;
import forge.deck.CommanderDeckGenerator;
import forge.deck.DeckProxy;
import forge.deck.FDeckViewer;
import forge.deck.io.DeckPreferences;
import forge.game.card.CardView;
import forge.gamemodes.planarconquest.ConquestCommander;
import forge.item.InventoryItem;
import forge.item.PaperCard;
@@ -54,6 +53,7 @@ import forge.toolbox.FEvent.FEventHandler;
import forge.toolbox.FLabel;
import forge.toolbox.FScrollPane;
import forge.toolbox.FTextField;
import forge.util.ImageUtil;
import forge.util.Localizer;
import forge.util.TextUtil;
import forge.util.Utils;
@@ -1016,8 +1016,20 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
} else {
//commander bg
g.drawImage(FSkin.getDeckbox().get(0), FSkin.getDeckbox().get(0), x, y, w, h, Color.GREEN, selected);
PaperCard paperCard = null;
String imageKey = item.getImageKey(false);
if (imageKey != null) {
if (imageKey.startsWith(ImageKeys.CARD_PREFIX))
paperCard = ImageUtil.getPaperCardFromImageKey(imageKey);
}
if (paperCard != null && Forge.enableUIMask.equals("Art")) {
CardImageRenderer.drawCardImage(g, CardView.getCardForUi(paperCard), false,
x + (w - w * scale) / 2, y + (h - h * scale) / 1.5f, w * scale, h * scale, CardStackPosition.Top, true, false, false, true);
} else {
TextureRegion tr = ImageCache.croppedBorderImage(dpImg);
g.drawImage(tr, x+(w-w*scale)/2, y+(h-h*scale)/1.5f, w*scale, h*scale);
g.drawImage(tr, x + (w - w * scale) / 2, y + (h - h * scale) / 1.5f, w * scale, h * scale);
}
}
//fake labelname shadow
g.drawText(item.getName(), GROUP_HEADER_FONT, Color.BLACK, (x + PADDING)-1f, (y + PADDING*2)+1f, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false);
@@ -1026,8 +1038,8 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
} else {
if (!dp.isGeneratedDeck()){
//If deck has Commander, use it as cardArt reference
String deckImageKey = dp.getDeck().getCommanders().isEmpty() ? dp.getHighestCMCCard().getImageKey(false) : dp.getDeck().getCommanders().get(0).getImageKey(false);
FImageComplex cardArt = CardRenderer.getCardArt(deckImageKey, false, false, false);
PaperCard paperCard = dp.getDeck().getCommanders().isEmpty() ? dp.getHighestCMCCard() : dp.getDeck().getCommanders().get(0);
FImageComplex cardArt = CardRenderer.getCardArt(paperCard);
//draw the deckbox
if (cardArt == null){
//draw generic box if null or still loading

View File

@@ -161,6 +161,10 @@ public class HomeScreen extends FScreen {
return QuestCommander;
}
public int getActiveButtonIndex() {
return activeButtonIndex;
}
public String getQuestWorld() {
return QuestWorld;
}

View File

@@ -105,8 +105,11 @@ public class LoadGameMenu extends FPopupMenu {
protected void buildMenu() {
FScreen currentScreen = Forge.getCurrentScreen();
for (LoadGameScreen lgs : LoadGameScreen.values()) {
//fixes the overlapping menu items when the user suddenly switch from load game screen index to another screen
if (HomeScreen.instance.getActiveButtonIndex() == 1) {
addItem(lgs.item);
lgs.item.setSelected(currentScreen == lgs.screen);
}
}
}
}

View File

@@ -521,7 +521,7 @@ public class SettingsPage extends TabPage<SettingsScreen> {
lstSettings.addItem(new CustomSelectSetting(FPref.UI_ENABLE_BORDER_MASKING,
localizer.getMessage("lblBorderMaskOption"),
localizer.getMessage("nlBorderMaskOption"),
new String[]{"Off", "Crop", "Full"}) {
new String[]{"Off", "Crop", "Full", "Art"}) {
@Override
public void valueChanged(String newValue) {
super.valueChanged(newValue);

View File

@@ -393,7 +393,7 @@ public class FChoiceList<T> extends FList<T> implements ActivateHandler {
boolean showAlt = false;
if(cardView.hasAlternateState()){
if(cardView.hasBackSide())
showAlt = value.contains(cardView.getBackSideName());
showAlt = value.contains(cardView.getBackSideName()) || cardView.getAlternateState().getAbilityText().contains(value);
else if (cardView.isAdventureCard())
showAlt = value.equals(cardView.getAlternateState().getAbilityText());
else if (cardView.isSplitCard()) {
@@ -520,9 +520,12 @@ public class FChoiceList<T> extends FList<T> implements ActivateHandler {
@Override
public void drawValue(Graphics g, T value, FSkinFont font, FSkinColor foreColor, boolean pressed, float x, float y, float w, float h) {
//should fix NPE ie Thief of Sanity, Gonti... etc
CardView cv = ((IHasCardView)value).getCardView().isFaceDown() && ((IHasCardView)value).getCardView().isInZone(EnumSet.of(ZoneType.Exile)) ? ((IHasCardView)value).getCardView().getBackup() : ((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, true);
CardView cv = ((IHasCardView)value).getCardView();
if (cv != null) {
CardView render = cv.isFaceDown() && cv.isInZone(EnumSet.of(ZoneType.Exile)) ? cv.getBackup() : cv;
boolean showAlternate = showAlternate(render, value.toString());
CardRenderer.drawCardWithOverlays(g, render, x, y, VStack.CARD_WIDTH, VStack.CARD_HEIGHT, CardStackPosition.Top, false, showAlternate, true);
}
float dx = VStack.CARD_WIDTH + FList.PADDING;
x += dx;

View File

@@ -3,6 +3,6 @@ ManaCost:4 U
Types:Creature Shapeshifter
PT:0/0
K:ETBReplacement:Copy:DBCopy:Optional
SVar:DBCopy:DB$ Clone | Choices$ Creature.Other | ChoiceZone$ Graveyard | SpellDescription$ You may have CARDNAME enter the battlefield as a copy of any artifact on the battlefield.
SVar:DBCopy:DB$ Clone | Choices$ Creature.Other | ChoiceZone$ Graveyard | SpellDescription$ You may have CARDNAME enter the battlefield as a copy of any creature card in a graveyard.
SVar:Picture:http://www.wizards.com/global/images/magic/general/body_double.jpg
Oracle:You may have Body Double enter the battlefield as a copy of any creature card in a graveyard.

View File

@@ -4,6 +4,6 @@ Types:Land
A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}.
A:AB$ Mana | Cost$ T | Produced$ W | SubAbility$ DBPain | SpellDescription$ Add {W}. CARDNAME deals 1 damage to you.
A:AB$ Mana | Cost$ T | Produced$ G | SubAbility$ DBPain | SpellDescription$ Add {G}. CARDNAME deals 1 damage to you.
SVar:DBPain:DB$DealDamage | NumDmg$ 1 | Defined$ You
SVar:DBPain:DB$ DealDamage | NumDmg$ 1 | Defined$ You
SVar:Picture:http://www.wizards.com/global/images/magic/general/brushland.jpg
Oracle:{T}: Add {C}.\n{T}: Add {G} or {W}. Brushland deals 1 damage to you.

View File

@@ -2,7 +2,7 @@ Name:Chrome Mox
ManaCost:0
Types:Artifact
T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | OptionalDecider$ You | Execute$ TrigExile | TriggerDescription$ Imprint — When CARDNAME enters the battlefield, you may exile a nonartifact, nonland card from your hand.
SVar:TrigExile:DB$ChangeZone | Imprint$ True | Origin$ Hand | Destination$ Exile | ChangeType$ Card.nonArtifact+nonLand | ChangeNum$ 1
SVar:TrigExile:DB$ ChangeZone | Imprint$ True | Origin$ Hand | Destination$ Exile | ChangeType$ Card.nonArtifact+nonLand | ChangeNum$ 1
A:AB$ ManaReflected | Cost$ T | Valid$ Defined.Imprinted | ColorOrType$ Color | ReflectProperty$ Is | SpellDescription$ Add one mana of any of the exiled card's colors.
T:Mode$ ChangesZone | Origin$ Battlefield | ValidCard$ Card.Self | Destination$ Any | Execute$ DBCleanup | Static$ True
T:Mode$ ChangesZone | Origin$ Exile | Destination$ Any | Static$ True | ValidCard$ Card.IsImprinted+ExiledWithSource | Execute$ DBForget

View File

@@ -3,6 +3,6 @@ ManaCost:no cost
Types:Land
A:AB$ Mana | Cost$ T | Produced$ Any | Amount$ 1 | SpellDescription$ Add one mana of any color.
T:Mode$ Taps | ValidCard$ Card.Self | Execute$ TrigDamage | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME becomes tapped, it deals 1 damage to you.
SVar:TrigDamage:DB$DealDamage | Defined$ You | NumDmg$ 1
SVar:TrigDamage:DB$ DealDamage | Defined$ You | NumDmg$ 1
SVar:Picture:http://www.wizards.com/global/images/magic/general/city_of_brass.jpg
Oracle:Whenever City of Brass becomes tapped, it deals 1 damage to you.\n{T}: Add one mana of any color.

View File

@@ -6,7 +6,7 @@ A:AB$ ChangeZoneAll | Cost$ Sac<1/CARDNAME> | ChangeType$ Card.Creature+IsRememb
T:Mode$ ChangesZone | Origin$ Exile | Destination$ Any | Static$ True | ValidCard$ Card.IsRemembered+ExiledWithSource | Execute$ DBForget
SVar:DBForget:DB$ Pump | ForgetObjects$ TriggeredCard
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | Static$ True | ValidCard$ Card.Self | Execute$ DBCleanup
SVar:DBCleanup:DB$Cleanup | ClearRemembered$ True
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:NonStackingEffect:True
AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/cold_storage.jpg

View File

@@ -1,7 +1,7 @@
Name:Devour Intellect
ManaCost:B
Types:Sorcery
A:SP$ Pump | TgtPrompt$ Select target opponent | ValidTgt$ Opponent | SubAbility$ DBBranch
A:SP$ Pump | TgtPrompt$ Select target opponent | ValidTgts$ Opponent | SubAbility$ DBBranch
SVar:DBBranch:DB$ Branch | BranchConditionSVar$ TreasureCheck | BranchConditionSVarCompare$ GE1 | FalseSubAbility$ DBDiscard | TrueSubAbility$ DBTreasureDiscard
SVar:DBDiscard:DB$ Discard | Defined$ Targeted | NumCards$ 1 | Mode$ TgtChoose
SVar:DBTreasureDiscard:DB$ Discard | Defined$ Targeted | NumCards$ 1 | Mode$ RevealYouChoose | DiscardValid$ Card.nonLand

View File

@@ -3,7 +3,7 @@ ManaCost:2 W B G
Types:Legendary Creature Nightmare Insect
PT:3/3
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ PutKeywordCounter | TriggerDescription$ When CARDNAME enters the battlefield, put a flying counter on any creature you control if a creature card in your graveyard has flying. Repeat this process for first strike, double strike, deathtouch, hexproof, indestructible, lifelink, menace, reach, trample, and vigilance. Then put a +1/+1 counter on NICKNAME for each counter put on a creature this way.
SVar:PutKeywordCounter:DB$ PutCounter | Choices$ Creature.YouCtrl | ChoiceTitle$ Choose a creature | SharedKeywords$ Flying & First Strike & Double Strike & Deathtouch & Hexproof & Indestructible & Lifelink & Menace & Reach & Trample & Vigilance | SharedKeywordsZone$ Graveyard | SharedRestrictions$ Card.YouOwn | CounterNum$ 1 | RememberAmount$ True | SubAbility$ PutCounters
SVar:PutKeywordCounter:DB$ PutCounter | Choices$ Creature.YouCtrl | ChoiceTitle$ Choose a creature | SharedKeywords$ Flying & First Strike & Double Strike & Deathtouch & Hexproof & Indestructible & Lifelink & Menace & Reach & Trample & Vigilance | SharedKeywordsZone$ Graveyard | SharedRestrictions$ Creature.YouOwn | CounterNum$ 1 | RememberAmount$ True | SubAbility$ PutCounters
SVar:PutCounters:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ X | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Count$RememberedNumber

View File

@@ -2,7 +2,7 @@ Name:Promise of Tomorrow
ManaCost:2 W
Types:Enchantment
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigChange | TriggerDescription$ Whenever a creature you control dies, exile it.
SVar:TrigChange:DB$ ChangeZone | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Exile
SVar:TrigChange:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Exile
T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield | IsPresent$ Creature.YouCtrl | PresentCompare$ EQ0 | Execute$ TrigSac | TriggerDescription$ At the beginning of each end step, if you control no creatures, sacrifice CARDNAME and return all cards exiled with it to the battlefield under your control.
SVar:TrigSac:DB$ Sacrifice | Defined$ Self | SubAbility$ DBChangeZoneAll
SVar:DBChangeZoneAll:DB$ ChangeZoneAll | Origin$ Exile | Destination$ Battlefield | ChangeType$ Card.ExiledWithSource

View File

@@ -5,6 +5,6 @@ A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}.
A:AB$ Mana | Cost$ T | Produced$ U | SubAbility$ DBPain | SpellDescription$ Add {U}. CARDNAME deals 1 damage to you.
A:AB$ Mana | Cost$ T | Produced$ G | SubAbility$ DBPain | SpellDescription$ Add {G}. CARDNAME deals 1 damage to you.
K:CARDNAME enters the battlefield tapped.
SVar:DBPain:DB$DealDamage | NumDmg$ 1 | Defined$ You
SVar:DBPain:DB$ DealDamage | NumDmg$ 1 | Defined$ You
SVar:Picture:http://www.wizards.com/global/images/magic/general/skyshroud_forest.jpg
Oracle:Skyshroud Forest enters the battlefield tapped.\n{T}: Add {C}.\n{T}: Add {G} or {U}. Skyshroud Forest deals 1 damage to you.

View File

@@ -0,0 +1,7 @@
Name:Avacyn's Memorial
ManaCost:5 W W W
Types:Legendary Artifact
K:Indestructible
S:Mode$ Continuous | Affected$ Permanent.Other+YouCtrl+Legendary | AddKeyword$ Indestructible | Description$ Other legendary permanents you control have indestructible.
DeckNeeds:Type$Legendary
Oracle:Indestructible\nOther legendary permanents you control have indestructible.

View File

@@ -0,0 +1,19 @@
Name:Baithook Angler
ManaCost:1 U
Types:Creature Human Peasant
PT:2/1
K:Disturb:1 U
AlternateMode:DoubleFaced
Oracle:Disturb {1}{U} (You may cast this card from your graveyard transformed for its disturb cost.)
ALTERNATE
Name:Hook-Haunt Drifter
ManaCost:no cost
Types:Creature Spirit
Colors:blue
PT:1/2
K:Flying
R:Event$ Moved | ValidCard$ Card.Self | Destination$ Graveyard | ReplaceWith$ Exile | Description$ If CARDNAME would be put into a graveyard from anywhere, exile it instead.
SVar:Exile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard
Oracle:Flying\nIf Hook-Haunt Drifter would be put into a graveyard from anywhere, exile it instead.

View File

@@ -0,0 +1,20 @@
Name:Beloved Beggar
ManaCost:1 W
Types:Creature Human Peasant
PT:0/4
K:Disturb:4 W W
AlternateMode:DoubleFaced
Oracle:Disturb {4}{W}{W} (You may cast this card from your graveyard transformed for its disturb cost.)
ALTERNATE
Name:Generous Soul
ManaCost:no cost
Types:Creature Spirit
Colors:white
PT:4/4
K:Flying
K:Vigilance
R:Event$ Moved | ValidCard$ Card.Self | Destination$ Graveyard | ReplaceWith$ Exile | Description$ If CARDNAME would be put into a graveyard from anywhere, exile it instead.
SVar:Exile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard
Oracle:Flying, vigilance\nIf Generous Soul would be put into a graveyard from anywhere, exile it instead.

View File

@@ -0,0 +1,8 @@
Name:Candlelit Cavalry
ManaCost:4 G
Types:Creature Human Knight
PT:5/5
T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ GE3 | Execute$ TrigPump | TriggerDescription$ Coven At the beginning of combat on your turn, if you control three or more creatures with different powers, CARDNAME gains trample until end of turn.
SVar:TrigPump:DB$ Pump | Defined$ Self | KW$ Trample
SVar:X:Count$DifferentPower_Creature.YouCtrl
Oracle:Coven — At the beginning of combat on your turn, if you control three or more creatures with different powers, Candlelit Cavalry gains trample until end of turn.

View File

@@ -0,0 +1,10 @@
Name:Candletrap
ManaCost:W
Types:Enchantment Aura
K:Enchant creature
A:SP$ Attach | Cost$ W | ValidTgts$ Creature | AILogic$ Curse
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Defender | Description$ Enchanted creature has defender.
R:Event$ DamageDone | Prevent$ True | IsCombat$ True | ValidSource$ Creature.EnchantedBy | Description$ Prevent all combat damage that would be dealt by enchanted creature.
A:AB$ ChangeZone | Cost$ 2 W Sac<1/CARDNAME> | Defined$ Enchanted | CheckSVar$ X | SVarCompare$ GE3 | Origin$ Battlefield | Destination$ Exile | PrecostDesc$ Coven — | SpellDescription$ Exile enchanted creature. Activate only if you control three or more creatures with different powers.
SVar:X:Count$DifferentPower_Creature.YouCtrl
Oracle:Enchant creature\nEnchanted creature has defender.\nPrevent all combat damage that would be dealt by enchanted creature.\nCoven — {2}{W}, Sacrifice Candletrap: Exile enchanted creature. Activate only if you control three or more creatures with different powers.

View File

@@ -0,0 +1,10 @@
Name:Curse of Obsession
ManaCost:4 R
Types:Enchantment Aura Curse
K:Enchant player
A:SP$ Attach | ValidTgts$ Player | AILogic$ Curse
T:Mode$ Phase | Phase$ Draw | ValidPlayer$ Player.EnchantedBy | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ At the beginning of enchanted player's draw step, that player draws two additional cards.
SVar:TrigDraw:DB$ Draw | Defined$ TriggeredPlayer | NumCards$ 2
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ Player.EnchantedBy | TriggerZones$ Battlefield | Execute$ TrigDiscard | TriggerDescription$ At the beginning of enchanted player's end step, that player discards their hand.
SVar:TrigDiscard:DB$ Discard | Defined$ TriggeredPlayer | Mode$ Hand
Oracle:Enchant player\nAt the beginning of enchanted player's draw step, that player draws two additional cards.\nAt the beginning of enchanted player's end step, that player discards their hand.

View File

@@ -0,0 +1,5 @@
Name:Defenestrate
ManaCost:2 B
Types:Instant
A:SP$ Destroy | Cost$ 2 B | ValidTgts$ Creature.withoutFlying | TgtPrompt$ Select target creature without flying | SpellDescription$ Destroy target creature without flying.
Oracle:Destroy target creature without flying.

View File

@@ -0,0 +1,9 @@
Name:Deserted Beach
ManaCost:no cost
Types:Land
A:AB$ Mana | Cost$ T | Produced$ W | SpellDescription$ Add {W}.
A:AB$ Mana | Cost$ T | Produced$ U | SpellDescription$ Add {U}.
K:ETBReplacement:Other:LandTapped
SVar:LandTapped:DB$ Tap | Defined$ Self | ETB$ True | ConditionCheckSVar$ ETBCheckSVar2 | ConditionSVarCompare$ LT2 | SpellDescription$ CARDNAME enters the battlefield tapped unless you control two or more other lands.
SVar:ETBCheckSVar2:Count$LastStateBattlefield Land.YouCtrl
Oracle:Deserted Beach enters the battlefield tapped unless you control two or more other lands.\n{T}: Add {W} or {U}.

View File

@@ -0,0 +1,8 @@
Name:Festival Crasher
ManaCost:1 R
Types:Creature Devil
PT:1/3
T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever you cast an instant or sorcery spell, CARDNAME gets +2/+0 until end of turn.
SVar:TrigPump:DB$ Pump | Defined$ Self | NumAtt$ 2
DeckHints:Type$Instant|Sorcery
Oracle:Whenever you cast an instant or sorcery spell, Festival Crasher gets +2/+0 until end of turn.

View File

@@ -0,0 +1,8 @@
Name:Galvanic Iteration
ManaCost:U R
Types:Instant
K:Flashback:1 U R
A:SP$ DelayedTrigger | Cost$ U R | AILogic$ SpellCopy | Execute$ EffTrigCopy | ThisTurn$ True | Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | SpellDescription$ When you cast your next instant or sorcery spell this turn, copy that spell. You may choose new targets for the copy.
SVar:EffTrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | MayChooseTarget$ True
SVar:AIPriorityModifier:9
Oracle:When you cast your next instant or sorcery spell this turn, copy that spell. You may choose new targets for the copy.\nFlashback {1}{U}{R} (You may cast this card from your graveyard for its flashback cost. Then exile it.)

View File

@@ -0,0 +1,11 @@
Name:Gisa, Glorious Resurrector
ManaCost:2 B B
Types:Legendary Creature Human Wizard
PT:4/4
R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.OppCtrl | ReplaceWith$ Exile | Description$ If a creature an opponent controls would die, exile it instead.
SVar:Exile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | IsPresent$ Card.ExiledWithSource | PresentZone$ Exile | Execute$ TrigChange | TriggerDescription$ At the beginning of your upkeep, put all creature cards exiled with CARDNAME onto the battlefield under your control. They gain decayed. (A creature with decayed can't block. When it attacks, sacrifice it at end of combat.)
SVar:TrigChange:DB$ ChangeZoneAll | ChangeType$ Card.ExiledWithSource | Origin$ Exile | Destination$ Battlefield | GainControl$ True | RememberChanged$ True | SubAbility$ DBPump
SVar:DBPump:DB$ PumpAll | ValidCards$ Card.IsRemembered | KW$ Decayed | Duration$ Permanent | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
Oracle:If a creature an opponent controls would die, exile it instead.\nAt the beginning of your upkeep, put all creature cards exiled with Gisa, Glorious Resurrector onto the battlefield under your control. They gain decayed. (A creature with decayed can't block. When it attacks, sacrifice it at end of combat.)

View File

@@ -0,0 +1,9 @@
Name:Haunted Ridge
ManaCost:no cost
Types:Land
A:AB$ Mana | Cost$ T | Produced$ B | SpellDescription$ Add {B}.
A:AB$ Mana | Cost$ T | Produced$ R | SpellDescription$ Add {R}.
K:ETBReplacement:Other:LandTapped
SVar:LandTapped:DB$ Tap | Defined$ Self | ETB$ True | ConditionCheckSVar$ ETBCheckSVar2 | ConditionSVarCompare$ LT2 | SpellDescription$ CARDNAME enters the battlefield tapped unless you control two or more other lands.
SVar:ETBCheckSVar2:Count$LastStateBattlefield Land.YouCtrl
Oracle:Haunted Ridge enters the battlefield tapped unless you control two or more other lands.\n{T}: Add {B} or {R}.

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