Merge branch 'master' into Attractions

# Conflicts:
#	forge-core/src/main/java/forge/deck/DeckFormat.java
This commit is contained in:
Jetz
2024-06-19 22:58:49 -04:00
941 changed files with 20173 additions and 118050 deletions

View File

@@ -52,6 +52,7 @@ public final class CardRules implements ICardCharacteristics {
private ColorSet deckbuildingColors;
private String meldWith;
private String partnerWith;
private boolean addsWildCardColor;
private boolean custom;
public CardRules(ICardFace[] faces, CardSplitType altMode, CardAiHints cah) {
@@ -70,6 +71,7 @@ public final class CardRules implements ICardCharacteristics {
aiHints = cah;
meldWith = "";
partnerWith = "";
addsWildCardColor = false;
//calculate color identity
byte colMask = calculateColorIdentity(mainPart);
@@ -92,6 +94,8 @@ public final class CardRules implements ICardCharacteristics {
colorIdentity = newRules.colorIdentity;
meldWith = newRules.meldWith;
partnerWith = newRules.partnerWith;
addsWildCardColor = newRules.addsWildCardColor;
tokens = newRules.tokens;
}
private static byte calculateColorIdentity(final ICardFace face) {
@@ -268,6 +272,9 @@ public final class CardRules implements ICardCharacteristics {
}
public boolean canBeCommander() {
if (mainPart.getOracleText().contains(" is your commander, choose a color before the game begins.")) {
addsWildCardColor = true;
}
if (mainPart.getOracleText().contains("can be your commander") || canBeBackground()) {
return true;
}
@@ -385,11 +392,15 @@ public final class CardRules implements ICardCharacteristics {
return partnerWith;
}
public boolean getAddsWildCardColor() {
return addsWildCardColor;
}
// vanguard card fields, they don't use sides.
private int deltaHand;
private int deltaLife;
private List<String> tokens;
private List<String> tokens = Collections.emptyList();
public List<String> getTokens() {
return tokens;
@@ -435,6 +446,7 @@ public final class CardRules implements ICardCharacteristics {
private CardSplitType altMode = CardSplitType.None;
private String meldWith = "";
private String partnerWith = "";
private boolean addsWildCardColor = false;
private String handLife = null;
private String normalizedName = "";
private Set<String> supportedFunctionalVariants = null;
@@ -473,6 +485,7 @@ public final class CardRules implements ICardCharacteristics {
this.has = null;
this.meldWith = "";
this.partnerWith = "";
this.addsWildCardColor = false;
this.normalizedName = "";
this.supportedFunctionalVariants = null;
this.tokens = Lists.newArrayList();
@@ -497,7 +510,10 @@ public final class CardRules implements ICardCharacteristics {
result.setNormalizedName(this.normalizedName);
result.meldWith = this.meldWith;
result.partnerWith = this.partnerWith;
result.tokens = tokens.isEmpty() ? Collections.emptyList() : tokens;
result.addsWildCardColor = this.addsWildCardColor;
if (!tokens.isEmpty()) {
result.tokens = tokens;
}
if (StringUtils.isNotBlank(handLife))
result.setVanguardProperties(handLife);
result.supportedFunctionalVariants = this.supportedFunctionalVariants;
@@ -777,7 +793,10 @@ public final class CardRules implements ICardCharacteristics {
}
public boolean hasStartOfKeyword(final String k) {
for (final String inst : mainPart.getKeywords()) {
return hasStartOfKeyword(k, mainPart);
}
public boolean hasStartOfKeyword(final String k, ICardFace cf) {
for (final String inst : cf.getKeywords()) {
final String[] parts = inst.split(":");
if (parts[0].equals(k)) {
return true;

View File

@@ -8,6 +8,7 @@ import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.util.CardTranslation;
import forge.util.ComparableOp;
@@ -186,7 +187,7 @@ public final class CardRulesPredicates {
return new Predicate<CardRules>() {
@Override
public boolean apply(final CardRules card) {
return card.hasStartOfKeyword(keyword);
return Iterables.any(card.getAllFaces(), cf -> cf != null && card.hasStartOfKeyword(keyword, cf));
}
};
}

View File

@@ -131,6 +131,11 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
return (this.myColor & ~colormask) == 0;
}
/** This returns the colors that colormask contains that are not in color */
public ColorSet getMissingColors(final byte colormask) {
return fromMask(this.myColor & ~colormask);
}
/** Operand has no other colors except defined by this. */
public boolean containsAllColorsFrom(final int colorProfile) {
return (~this.myColor & colorProfile) == 0;

View File

@@ -25,6 +25,7 @@ import forge.StaticData;
import forge.card.CardRules;
import forge.card.CardRulesPredicates;
import forge.card.CardType;
import forge.card.ColorSet;
import forge.card.ICardFace;
import forge.deck.generation.DeckGenPool;
import forge.deck.generation.DeckGeneratorBase.FilterCMC;
@@ -126,7 +127,7 @@ public enum DeckFormat {
private final Predicate<CardRules> cardPoolFilter;
private final Predicate<PaperCard> paperCardPoolFilter;
private final static String ADVPROCLAMATION = "Advantageous Proclamation";
private final static String SOVREALM = "Sovereign's Realm";
// private final static String SOVREALM = "Sovereign's Realm";
DeckFormat(Range<Integer> mainRange0, Range<Integer> sideRange0, int maxCardCopies0, Predicate<CardRules> cardPoolFilter0, Predicate<PaperCard> paperCardPoolFilter0) {
mainRange = mainRange0;
@@ -212,17 +213,19 @@ public enum DeckFormat {
int min = getMainRange().getMinimum();
int max = getMainRange().getMaximum();
boolean noBasicLands = false;
// boolean noBasicLands = false;
// Adjust minimum base on number of Advantageous Proclamation or similar cards
CardPool conspiracies = deck.get(DeckSection.Conspiracy);
if (conspiracies != null) {
min -= (5 * conspiracies.countByName(ADVPROCLAMATION));
noBasicLands = conspiracies.countByName(SOVREALM) > 0;
// Commented out to remove warnings from the code.
// noBasicLands = conspiracies.countByName(SOVREALM) > 0;
}
if (hasCommander()) {
byte cmdCI = 0;
int wildColors = 0;
if (equals(DeckFormat.Oathbreaker)) { // 1 Oathbreaker and 1 Signature Spell
PaperCard oathbreaker = deck.getOathbreaker();
if (oathbreaker == null) {
@@ -235,8 +238,7 @@ public enum DeckFormat {
return "has too many commanders";
}
cmdCI = oathbreaker.getRules().getColorIdentity().getColor();
}
else { // 1 Commander or 2 Partner Commanders
} else { // 1 Commander or 2 Partner Commanders
final List<PaperCard> commanders = deck.getCommanders();
if (commanders.isEmpty()) {
@@ -252,6 +254,7 @@ public enum DeckFormat {
return "has an illegal commander";
}
cmdCI |= pc.getRules().getColorIdentity().getColor();
wildColors += pc.getRules().getAddsWildCardColor() ? 1 : 0;
}
// special check for Partner
@@ -280,10 +283,16 @@ public enum DeckFormat {
continue;
}
}
if (!cp.getKey().getRules().getColorIdentity().hasNoColorsExcept(cmdCI)) {
ColorSet missingColors = cp.getKey().getRules().getColorIdentity().getMissingColors(cmdCI);
if (missingColors.countColors() > 0) {
if (missingColors.countColors() <= wildColors) {
wildColors -= missingColors.countColors();
cmdCI |= missingColors.getColor();
} else {
erroneousCI.add(cp.getKey());
}
}
}
if (deck.has(DeckSection.Sideboard)) {
for (final Entry<PaperCard, Integer> cp : deck.get(DeckSection.Sideboard)) {
if (!cp.getKey().getRules().getColorIdentity().hasNoColorsExcept(cmdCI)) {

View File

@@ -29,7 +29,6 @@ import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
/**
* A lightweight version of a card that matches real-world cards, to use outside of games (eg. inventory, decks, trade).
@@ -38,7 +37,7 @@ import java.io.Serializable;
*
* @author Forge
*/
public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet, IPaperCard, Serializable {
public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet, IPaperCard {
private static final long serialVersionUID = 2942081982620691205L;
// Reference to rules

View File

@@ -50,7 +50,6 @@ import forge.util.Aggregates;
import forge.util.MyRandom;
import forge.util.Visitor;
import forge.util.collect.FCollection;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.tuple.Pair;
@@ -327,7 +326,9 @@ public class Game {
int plId = 0;
for (RegisteredPlayer psc : players0) {
IGameEntitiesFactory factory = (IGameEntitiesFactory)psc.getPlayer();
Player pl = factory.createIngamePlayer(this, plId++);
// If the Registered Player already has a pre-assigned ID, use that. Otherwise, assign a new one.
Integer id = psc.getId();
Player pl = factory.createIngamePlayer(this, id == null ? plId++ : id);
allPlayers.add(pl);
ingamePlayers.add(pl);

View File

@@ -248,7 +248,7 @@ public final class GameActionUtil {
// do only non intrinsic
if (iSa.isSpell() && !iSa.isIntrinsic()) {
alternatives.add(iSa);
alternatives.addAll(getMayPlaySpellOptions(iSa, source, activator, altCostOnly));
alternatives.addAll(getMayPlaySpellOptions(iSa, stackCopy, activator, altCostOnly));
// currently only AltCost get added this way
}
}

View File

@@ -84,9 +84,11 @@ public abstract class SpellAbilityEffect {
String spellDesc = CardTranslation.translateSingleDescriptionText(rawSDesc,
sa.getHostCard().getName());
int idx = spellDesc.indexOf("(");
if (idx > 0) { //trim reminder text from StackDesc
spellDesc = spellDesc.substring(0, spellDesc.indexOf("(") - 1);
//trim reminder text from StackDesc
int idxL = spellDesc.indexOf(" (");
int idxR = spellDesc.indexOf(")");
if (idxL > 0 && idxR > idxL) {
spellDesc = spellDesc.replace(spellDesc.substring(idxL, idxR + 1), "");
}
if (reps != null) {

View File

@@ -26,9 +26,10 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
@Override
protected String getStackDescription(SpellAbility sa) {
final StringBuilder sb = new StringBuilder();
final List<Player> players = getDefinedPlayersOrTargeted(sa);
sb.append(Lang.joinHomogenous(getDefinedPlayersOrTargeted(sa)));
sb.append("chooses from a list.");
sb.append(Lang.joinHomogenous(players));
sb.append(players.size() == 1 ? " chooses" : " choose").append(" from a list.");
return sb.toString();
}

View File

@@ -120,8 +120,8 @@ public class DebuffEffect extends SpellAbilityEffect {
ProtectionFromColor = true;
}
if (ProtectionFromColor) {
// Split "Protection from all colors" into extra Protection from <color>
String allColors = "Protection from all colors";
// Split "Protection from each color" into extra Protection from <color>
String allColors = "Protection from each color";
if (tgtC.hasKeyword(allColors)) {
final List<String> allColorsProtect = Lists.newArrayList();
@@ -134,7 +134,7 @@ public class DebuffEffect extends SpellAbilityEffect {
}
// Extra for Spectra Ward
allColors = "Protection:Card.nonColorless:all colors:Aura";
allColors = "Protection:Card.nonColorless:each color:Aura";
if (tgtC.hasKeyword(allColors)) {
final List<String> allColorsProtect = Lists.newArrayList();

View File

@@ -180,12 +180,8 @@ public class RepeatEachEffect extends SpellAbilityEffect {
}
}
for (final Player p : repeatPlayers) {
if (optional) {
if (!p.getController().confirmAction(repeat, null, sa.getParam("RepeatOptionalMessage"), null)) {
if (optional && !p.getController().confirmAction(repeat, null, sa.getParam("RepeatOptionalMessage"), null)) {
continue;
} else if (sa.hasParam("RememberDeciders")) {
source.addRemembered(p);
}
}
if (nextTurn) {
game.getCleanup().addUntil(p, new GameCommand() {

View File

@@ -2258,7 +2258,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
// If no colon exists in Madness keyword, it must have been granted and assumed the cost from host
sbLong.append("Madness ").append(this.getManaCost()).append(" (").append(inst.getReminderText());
sbLong.append(")").append("\r\n");
} else if (keyword.startsWith("Emerge") || keyword.startsWith("Reflect")) {
} else if (keyword.startsWith("Reflect")) {
final String[] k = keyword.split(":");
sbLong.append(k[0]).append(" ").append(ManaCostParser.parse(k[1]));
sbLong.append(" (").append(inst.getReminderText()).append(")");
@@ -2325,6 +2325,16 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
}
sbLong.append("\r\n");
} else if (keyword.startsWith("Emerge")) {
final String[] k = keyword.split(":");
sbLong.append(k[0]);
if (k.length > 2) {
sbLong.append(" from ").append(k[2].toLowerCase());
}
sbLong.append(" ").append(ManaCostParser.parse(k[1]));
sbLong.append(" (").append(inst.getReminderText()).append(")");
sbLong.append("\r\n");
} else if (inst.getKeyword().equals(Keyword.COMPANION)) {
sbLong.append("Companion — ");
sbLong.append(((Companion)inst).getDescription());
@@ -2484,7 +2494,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|| keyword.equals("Undaunted") || keyword.startsWith("Monstrosity")
|| keyword.startsWith("Embalm") || keyword.equals("Prowess")
|| keyword.startsWith("Eternalize") || keyword.startsWith("Reinforce")
|| keyword.startsWith("Champion") || keyword.startsWith("Prowl") || keyword.startsWith("Adapt")
|| keyword.startsWith("Champion") || keyword.startsWith("Freerunning") || keyword.startsWith("Prowl") || keyword.startsWith("Adapt")
|| keyword.startsWith("Amplify") || keyword.startsWith("Ninjutsu") || keyword.startsWith("Chapter")
|| keyword.startsWith("Transfigure") || keyword.startsWith("Aura swap")
|| keyword.startsWith("Cycling") || keyword.startsWith("TypeCycling")
@@ -3002,7 +3012,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} else if (keyword.startsWith("Starting intensity")) {
sbAfter.append(TextUtil.fastReplace(keyword, ":", " ")).append("\r\n");
} else if (keyword.startsWith("Escalate") || keyword.startsWith("Buyback")
|| keyword.startsWith("Prowl")) {
|| keyword.startsWith("Freerunning") || keyword.startsWith("Prowl")) {
final String[] k = keyword.split(":");
final String manacost = k[1];
final Cost cost = new Cost(manacost, false);
@@ -6757,7 +6767,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
pW = true;
protectKey += "W";
}
} else if (kw.contains("all colors")) {
} else if (kw.contains("each color")) {
protectKey += "allcolors:";
} else if (kw.equals("Protection from everything")) {
protectKey += "everything:";

View File

@@ -676,7 +676,7 @@ public class CardFactoryUtil {
validSource = "Green" + (damage ? "Source" : "");
} else if (protectType.equals("colorless")) {
validSource = "Colorless" + (damage ? "Source" : "");
} else if (protectType.equals("all colors")) {
} else if (protectType.equals("each color")) {
validSource = "nonColorless" + (damage ? "Source" : "");
} else if (protectType.equals("everything")) {
return "";
@@ -2830,16 +2830,22 @@ public class CardFactoryUtil {
} else if (keyword.startsWith("Emerge")) {
final String[] kw = keyword.split(":");
String costStr = kw[1];
final SpellAbility sa = card.getFirstSpellAbility();
String validStr = kw.length > 2 ? kw[2] : "Creature";
String desc = "(Emerge";
if (kw.length > 2) {
desc += " from " + kw[2].toLowerCase();
}
desc += ")";
final SpellAbility sa = card.getFirstSpellAbility();
final SpellAbility newSA = sa.copyWithDefinedCost(new Cost(costStr, false));
newSA.getRestrictions().setIsPresent("Creature.YouCtrl+CanBeSacrificedBy");
newSA.getRestrictions().setIsPresent(validStr + ".YouCtrl+CanBeSacrificedBy");
newSA.putParam("Secondary", "True");
newSA.setAlternativeCost(AlternativeCost.Emerge);
newSA.setDescription(sa.getDescription() + " (Emerge)");
newSA.putParam("AfterDescription", "(Emerge)");
newSA.setDescription(sa.getDescription() + " " + desc);
newSA.putParam("AfterDescription", desc);
newSA.setIntrinsic(intrinsic);
inst.addSpellAbility(newSA);
} else if (keyword.startsWith("Embalm")) {
@@ -3105,6 +3111,27 @@ public class CardFactoryUtil {
// instantiate attach ability
final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card);
inst.addSpellAbility(sa);
} else if (keyword.startsWith("Freerunning")) {
final String[] k = keyword.split(":");
final Cost freerunningCost = new Cost(k[1], false);
final SpellAbility newSA = card.getFirstSpellAbility().copyWithDefinedCost(freerunningCost);
if (host.isInstant() || host.isSorcery()) {
newSA.putParam("Secondary", "True");
}
newSA.putParam("PrecostDesc", "Freerunning");
newSA.putParam("CostDesc", ManaCostParser.parse(k[1]));
// makes new SpellDescription
final StringBuilder sb = new StringBuilder();
sb.append(newSA.getCostDescription());
sb.append("(").append(inst.getReminderText()).append(")");
newSA.setDescription(sb.toString());
newSA.setAlternativeCost(AlternativeCost.Freerunning);
newSA.setIntrinsic(intrinsic);
inst.addSpellAbility(newSA);
} else if (keyword.startsWith("Fuse") && card.getStateName().equals(CardStateName.Original)) {
final SpellAbility sa = AbilityFactory.buildFusedAbility(card.getCard());
card.addSpellAbility(sa);
@@ -3900,7 +3927,8 @@ public class CardFactoryUtil {
final String[] k = keyword.split(":");
sbDesc.append(" from ").append(k[2]);
sbValid.append("| ValidSource$ ").append(k[1]);
final String param = k[2].contains("abilities") ? "ValidSA$ " : "ValidSource$ ";
sbValid.append("| ").append(param).append(k[1]);
}
String effect = "Mode$ CantTarget | ValidCard$ Card.Self | Secondary$ True"

View File

@@ -2120,6 +2120,14 @@ public class CardProperty {
}
List<String> nameList = Lists.newArrayList(names.split(";"));
return nameList.contains(card.getName());
} else if (property.equals("NotedNameAetherSearcher")) {
String names = sourceController.getDraftNotes().get("Aether Searcher");
if (names == null || names.isEmpty()) {
return false;
}
List<String> nameList = Lists.newArrayList(names.split(";"));
return nameList.contains(card.getName());
} else if (property.equals("NotedTypes")) {
// Should Paliano Vanguard be hardcoded here or part of the property?

View File

@@ -57,7 +57,7 @@ public final class CardUtil {
"Cycling", "Echo", "Kicker", "Flashback", "Madness", "Morph",
"Affinity", "Entwine", "Splice", "Ninjutsu",
"Transmute", "Replicate", "Recover", "Squad", "Suspend", "Aura swap",
"Fortify", "Transfigure", "Champion", "Evoke", "Prowl",
"Fortify", "Transfigure", "Champion", "Evoke", "Prowl", "Freerunning",
"Reinforce", "Unearth", "Level up", "Miracle", "Overload", "Cleave",
"Scavenge", "Encore", "Bestow", "Outlast", "Dash", "Surge", "Emerge", "Hexproof:",
"etbCounter", "Reflect", "Ward").build();

View File

@@ -326,8 +326,28 @@ public class CardView extends GameEntityView {
void updateDamage(Card c) {
set(TrackableProperty.Damage, c.getDamage());
updateLethalDamage(c);
//update CrackOverlay (currently 16 overlays)
set(TrackableProperty.CrackOverlay, c.getDamage() > 0 ? MyRandom.getRandom().nextInt(16) : 0);
//get crackoverlay by level of damage light 0, medium 1, heavy 2, max 3
int randCrackLevel = 0;
if (c.getDamage() > 0) {
switch (c.getDamage()) {
case 1:
case 2:
randCrackLevel = 0;
break;
case 3:
case 4:
randCrackLevel = 1;
break;
case 5:
case 6:
randCrackLevel = 2;
break;
default:
randCrackLevel = 3;
break;
}
}
set(TrackableProperty.CrackOverlay, randCrackLevel);
}
public int getAssignedDamage() {
@@ -1466,6 +1486,7 @@ public class CardView extends GameEntityView {
public String getKeywordKey() { return get(TrackableProperty.KeywordKey); }
public String getProtectionKey() { return get(TrackableProperty.ProtectionKey); }
public String getHexproofKey() { return get(TrackableProperty.HexproofKey); }
public boolean hasAnnihilator() { return get(TrackableProperty.HasAnnihilator); }
public boolean hasDeathtouch() { return get(TrackableProperty.HasDeathtouch); }
public boolean hasToxic() { return get(TrackableProperty.HasToxic); }
public boolean hasDevoid() { return get(TrackableProperty.HasDevoid); }
@@ -1473,6 +1494,7 @@ public class CardView extends GameEntityView {
public boolean hasDivideDamage() { return get(TrackableProperty.HasDivideDamage); }
public boolean hasDoubleStrike() { return get(TrackableProperty.HasDoubleStrike); }
public boolean hasDoubleTeam() { return get(TrackableProperty.HasDoubleTeam); }
public boolean hasExalted() { return get(TrackableProperty.HasExalted); }
public boolean hasFirstStrike() { return get(TrackableProperty.HasFirstStrike); }
public boolean hasFlying() { return get(TrackableProperty.HasFlying); }
public boolean hasFear() { return get(TrackableProperty.HasFear); }
@@ -1542,6 +1564,7 @@ public class CardView extends GameEntityView {
}
void updateKeywords(Card c, CardState state) {
c.updateKeywordsCache(state);
set(TrackableProperty.HasAnnihilator, c.hasKeyword(Keyword.ANNIHILATOR, state));
set(TrackableProperty.HasDeathtouch, c.hasKeyword(Keyword.DEATHTOUCH, state));
set(TrackableProperty.HasToxic, c.hasKeyword(Keyword.TOXIC, state));
set(TrackableProperty.HasDevoid, c.hasKeyword(Keyword.DEVOID, state));
@@ -1549,6 +1572,7 @@ public class CardView extends GameEntityView {
set(TrackableProperty.HasDivideDamage, c.hasKeyword("You may assign CARDNAME's combat damage divided as " +
"you choose among defending player and/or any number of creatures they control."));
set(TrackableProperty.HasDoubleStrike, c.hasKeyword(Keyword.DOUBLE_STRIKE, state));
set(TrackableProperty.HasExalted, c.hasKeyword(Keyword.EXALTED, state));
set(TrackableProperty.HasFirstStrike, c.hasKeyword(Keyword.FIRST_STRIKE, state));
set(TrackableProperty.HasFlying, c.hasKeyword(Keyword.FLYING, state));
set(TrackableProperty.HasFear, c.hasKeyword(Keyword.FEAR, state));

View File

@@ -223,7 +223,15 @@ public class CostAdjustment {
// Reduce cost
int sumGeneric = 0;
if (sa.hasParam("ReduceCost")) {
sumGeneric += AbilityUtils.calculateAmount(originalCard, sa.getParam("ReduceCost"), sa);
String cst = sa.getParam("ReduceCost");
String amt = sa.getParamOrDefault("ReduceAmount", cst);
int num = AbilityUtils.calculateAmount(originalCard, amt, sa);
if (sa.hasParam("ReduceAmount") && num > 0) {
cost.subtractManaCost(new ManaCost(new ManaCostParser(Strings.repeat(cst + " ", num))));
} else {
sumGeneric += num;
}
}
while (!reduceAbilities.isEmpty()) {
@@ -379,9 +387,15 @@ public class CostAdjustment {
}
private static void adjustCostByEmerge(final ManaCostBeingPaid cost, final SpellAbility sa) {
CardCollectionView canEmerge = CardLists.filter(sa.getActivatingPlayer().getCreaturesInPlay(), CardPredicates.canBeSacrificedBy(sa, false));
String kw = sa.getKeyword().getOriginal();
String k[] = kw.split(":");
String validStr = k.length > 2 ? k[2] : "Creature";
Player p = sa.getActivatingPlayer();
CardCollectionView canEmerge = CardLists.filter(p.getCardsIn(ZoneType.Battlefield),
CardPredicates.restriction(validStr, p, sa.getHostCard(), sa),
CardPredicates.canBeSacrificedBy(sa, false));
final CardCollectionView toSacList = sa.getHostCard().getController().getController().choosePermanentsToSacrifice(sa, 0, 1, canEmerge, "Creature");
final CardCollectionView toSacList = p.getController().choosePermanentsToSacrifice(sa, 0, 1, canEmerge, validStr);
if (toSacList.isEmpty()) {
return;

View File

@@ -0,0 +1,13 @@
package forge.game.keyword;
public class Emerge extends KeywordWithCostAndType {
protected void parse(String details) {
final String[] k = details.split(":");
if (k.length < 2) {
super.parse("Creature:" + k[0]);
} else {
// Flip parameters
super.parse(k[1] + ":" + k[0]);
}
}
}

View File

@@ -69,7 +69,7 @@ public enum Keyword {
DREDGE("Dredge", KeywordWithAmount.class, false, "If you would draw a card, instead you may put exactly {%d:card} from the top of your library into your graveyard. If you do, return this card from your graveyard to your hand. Otherwise, draw a card."),
ECHO("Echo", KeywordWithCost.class, false, "At the beginning of your upkeep, if this permanent came under your control since the beginning of your last upkeep, sacrifice it unless you pay %s."),
EMBALM("Embalm", KeywordWithCost.class, false, "%s, Exile this card from your graveyard: Create a token that's a copy of this card, except it's white, it has no mana cost, and it's a Zombie in addition to its other types. Embalm only as a sorcery."),
EMERGE("Emerge", KeywordWithCost.class, false, "You may cast this spell by sacrificing a creature and paying the emerge cost reduced by that creature's mana value."),
EMERGE("Emerge", Emerge.class, false, "You may cast this spell by sacrificing {1:%2$s} and paying the emerge cost reduced by that %2$s's mana value."),
ENCHANT("Enchant", KeywordWithType.class, false, "Target a %s as you cast this. This card enters the battlefield attached to that %s."),
ENCORE("Encore", KeywordWithCost.class, false, "%s, Exile this card from your graveyard: For each opponent, create a token copy that attacks that opponent this turn if able. They gain haste. Sacrifice them at the beginning of the next end step. Activate only as a sorcery."),
ENLIST("Enlist", SimpleKeyword.class, false, "As this creature attacks, you may tap a nonattacking creature you control without summoning sickness. When you do, add its power to this creatures until end of turn."),
@@ -95,6 +95,7 @@ public enum Keyword {
FOR_MIRRODIN("For Mirrodin", SimpleKeyword.class, false, "When this Equipment enters the battlefield, create a 2/2 red Rebel creature token, then attach this to it."),
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."),
FREERUNNING("Freerunning", KeywordWithCost.class, false, "You may cast this spell for its freerunning cost if you dealt combat damage to a player this turn with an Assassin or commander."),
FRENZY("Frenzy", KeywordWithAmount.class, false, "Whenever this creature attacks and isn't blocked, it gets +%d/+0 until end of turn."),
FRIENDS_FOREVER("Friends forever", Partner.class, true, "You can have two commanders if both have friends forever."),
FUSE("Fuse", SimpleKeyword.class, true, "You may cast one or both halves of this card from your hand."),

View File

@@ -2150,6 +2150,10 @@ public class Player extends GameEntity implements Comparable<Player> {
return !game.getDamageDoneThisTurn(true, true, sb.toString(), "Player", null, this, null).isEmpty();
}
public final boolean hasFreerunning() {
return !game.getDamageDoneThisTurn(true, true, "Card.Assassin+YouCtrl,Card.IsCommander+YouCtrl", "Player", null, this, null).isEmpty();
}
public final void setLibrarySearched(final int l) {
numLibrarySearchedOwn = l;
}

View File

@@ -2,7 +2,6 @@ package forge.game.player;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.game.CardTraitBase;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
@@ -15,6 +14,7 @@ import forge.util.Expressions;
import forge.util.TextUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
@@ -264,6 +264,10 @@ public class PlayerProperty {
if (source.getChosenPlayer() == null || !source.getChosenPlayer().equals(player)) {
return false;
}
} else if (property.equals("NotedDefender")) {
String tracker = player.getDraftNotes().getOrDefault("Cogwork Tracker", "");
return Iterables.contains(Arrays.asList(tracker.split(",")), String.valueOf(player));
} else if (property.startsWith("life")) {
int life = player.getLife();
int amount = AbilityUtils.calculateAmount(source, property.substring(6), spellAbility);

View File

@@ -1,13 +1,7 @@
package forge.game.player;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.LobbyPlayer;
import forge.deck.CardPool;
import forge.deck.Deck;
@@ -16,6 +10,11 @@ import forge.game.GameType;
import forge.item.IPaperCard;
import forge.item.PaperCard;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public class RegisteredPlayer {
private final Deck originalDeck; // never return or modify this instance (it's a reference to game resources)
private Deck currentDeck;
@@ -38,6 +37,7 @@ public class RegisteredPlayer {
private List<PaperCard> vanguardAvatars = null;
private PaperCard planeswalker = null;
private int teamNumber = -1; // members of teams with negative id will play FFA.
private Integer id = null;
private boolean randomFoil = false;
private boolean enableETBCountersEffect = false;
@@ -46,6 +46,14 @@ public class RegisteredPlayer {
restoreDeck();
}
public final Integer getId() {
return id;
}
public final void setId(Integer id0) {
id = id0;
}
public final Deck getDeck() {
return currentDeck;
}

View File

@@ -11,6 +11,7 @@ public enum AlternativeCost {
Evoke,
Flashback,
Foretold,
Freerunning,
Madness,
MTMtE, // More Than Meets the Eye (Transformers Universes Beyond)
Mutate,

View File

@@ -1549,6 +1549,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return isAlternativeCost(AlternativeCost.Evoke);
}
public final boolean isFreerunning() {
return isAlternativeCost(AlternativeCost.Freerunning);
}
public final boolean isMadness() {
return isAlternativeCost(AlternativeCost.Madness);
}

View File

@@ -444,6 +444,11 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
return false;
}
}
if (sa.isFreerunning()) {
if (!activator.hasFreerunning()) {
return false;
}
}
if (this.getIsPresent() != null) {
FCollection<GameObject> list;
if (getPresentDefined() != null) {

View File

@@ -70,7 +70,8 @@ public class TriggerSpellAbilityCastOrCopy extends Trigger {
}
/** {@inheritDoc}
* @param runParams*/
* @param runParams
**/
@Override
public final boolean performTest(final Map<AbilityKey, Object> runParams) {
final SpellAbility spellAbility = (SpellAbility) runParams.get(AbilityKey.SpellAbility);

View File

@@ -144,6 +144,7 @@ public enum TrackableProperty {
CountBasicLandTypes(TrackableTypes.IntegerType),
KeywordKey(TrackableTypes.StringType),
HasAnnihilator(TrackableTypes.BooleanType),
HasDeathtouch(TrackableTypes.BooleanType),
HasToxic(TrackableTypes.BooleanType),
HasDevoid(TrackableTypes.BooleanType),
@@ -151,6 +152,7 @@ public enum TrackableProperty {
HasDivideDamage(TrackableTypes.BooleanType),
HasDoubleStrike(TrackableTypes.BooleanType),
HasDoubleTeam(TrackableTypes.BooleanType),
HasExalted(TrackableTypes.BooleanType),
HasFirstStrike(TrackableTypes.BooleanType),
HasFlying(TrackableTypes.BooleanType),
HasFear(TrackableTypes.BooleanType),

View File

@@ -1,17 +1,7 @@
package forge.screens.home.sanctioned;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.SwingUtilities;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.Singletons;
import forge.deck.Deck;
import forge.deck.DeckGroup;
@@ -38,6 +28,14 @@ import forge.screens.deckeditor.views.VStatistics;
import forge.toolbox.FOptionPane;
import forge.util.Localizer;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Controls the draft submenu in the home UI.
*
@@ -142,6 +140,12 @@ public enum CSubmenuDraft implements ICDoc {
FModel.getGauntletMini().resetGauntletDraft();
String duelType = (String)VSubmenuDraft.SINGLETON_INSTANCE.getCbOpponent().getSelectedItem();
if (duelType == null) {
FOptionPane.showErrorDialog("Please select duel types for the draft match.", "Missing opponent items");
return;
}
final DeckGroup opponentDecks = FModel.getDecks().getDraft().get(humanDeck.getName());
if (gauntlet) {
if ("Gauntlet".equals(duelType)) {
@@ -161,7 +165,7 @@ public enum CSubmenuDraft implements ICDoc {
}
});
List<Deck> aiDecks = Lists.newArrayList();
Map<Integer, Deck> aiMap = Maps.newHashMap();
if (VSubmenuDraft.SINGLETON_INSTANCE.isSingleSelected()) {
// Restore Zero Indexing
final int aiIndex = Integer.parseInt(duelType)-1;
@@ -169,28 +173,43 @@ public enum CSubmenuDraft implements ICDoc {
if (aiDeck == null) {
throw new IllegalStateException("Draft: Computer deck is null!");
}
aiDecks.add(aiDeck);
aiMap.put(aiIndex, aiDeck);
} else {
final int numOpponents = Integer.parseInt(duelType);
List<Deck> randomOpponents = Lists.newArrayList(opponentDecks.getAiDecks());
Collections.shuffle(randomOpponents);
aiDecks = randomOpponents.subList(0, numOpponents);
for(Deck d : aiDecks) {
if (d == null) {
int maxDecks = opponentDecks.getAiDecks().size();
if (numOpponents > maxDecks) {
throw new IllegalStateException("Draft: Not enough decks for the number of opponents!");
}
List<Integer> aiIndices = Lists.newArrayList();
for(int i = 0; i < maxDecks; i++) {
aiIndices.add(i);
}
Collections.shuffle(aiIndices);
aiIndices = aiIndices.subList(0, numOpponents);
for(int i : aiIndices) {
final Deck aiDeck = opponentDecks.getAiDecks().get(i);
if (aiDeck == null) {
throw new IllegalStateException("Draft: Computer deck is null!");
}
aiMap.put(i + 1, aiDeck);
}
}
final List<RegisteredPlayer> starter = new ArrayList<>();
// Human is 0
final RegisteredPlayer human = new RegisteredPlayer(humanDeck.getDeck()).setPlayer(GamePlayerUtil.getGuiPlayer());
starter.add(human);
for(Deck aiDeck : aiDecks) {
starter.add(new RegisteredPlayer(aiDeck).setPlayer(GamePlayerUtil.createAiPlayer()));
}
for (final RegisteredPlayer pl : starter) {
pl.assignConspiracies();
human.setId(0);
for(Map.Entry<Integer, Deck> aiDeck : aiMap.entrySet()) {
RegisteredPlayer aiPlayer = new RegisteredPlayer(aiDeck.getValue()).setPlayer(GamePlayerUtil.createAiPlayer());
aiPlayer.setId(aiDeck.getKey());
starter.add(aiPlayer);
aiPlayer.assignConspiracies();
}
final HostedMatch hostedMatch = GuiBase.getInterface().hostMatch();

View File

@@ -94,10 +94,12 @@ public class CardFaceSymbols {
//ability icons
MANA_IMAGES.put("commander", FSkin.getImage(FSkinProp.IMG_ABILITY_COMMANDER));
MANA_IMAGES.put("ringbearer", FSkin.getImage(FSkinProp.IMG_ABILITY_RINGBEARER));
MANA_IMAGES.put("annihilator", FSkin.getImage(FSkinProp.IMG_ABILITY_ANNIHILATOR));
MANA_IMAGES.put("toxic", FSkin.getImage(FSkinProp.IMG_ABILITY_TOXIC));
MANA_IMAGES.put("deathtouch", FSkin.getImage(FSkinProp.IMG_ABILITY_DEATHTOUCH));
MANA_IMAGES.put("defender", FSkin.getImage(FSkinProp.IMG_ABILITY_DEFENDER));
MANA_IMAGES.put("doublestrike", FSkin.getImage(FSkinProp.IMG_ABILITY_DOUBLE_STRIKE));
MANA_IMAGES.put("exalted", FSkin.getImage(FSkinProp.IMG_ABILITY_EXALTED));
MANA_IMAGES.put("firststrike", FSkin.getImage(FSkinProp.IMG_ABILITY_FIRST_STRIKE));
MANA_IMAGES.put("fear", FSkin.getImage(FSkinProp.IMG_ABILITY_FEAR));
MANA_IMAGES.put("flash", FSkin.getImage(FSkinProp.IMG_ABILITY_FLASH));

View File

@@ -575,6 +575,14 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
CardFaceSymbols.drawAbilitySymbol("firststrike", g, abiX, abiY, abiScale, abiScale);
abiY += abiSpace;
}
if (card.getCurrentState().hasAnnihilator()) {
CardFaceSymbols.drawAbilitySymbol("annihilator", g, abiX, abiY, abiScale, abiScale);
abiY += abiSpace;
}
if (card.getCurrentState().hasExalted()) {
CardFaceSymbols.drawAbilitySymbol("exalted", g, abiX, abiY, abiScale, abiScale);
abiY += abiSpace;
}
if (card.getCurrentState().hasDeathtouch()) {
CardFaceSymbols.drawAbilitySymbol("deathtouch", g, abiX, abiY, abiScale, abiScale);
abiY += abiSpace;

View File

@@ -26,6 +26,7 @@ import java.util.List;
public class BoosterDraftTest implements IBoosterDraft {
private int n = 3;
private int round = 1;
@Override
@Test(timeOut = 1000)
@@ -43,6 +44,11 @@ public class BoosterDraftTest implements IBoosterDraft {
return null;
}
@Override
public int getRound() {
return round;
}
@Override
public CardPool nextChoice() {
this.n--;
@@ -95,4 +101,9 @@ public class BoosterDraftTest implements IBoosterDraft {
public LimitedPlayer getNeighbor(LimitedPlayer p, boolean left) {
return null;
}
@Override
public LimitedPlayer getPlayer(int i) {
return null;
}
}

View File

@@ -27,7 +27,7 @@ public class RewardSprite extends CharacterSprite {
if (data != null) {
rewards = JSONStringLoader.parse(RewardData[].class, data, default_reward);
} else { //Shouldn't happen, but make sure it doesn't fly by.
System.err.printf("Reward data is null. Using a default reward.");
System.err.print("Reward data is null. Using a default reward.");
rewards = JSONStringLoader.parse(RewardData[].class, default_reward, default_reward);
}
}

View File

@@ -599,7 +599,7 @@ public class AdventureEventData implements Serializable {
} else {
description += "\n";
}
description += String.format("Prizes\n3 round wins: 500 gold\n2 round wins: 200 gold\n1 round win: 100 gold\n");
description += "Prizes\n3 round wins: 500 gold\n2 round wins: 200 gold\n1 round win: 100 gold\n";
description += "Finishing event will award an unsellable copy of each card in your Jumpstart deck.";
}
return description;

View File

@@ -1106,10 +1106,13 @@ public class AdventureDeckEditor extends TabPageScreen<AdventureDeckEditor> {
if (canOnlyBePartnerCommander(card)) {
return; //don't auto-change commander unexpectedly
}
DeckSectionPage main = getMainDeckPage();
if (main == null)
return;
if (!cardManager.isInfinite()) {
removeCard(card);
}
getMainDeckPage().addCard(card);
main.addCard(card);
}
@Override

View File

@@ -238,12 +238,18 @@ public class ArenaScene extends UIScene implements IAfterMatch {
arenaPlane.addActor(lost);
}
boolean started = false;
private void startRound() {
if (started)
return;
started = true;
DuelScene duelScene = DuelScene.instance();
EnemySprite enemy = enemies.get(enemies.size - 1);
FThreads.invokeInEdtNowOrLater(() -> {
Forge.setTransitionScreen(new TransitionScreen(() -> {
duelScene.initDuels(WorldStage.getInstance().getPlayerSprite(), enemy);
started = false;
duelScene.initDuels(WorldStage.getInstance().getPlayerSprite(), enemy, true, null);
Forge.switchScene(duelScene);
}, Forge.takeScreenshot(), true, false, false, false, "", Current.player().avatar(), enemy.getAtlasPath(), Current.player().getName(), enemy.getName()));
});

View File

@@ -127,7 +127,7 @@ public class NewGameScene extends MenuScene {
modeNames[i] = modes.get(i).getName();
mode.setTextList(modeNames);
gender.setTextList(new String[]{Forge.getLocalizer().getInstance().getMessage("lblMale"), Forge.getLocalizer().getInstance().getMessage("lblFemale")});
gender.setTextList(new String[]{Forge.getLocalizer().getMessage("lblMale"), Forge.getLocalizer().getMessage("lblFemale")});
gender.addListener(new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
@@ -217,7 +217,7 @@ public class NewGameScene extends MenuScene {
}
Forge.switchScene(GameScene.instance());
};
Forge.setTransitionScreen(new TransitionScreen(runnable, null, false, true, "Generating World..."));
Forge.setTransitionScreen(new TransitionScreen(runnable, null, false, true, Forge.getLocalizer().getMessage("lblGeneratingWorld")));
return true;
}

View File

@@ -273,6 +273,7 @@ public class PlayerStatisticScene extends UIScene {
if (g != null) //skip variants
continue;
}
a.updateTrophyImage();
TextureRegion textureRegion = new TextureRegion(((FBufferedImage) a.getImage()).getTexture());
textureRegion.flip(false, true);
Image image = new Image(textureRegion);

View File

@@ -1,6 +1,5 @@
package forge.adventure.scene;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.ui.*;
@@ -99,7 +98,6 @@ public class SaveLoadScene extends UIScene {
ui.onButtonPress("return", SaveLoadScene.this::back);
difficulty.setSelectedIndex(1);
difficulty.setAlignment(Align.center);
difficulty.getStyle().fontColor = Color.GOLD;
difficulty.setX(scrollPane.getWidth() - difficulty.getWidth() + 5);
difficulty.setY(scrollPane.getTop() - difficulty.getHeight() - 5);
}
@@ -196,14 +194,19 @@ public class SaveLoadScene extends UIScene {
return true;
}
boolean loaded = false;
public void loadSave() {
if (loaded)
return;
loaded = true;
switch (mode) {
case Save:
if (TileMapScene.instance().currentMap().isInMap()) {
//Access to screen should be disabled, but stop the process just in case.
//Saving needs to be disabled inside maps until we can capture and load exact map state
//Otherwise location based events for quests can be skipped by saving and then loading outside the map
Dialog noSave = createGenericDialog("", "!!GAME NOT SAVED!!\nManual saving is only available on the world map","OK",null, null, null);
Dialog noSave = createGenericDialog("", Forge.getLocalizer().getMessage("lblGameNotSaved"), Forge.getLocalizer().getMessage("lblOK"),null, null, null);
showDialog(noSave);
return;
}
@@ -226,17 +229,19 @@ public class SaveLoadScene extends UIScene {
showDialog(saveDialog);
stage.setKeyboardFocus(textInput);
}
loaded = false;
break;
case Load:
try {
Forge.setTransitionScreen(new TransitionScreen(() -> {
loaded = false;
if (WorldSave.load(currentSlot)) {
SoundSystem.instance.changeBackgroundTrack();
Forge.switchScene(GameScene.instance());
} else {
Forge.clearTransitionScreen();
}
}, null, false, true, "Loading World..."));
}, null, false, true, Forge.getLocalizer().getMessage("lblLoadingWorld")));
} catch (Exception e) {
Forge.clearTransitionScreen();
}
@@ -244,6 +249,7 @@ public class SaveLoadScene extends UIScene {
case NewGamePlus:
try {
Forge.setTransitionScreen(new TransitionScreen(() -> {
loaded = false;
if (WorldSave.load(currentSlot)) {
WorldSave.getCurrentSave().clearChanges();
WorldSave.getCurrentSave().getWorld().generateNew(0);
@@ -261,8 +267,9 @@ public class SaveLoadScene extends UIScene {
} else {
Forge.clearTransitionScreen();
}
}, null, false, true, "Generating World..."));
}, null, false, true, Forge.getLocalizer().getMessage("lblGeneratingWorld")));
} catch (Exception e) {
loaded = false;
Forge.clearTransitionScreen();
}
break;

View File

@@ -1,24 +1,33 @@
package forge.adventure.scene;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.scenes.scene2d.ui.Dialog;
import com.badlogic.gdx.utils.Timer;
import com.github.tommyettinger.textra.TextraButton;
import com.github.tommyettinger.textra.TypingLabel;
import forge.Forge;
import forge.adventure.stage.GameHUD;
import forge.adventure.stage.GameStage;
import forge.adventure.stage.MapStage;
import forge.adventure.util.Config;
import forge.adventure.util.Controls;
import forge.adventure.world.WorldSave;
import forge.localinstance.properties.ForgeProfileProperties;
import forge.screens.TransitionScreen;
import forge.sound.SoundSystem;
import forge.util.ZipUtil;
import java.io.File;
import java.io.IOException;
/**
* First scene after the splash screen
*/
public class StartScene extends UIScene {
private static StartScene object;
Dialog exitDialog;
Dialog exitDialog, backupDialog, zipDialog, unzipDialog;
TextraButton saveButton, resumeButton, continueButton;
TypingLabel version = Controls.newTypingLabel("{GRADIENT}[%80]" + Forge.CURRENT_VERSION + "{ENDGRADIENT}");
public StartScene() {
@@ -30,6 +39,7 @@ public class StartScene extends UIScene {
ui.onButtonPress("Resume", StartScene.this::Resume);
ui.onButtonPress("Continue", StartScene.this::Continue);
ui.onButtonPress("Settings", StartScene.this::settings);
ui.onButtonPress("Backup", StartScene.this::backup);
ui.onButtonPress("Exit", StartScene.this::Exit);
ui.onButtonPress("Switch", StartScene.this::switchToClassic);
@@ -40,6 +50,9 @@ public class StartScene extends UIScene {
saveButton.setVisible(false);
resumeButton.setVisible(false);
version.setHeight(5);
version.skipToTheEnd();
ui.addActor(version);
}
public static StartScene instance() {
@@ -55,7 +68,7 @@ public class StartScene extends UIScene {
public boolean Save() {
if (TileMapScene.instance().currentMap().isInMap()) {
Dialog noSave = createGenericDialog("", "!!GAME NOT SAVED!!\nManual saving is only available on the world map","OK",null, null, null);
Dialog noSave = createGenericDialog("", Forge.getLocalizer().getMessage("lblGameNotSaved"), Forge.getLocalizer().getMessage("lblOK"),null, null, null);
showDialog(noSave);
} else {
SaveLoadScene.instance().setMode(SaveLoadScene.Modes.Save);
@@ -79,20 +92,27 @@ public class StartScene extends UIScene {
return true;
}
boolean loaded = false;
public boolean Continue() {
final String lastActiveSave = Config.instance().getSettingData().lastActiveSave;
if (WorldSave.isSafeFile(lastActiveSave)) {
if (loaded)
return true;
loaded = true;
try {
Forge.setTransitionScreen(new TransitionScreen(() -> {
loaded = false;
if (WorldSave.load(WorldSave.filenameToSlot(lastActiveSave))) {
SoundSystem.instance.changeBackgroundTrack();
Forge.switchScene(GameScene.instance());
} else {
Forge.clearTransitionScreen();
}
}, null, false, true, "Loading World..."));
}, null, false, true, Forge.getLocalizer().getMessage("lblLoadingWorld")));
} catch (Exception e) {
loaded = false;
Forge.clearTransitionScreen();
}
}
@@ -105,6 +125,86 @@ public class StartScene extends UIScene {
return true;
}
public boolean backup() {
if (backupDialog == null) {
backupDialog = createGenericDialog(Forge.getLocalizer().getMessage("lblData"),
null, Forge.getLocalizer().getMessage("lblBackup"),
Forge.getLocalizer().getMessage("lblRestore"),
() -> {
removeDialog();
Timer.schedule(new Timer.Task() {
@Override
public void run() {
generateBackup();
}
}, 0.2f);
},
() -> {
removeDialog();
Timer.schedule(new Timer.Task() {
@Override
public void run() {
restoreBackup();
}
}, 0.2f);
}, true, Forge.getLocalizer().getMessage("lblCancel"));
}
showDialog(backupDialog);
return true;
}
public boolean generateBackup() {
try {
File source = new FileHandle(ForgeProfileProperties.getUserDir() + "/adventure/Shandalar").file();
File target = new FileHandle(Forge.getDeviceAdapter().getDownloadsDir()).file();
ZipUtil.zip(source, target, ZipUtil.backupAdvFile);
zipDialog = createGenericDialog("",
Forge.getLocalizer().getMessage("lblSaveLocation") + "\n" + target.getAbsolutePath() + File.separator + ZipUtil.backupAdvFile,
Forge.getLocalizer().getMessage("lblOK"), null, this::removeDialog, null);
} catch (IOException e) {
zipDialog = createGenericDialog("",
Forge.getLocalizer().getMessage("lblErrorSavingFile") + "\n\n" + e.getMessage(),
Forge.getLocalizer().getMessage("lblOK"), null, this::removeDialog, null);
} finally {
showDialog(zipDialog);
}
return true;
}
public boolean restoreBackup() {
File source = new FileHandle(Forge.getDeviceAdapter().getDownloadsDir() + ZipUtil.backupAdvFile).file();
File target = new FileHandle(ForgeProfileProperties.getUserDir() + "/adventure/Shandalar").file().getParentFile();
if (unzipDialog == null) {
unzipDialog = createGenericDialog("",
Forge.getLocalizer().getMessage("lblDoYouWantToRestoreBackup"),
Forge.getLocalizer().getMessage("lblYes"), Forge.getLocalizer().getMessage("lblNo"),
() -> {
removeDialog();
Timer.schedule(new Timer.Task() {
@Override
public void run() {
extract(source, target);
}
}, 0.2f);
}, this::removeDialog);
}
showDialog(unzipDialog);
return true;
}
public boolean extract(File source, File target) {
String title = "", val = "";
try {
val = Forge.getLocalizer().getMessage("lblFiles") + ":\n" + ZipUtil.unzip(source, target);
} catch (IOException e) {
title = Forge.getLocalizer().getMessage("lblError");
val = e.getMessage();
} finally {
Config.instance().getSettingData().lastActiveSave = null;
Config.instance().saveSettings();
showDialog(createGenericDialog(title, val,
Forge.getLocalizer().getMessage("lblOK"), null, this::removeDialog, null));
}
return true;
}
public boolean Exit() {
if (exitDialog == null) {
exitDialog = createGenericDialog(Forge.getLocalizer().getMessage("lblExitForge"),
@@ -123,16 +223,7 @@ public class StartScene extends UIScene {
Forge.switchToClassic();
}
@Override
public void enter() {
boolean hasSaveButton = WorldSave.getCurrentSave().getWorld().getData() != null;
if (hasSaveButton) {
TileMapScene scene = TileMapScene.instance();
hasSaveButton = !scene.currentMap().isInMap() || scene.isAutoHealLocation();
}
saveButton.setVisible(hasSaveButton);
saveButton.setDisabled(TileMapScene.instance().currentMap().isInMap());
public void updateResumeContinue() {
boolean hasResumeButton = WorldSave.getCurrentSave().getWorld().getData() != null;
resumeButton.setVisible(hasResumeButton);
@@ -146,7 +237,18 @@ public class StartScene extends UIScene {
} else {
continueButton.setVisible(false);
}
}
@Override
public void enter() {
boolean hasSaveButton = WorldSave.getCurrentSave().getWorld().getData() != null;
if (hasSaveButton) {
TileMapScene scene = TileMapScene.instance();
hasSaveButton = !scene.currentMap().isInMap() || scene.isAutoHealLocation();
}
saveButton.setVisible(hasSaveButton);
saveButton.setDisabled(TileMapScene.instance().currentMap().isInMap());
updateResumeContinue();
if (Forge.createNewAdventureMap) {
this.NewGame();

View File

@@ -239,6 +239,9 @@ public class UIScene extends Scene {
}
public Dialog createGenericDialog(String title, String label, String stringYes, String stringNo, Runnable runnableYes, Runnable runnableNo) {
return createGenericDialog(title, label, stringYes, stringNo, runnableYes, runnableNo, false, "");
}
public Dialog createGenericDialog(String title, String label, String stringYes, String stringNo, Runnable runnableYes, Runnable runnableNo, boolean cancelButton, String stringCancel) {
Dialog dialog = new Dialog(title == null ? "" : title, Controls.getSkin());
if (label != null)
dialog.text(label);
@@ -248,6 +251,10 @@ public class UIScene extends Scene {
TextraButton no = Controls.newTextButton(stringNo, runnableNo);
dialog.button(no);
}
if (cancelButton) {
TextraButton cancel = Controls.newTextButton(stringCancel, this::removeDialog);
dialog.button(cancel);
}
return dialog;
}

View File

@@ -39,7 +39,7 @@ public class ConsoleCommandInterpreter {
public String complete(String text) {
String[] words = splitOnSpace(text);
Command currentCommand = root;
String completionString = "";
StringBuilder completionString = new StringBuilder();
for (String name : words) {
if (!currentCommand.children.containsKey(name)) {
for (String key : currentCommand.children.keySet()) {
@@ -49,7 +49,7 @@ public class ConsoleCommandInterpreter {
}
break;
}
completionString += name + " ";
completionString.append(name).append(" ");
currentCommand = currentCommand.children.get(name);
}
return text;

View File

@@ -1039,6 +1039,7 @@ public class MapStage extends GameStage {
}
}
boolean started = false;
public void beginDuel(EnemySprite mob) {
if (mob == null) return;
mob.clearCollisionHeight();
@@ -1054,6 +1055,9 @@ public class MapStage extends GameStage {
Forge.restrictAdvMenus = true;
player.clearCollisionHeight();
startPause(0.8f, () -> {
if (started)
return;
started = true;
Forge.setCursor(null, Forge.magnifyToggle ? "1" : "2");
SoundSystem.instance.play(SoundEffectType.ManaBurn, false);
DuelScene duelScene = DuelScene.instance();
@@ -1061,6 +1065,7 @@ public class MapStage extends GameStage {
if (!isLoadingMatch) {
isLoadingMatch = true;
Forge.setTransitionScreen(new TransitionScreen(() -> {
started = false;
duelScene.initDuels(player, mob);
if (isInMap && effect != null && !mob.ignoreDungeonEffect)
duelScene.setDungeonEffect(effect);

View File

@@ -64,6 +64,7 @@ public class WorldStage extends GameStage implements SaveFileContent {
final Rectangle tempBoundingRect = new Rectangle();
final Vector2 enemyMoveVector = new Vector2();
boolean collided = false;
@Override
protected void onActing(float delta) {
if (isPaused() || MapStage.getInstance().isDialogOnlyInput())
@@ -110,6 +111,9 @@ public class WorldStage extends GameStage implements SaveFileContent {
}
if (player.collideWith(mob)) {
if (collided)
return;
collided = true;
player.setAnimation(CharacterSprite.AnimationTypes.Attack);
player.playEffect(Paths.EFFECT_SPARKS, 0.5f);
mob.setAnimation(CharacterSprite.AnimationTypes.Attack);
@@ -126,6 +130,7 @@ public class WorldStage extends GameStage implements SaveFileContent {
DuelScene duelScene = DuelScene.instance();
FThreads.invokeInEdtNowOrLater(() -> {
Forge.setTransitionScreen(new TransitionScreen(() -> {
collided = false;
duelScene.initDuels(player, mob);
Forge.switchScene(duelScene);
}, Forge.takeScreenshot(), true, false, false, false, "", Current.player().avatar(), mob.getAtlasPath(), Current.player().getName(), mob.getName()));

View File

@@ -21,6 +21,7 @@ import com.badlogic.gdx.utils.Timer;
import com.github.tommyettinger.textra.Font;
import com.github.tommyettinger.textra.TextraButton;
import com.github.tommyettinger.textra.TextraLabel;
import com.github.tommyettinger.textra.TypingButton;
import com.github.tommyettinger.textra.TypingLabel;
import forge.Forge;
import forge.adventure.player.AdventurePlayer;
@@ -84,12 +85,53 @@ public class Controls {
}
static class TypingButtonFix extends TypingButton {
public TypingButtonFix(@Null String text) {
super(text == null ? "NULL" : text, Controls.getSkin(), Controls.getTextraFont());
addListener(new ClickListener(){
@Override
public void clicked(InputEvent event, float x, float y) {
super.clicked(event, x, y);
SoundSystem.instance.play(SoundEffectType.ButtonPress, false);
}
});
}
@Override
public void setStyle(Button.ButtonStyle style, boolean makeGridGlyphs) {
super.setStyle(style, makeGridGlyphs);
this.getTextraLabel().setFont(Controls.getTextraFont());
}
@Override
public String getText() {
return this.getTextraLabel().storedText;
}
@Override
public void setText(@Null String text) {
getTextraLabel().storedText = text;
getTextraLabel().layout.setTargetWidth(getTextraLabel().getMaxWidth());
getTextraLabel().getFont().markup(text, getTextraLabel().layout.clear());
getTextraLabel().setWidth(getTextraLabel().layout.getWidth() + (getTextraLabel().style != null && getTextraLabel().style.background != null ? getTextraLabel().style.background.getLeftWidth() + getTextraLabel().style.background.getRightWidth() : 0.0F));
layout();
}
}
static public TextraButton newTextButton(String text) {
TextraButton button = new TextButtonFix(text);
button.getTextraLabel().setWrap(false);
return button;
}
static public TypingButton newTypingButton(String text) {
TypingButton button = new TypingButtonFix(text);
button.getTextraLabel().setWrap(false);
return button;
}
static public Rectangle getBoundingRect(Actor actor) {
return new Rectangle(actor.getX(), actor.getY(), actor.getWidth(), actor.getHeight());
}

View File

@@ -354,11 +354,10 @@ public class SaveFileData extends HashMap<String,byte[]>
final long localSUID = localClassDescriptor.getSerialVersionUID();
final long streamSUID = resultClassDescriptor.getSerialVersionUID();
if (streamSUID != localSUID) { // check for serialVersionUID mismatch.
final StringBuffer s = new StringBuffer("Overriding serialized class version mismatch: ");
s.append("local serialVersionUID = ").append(localSUID);
s.append(" stream serialVersionUID = ").append(streamSUID);
String s = "Overriding serialized class version mismatch: " + "local serialVersionUID = " + localSUID +
" stream serialVersionUID = " + streamSUID;
System.err.println("[Invalid Class Exception]\n"+s);
System.err.println("[Invalid Class Exception]\n"+ s);
resultClassDescriptor = localClassDescriptor; // Use local class descriptor for deserialization
}
}

View File

@@ -31,6 +31,8 @@ public class TemplateTmxMapLoader extends TmxMapLoader {
this.root = xml.parse(tmxFile);
parameter.generateMipMaps=true;
parameter.textureMinFilter = Texture.TextureFilter.Nearest;
parameter.textureMagFilter = Texture.TextureFilter.Nearest;
final Array<FileHandle> textureFiles = getDependencyFileHandles(tmxFile);
for (FileHandle textureFile : textureFiles) {
Texture texture = new Texture(textureFile, parameter.generateMipMaps);

View File

@@ -23,13 +23,10 @@ public class NavigationMap {
this.half = spriteSize / 2;
}
RayCastCallback callback = new RayCastCallback() {
@Override
public float reportRayFixture(Fixture fixture, Vector2 vector2, Vector2 vector21, float v) {
RayCastCallback callback = (fixture, vector2, vector21, v) -> {
if (v < 1.0)
rayCollided = true;
return 0;
}
};
// public void initializeOverworldGeometryGraph() {

View File

@@ -20,7 +20,7 @@ import java.util.ArrayList;
public class BiomeTexture implements Serializable {
private final BiomeData data;
private final int tileSize;
public static Pixmap emptyPixmap = null;
public Pixmap emptyPixmap = null;
ArrayList<ArrayList<Pixmap>> images = new ArrayList<>();
ArrayList<ArrayList<Pixmap>> smallImages = new ArrayList<>();
ArrayList<IntMap<Pixmap>> edgeImages = new ArrayList<>();

View File

@@ -25,6 +25,7 @@ public enum AbilityEffect {
if (soundClip == null) {
soundClip = AudioClip.createClip(ForgeConstants.EFFECTS_DIR + wav);
}
if (soundClip != null)
soundClip.play(FModel.getPreferences().getPrefInt(ForgePreferences.FPref.UI_VOL_SOUNDS)/100f);
animation.start();
}

View File

@@ -446,10 +446,7 @@ public class FSkin {
int crackCount = 0;
for (int j = 0; j < 4; j++) {
int x = j * 200;
for(int i = 0; i < 4; i++) {
int y = i * 279;
Forge.getAssets().cracks().put(crackCount++, new TextureRegion(Forge.getAssets().getTexture(f17), x, y, 200, 279));
}
Forge.getAssets().cracks().put(crackCount++, new TextureRegion(Forge.getAssets().getTexture(f17), x, 0, 200, 279));
}
//borders

View File

@@ -350,13 +350,15 @@ public enum FSkinImage implements FSkinImageInterface {
//COMMANDER
IMG_ABILITY_COMMANDER (FSkinProp.IMG_ABILITY_COMMANDER),
IMG_ABILITY_RINGBEARER (FSkinProp.IMG_ABILITY_RINGBEARER),
//ANNIHILATOR
IMG_ABILITY_ANNIHILATOR (FSkinProp.IMG_ABILITY_ANNIHILATOR),
//TOXIC
IMG_ABILITY_TOXIC (FSkinProp.IMG_ABILITY_TOXIC),
//ABILITY ICONS
IMG_ABILITY_DEATHTOUCH (FSkinProp.IMG_ABILITY_DEATHTOUCH),
IMG_ABILITY_DEFENDER (FSkinProp.IMG_ABILITY_DEFENDER),
IMG_ABILITY_DOUBLE_STRIKE (FSkinProp.IMG_ABILITY_DOUBLE_STRIKE),
IMG_ABILITY_EXALTED (FSkinProp.IMG_ABILITY_EXALTED),
IMG_ABILITY_FIRST_STRIKE (FSkinProp.IMG_ABILITY_FIRST_STRIKE),
IMG_ABILITY_FEAR (FSkinProp.IMG_ABILITY_FEAR),
IMG_ABILITY_FLASH (FSkinProp.IMG_ABILITY_FLASH),

View File

@@ -75,10 +75,12 @@ public class CardFaceSymbols {
Forge.getAssets().manaImages().put("commander", FSkinImage.IMG_ABILITY_COMMANDER);
Forge.getAssets().manaImages().put("ringbearer", FSkinImage.IMG_ABILITY_RINGBEARER);
Forge.getAssets().manaImages().put("annihilator", FSkinImage.IMG_ABILITY_ANNIHILATOR);
Forge.getAssets().manaImages().put("toxic", FSkinImage.IMG_ABILITY_TOXIC);
Forge.getAssets().manaImages().put("deathtouch", FSkinImage.IMG_ABILITY_DEATHTOUCH);
Forge.getAssets().manaImages().put("defender", FSkinImage.IMG_ABILITY_DEFENDER);
Forge.getAssets().manaImages().put("doublestrike", FSkinImage.IMG_ABILITY_DOUBLE_STRIKE);
Forge.getAssets().manaImages().put("exalted", FSkinImage.IMG_ABILITY_EXALTED);
Forge.getAssets().manaImages().put("firststrike", FSkinImage.IMG_ABILITY_FIRST_STRIKE);
Forge.getAssets().manaImages().put("fear", FSkinImage.IMG_ABILITY_FEAR);
Forge.getAssets().manaImages().put("flash", FSkinImage.IMG_ABILITY_FLASH);

View File

@@ -910,6 +910,24 @@ public class CardRenderer {
abiY += abiSpace;
abiCount += 1;
}
if (card.getCurrentState().hasAnnihilator()) {
if (abiCount > 5) {
abiY = cy + (abiSpace * (abiCount - 6));
abiX = cx + ((cw * 2) / 1.92f);
}
CardFaceSymbols.drawSymbol("annihilator", g, abiX, abiY, abiScale, abiScale);
abiY += abiSpace;
abiCount += 1;
}
if (card.getCurrentState().hasExalted()) {
if (abiCount > 5) {
abiY = cy + (abiSpace * (abiCount - 6));
abiX = cx + ((cw * 2) / 1.92f);
}
CardFaceSymbols.drawSymbol("exalted", g, abiX, abiY, abiScale, abiScale);
abiY += abiSpace;
abiCount += 1;
}
if (card.getCurrentState().hasDeathtouch()) {
if (abiCount > 5) {
abiY = cy + (abiSpace * (abiCount - 6));

View File

@@ -35,7 +35,21 @@
"Stinging Study",
"Study Hall",
"Witch's Clinic",
"Time Vault"
"Time Vault",
"Sol Ring",
"Mana Crypt",
"Tolarian Academy",
"Tinker",
"Demonic Tutor",
"Vampiric Tutor",
"Bazaar of Baghdad",
"Library of Alexandria",
"Mana Vault",
"Fastbond",
"Mishra's Workshop",
"Yawgmoth's Bargain",
"Gaea's Cradle",
"Commander's Sphere"
],
"restrictedEditions": [
"HTR",
@@ -47,12 +61,12 @@
"DS0",
"HHO",
"CMB1",
"UNF",
"UST",
"UGL",
"UNH",
"PPC1",
"UND",
"PUST",
"UST"
"PUST"
],
"difficulties": [
{

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

View File

@@ -1,12 +1,13 @@
Name:Fifth Head of the Hydra
ManaCost:no cost
Types:Creature Hydra
Colors:green,blue
PT:1/4
K:Defender
S:Mode$ Continuous | Affected$ You | AddKeyword$ You can't lose the game. | Description$ You can't lose the game and your opponents can't win the game.
S:Mode$ Continuous | Affected$ Opponent | AddKeyword$ You can't win the game. | Secondary$ True | Description$ You can't lose the game and your opponents can't win the game.
T:Mode$ Phase | Phase$ End Of Turn | ValidPlayer$ Opponent | TriggerZones$ Battlefield | Execute$ TrigDig | TriggerDescription$ At the beginning of each opponents end step, that player exiles 4 cards.
SVar:TrigDig:DB$ Dig | Defined$ TriggeredPlayer | DigNum$ 4 | DestinationZone$ Exile
T:Mode$ Phase | Phase$ End Of Turn | ValidPlayer$ Opponent | TriggerZones$ Battlefield | Execute$ TrigDig | TriggerDescription$ At the beginning of each opponents end step, that player exiles the top 4 cards from their library
SVar:TrigDig:DB$ Dig | Defined$ ActivePlayer | DigNum$ 4 | DestinationZone$ Exile | ChangeNum$ All
R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Battlefield | ValidCard$ Card.Self | ReplaceWith$ Exile | Description$ If CARDNAME would leave the battlefield, instead exile it with three time counters on it. It gains suspend.
SVar:Exile:DB$ ChangeZone | Hidden$ True | WithCountersType$ TIME | WithCountersAmount$ 3 | Origin$ All | Destination$ Exile | Defined$ ReplacedCard | SubAbility$ GiveSuspend
SVar:GiveSuspend:DB$ PumpAll | ValidCards$ Card.withoutSuspend+YouOwn | KW$ Suspend | PumpZone$ Exile | Duration$ Permanent

View File

@@ -1,6 +1,7 @@
Name:First Head of the Hydra
ManaCost:no cost
Types:Creature Hydra
Colors:green,blue
PT:1/4
K:Defender
S:Mode$ Continuous | Affected$ You | AddKeyword$ You can't lose the game. | Description$ You can't lose the game and your opponents can't win the game.

View File

@@ -1,6 +1,7 @@
Name:Fourth Head of the Hydra
ManaCost:no cost
Types:Creature Hydra
Colors:green,blue
PT:1/4
K:Defender
S:Mode$ Continuous | Affected$ You | AddKeyword$ You can't lose the game. | Description$ You can't lose the game and your opponents can't win the game.

View File

@@ -2,7 +2,9 @@ Name:Fungus of Slimefoot's Boss Effect
ManaCost:no cost
Colors:black,green
Types:Enchantment
S:Mode$ Continuous | Affected$ Saproling | EffectZone$ Command | AddType$ Land & Forest | SetColor$ Green | Description$ Saprolings you control are Forest lands in addition to their other types.
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | Execute$ TrigAnimateAll | TriggerDescription$ At the beginning of your upkeep, Saprolings you control perpetually become Forest lands in addition to their other types.
SVar:TrigAnimateAll:DB$ AnimateAll | Duration$ Perpetual | Abilities$ ABMana | Types$ Land,Forest | SetColor$ Green | ValidCards$ Saproling.YouCtrl+nonForest
SVar:ABMana:AB$Mana | Cost$ T | Produced$ G | SpellDescription$ Add {G}.
T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Command | CheckSVar$ X | SVarCompare$ GE2 | Execute$ TrigConjure | TriggerDescription$ At the beginning of each end step, if two or more creatures died this turn, conjure a random Fungus onto the battlefield.
SVar:TrigConjure:DB$ MakeCard | Conjure$ True | AtRandom$ True | Spellbook$ Cankerbloom,Corpsejack Menace,Undercellar Myconid,Utopia Mycon,Sporemound,Deathspore Thallid,Sporoloth Ancient,Thallid Shell-Dweller,Psychotrope Thallid,Sporecrown Thallid,Sporesower Thallid,Thallid | Zone$ Battlefield
SVar:X:Count$ThisTurnEntered_Graveyard_from_Battlefield_Creature.YouCtrl

View File

@@ -1,6 +1,7 @@
Name:Second Head of the Hydra
ManaCost:no cost
Types:Creature Hydra
Colors:green,blue
PT:1/4
K:Defender
S:Mode$ Continuous | Affected$ You | AddKeyword$ You can't lose the game. | Description$ You can't lose the game and your opponents can't win the game.

View File

@@ -1,6 +1,7 @@
Name:Sixth Head of the Hydra
ManaCost:no cost
Types:Creature Hydra
Colors:green,blue
PT:1/4
K:Defender
S:Mode$ Continuous | Affected$ You | AddKeyword$ You can't lose the game. | Description$ You can't lose the game and your opponents can't win the game.

View File

@@ -0,0 +1,9 @@
Name:Sorin's Amulet
ManaCost:no cost
Types:Enchantment
A:AB$ MakeCard | Cost$ B B PayShards<4> PayLife<4> | IsPresent$ Vampire.YouCtrl | PresentZone$ Library | PresentCompare$ GE20 | ActivationZone$ Command | GameActivationLimit$ 1 | Conjure$ True | AtRandom$ True | Spellbook$ Sorin; Grim Nemesis,Sorin; Imperious Bloodlord,Sorin; Lord of Innistrad,Sorin Markov,Sorin; Solemn Visitor,Sorin the Mirthless,Sorin; Vampire Lord,Sorin; Vengeful Bloodlord,| WithCounter$ TIME | WithCounterNum$ 3 | Zone$ Exile | RememberMade$ True | SubAbility$ GiveSuspend | SpellDescription$ Conjure a Sorin planeswalker into exile with three time counters, it gains suspend. Activate this ability only if your library contains 20 or more vampires and only once each game.
SVar:GiveSuspend:DB$ Pump | Defined$ Remembered | KW$ Suspend | PumpZone$ Exile | Duration$ Permanent | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
S:Mode$ Continuous | EffectZone$ Command | Affected$ Vampire.YouCtrl | AddPower$ 1 | AddToughness$ 1 | CheckSVar$ YourLife | SVarCompare$ LT10 | AddKeyword$ Lifelink | Description$ As long as your life total is lower than 10, Vampires you control have +1/+1 and have lifelink.
SVar:YourLife:Count$YourLifeTotal
Oracle:{B}{B},{M}{M}{M}{M},Pay 4 life: Conjure a Sorin planeswalker into exile with three time counters, it gains suspend. Activate this ability only if your library contains 20 or more vampires and only once each game. \nAs long as your life total is lower than 10, Vampires you control have +1/+1 and have lifelink.

View File

@@ -0,0 +1,17 @@
Name:Sorin's Boss Effect
ManaCost:no cost
Types:Enchantment
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | CheckSVar$ YourLife | SVarCompare$ GE40 | Execute$ TrigSeek | TriggerDescription$ As long as Sorin's life total is 40 or more, Sorin seeks two nonland cards every upkeep and can't lose the game and his opponents can't win the game.
SVar:TrigSeek:DB$ Seek | Num$ 2 | Type$ Card.nonLand
S:Mode$ Continuous | Affected$ You | AddKeyword$ You can't lose the game. | CheckSVar$ YourLife | EffectZone$ Command | SVarCompare$ GE40 | Secondary$ True | Description$ You can't lose the game and your opponents can't win the game.
S:Mode$ Continuous | Affected$ Opponent | AddKeyword$ You can't win the game. | Secondary$ True | EffectZone$ Command | CheckSVar$ YourLife | Secondary$ True | SVarCompare$ GE40 | Description$ You can't lose the game and your opponents can't win the game.
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | CheckSVar$ YourLifeCompare | SVarCompare$ EQ2 | Execute$ TrigConjure | TriggerDescription$ As long as Sorin's life total is between 20 and 40, at Sorin's upkeep, conjure a card from Sorin's Spellbook into exile with 2 time counters on it, it gains suspend.
SVar:TrigConjure:DB$ MakeCard | Conjure$ True | AtRandom$ True | Spellbook$ Sorin; Grim Nemesis,Sorin; Imperious Bloodlord,Sorin; Lord of Innistrad,Sorin Markov,Sorin; Solemn Visitor,Sorin the Mirthless,Sorin; Vampire Lord,Sorin; Vengeful Bloodlord,Timothar; Baron of Bats,Olivia Voldaren,Patriarch's Bidding,Licia; Sanguine Tribune,Astarion; the Decadent,Strefan; Maurer Progenitor,Evelyn; the Covetous,Anje; Maid of Dishonor,Edgar Markov | WithCounter$ TIME | WithCounterNum$ 2 | Zone$ Exile | RememberMade$ True | SubAbility$ GiveSuspend
SVar:GiveSuspend:DB$ Pump | Defined$ Remembered | KW$ Suspend | PumpZone$ Exile | Duration$ Permanent | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
S:Mode$ Continuous | EffectZone$ Command | Affected$ Vampire.YouCtrl | AddPower$ 2 | AddToughness$ 2 | CheckSVar$ YourLife | SVarCompare$ LT20 | AddKeyword$ Lifelink | Description$ As long as Sorin's life total is lower than 20, Sorin's Vampires get +2/+2 and have lifelink.
SVar:Y:Count$Compare YourLife GE20.1.0
SVar:X:Count$Compare YourLife LE40.1.0
SVar:YourLifeCompare:SVar$X/Plus.Y
SVar:YourLife:Count$YourLifeTotal
Oracle:As long as Sorin's life total is 40 or more, Sorin seeks two nonland cards every upkeep and can't lose the game and his opponents can't win the game. \nAs long as Sorin's life total is between 20 and 40, at Sorin's upkeep, conjure a card from Sorin's Spellbook into exile with 2 time counters on it, it gains suspend.\nAs long as Sorin's life total is lower than 20, Sorin's Vampires get +2/+2 and have lifelink.

View File

@@ -2,6 +2,7 @@ Name:Third Head of the Hydra
ManaCost:no cost
Types:Creature Hydra
PT:1/4
Colors:green,blue
K:Defender
S:Mode$ Continuous | Affected$ You | AddKeyword$ You can't lose the game. | Description$ You can't lose the game and your opponents can't win the game.
S:Mode$ Continuous | Affected$ Opponent | AddKeyword$ You can't win the game. | Secondary$ True | Description$ You can't lose the game and your opponents can't win the game.

View File

@@ -4,55 +4,79 @@ Name=slimefoot
[Main]
4 Abrupt Decay|GK1|1
3 Akawalli, the Seething Tower|LCI|1
4 Bayou|30A|1
2 Binding the Old Gods|KHM|1
2 Blood Artist|J22|1
4 Bojuka Bog|WOC|1
2 Casualties of War|KHC|1
4 Corpsejack Menace|CM2|1
4 Cultivate|AFC|1
1 Damnation|TSR|1
2 Deathbloom Thallid|J22|1
4 Deathbonnet Sprout|MID|1
4 Deathcap Glade|VOW|1
4 Deathcap Marionette|LCI|1
3 Deathsprout|C20|1
2 Doubling Season|CMM|1
2 Fists of Ironwood|RVR|1
11 Forest|DMU|1
2 Forest|DMU|2
3 Forest|DMU|3
4 Fungal Infection|J22|1
2 Ghave, Guru of Spores|PLIST|1
3 Golgari Germination|DDJ|1
2 Golgari Germination|DDJ|1
4 Golgari Rot Farm|C20|1
2 Grave Pact|8ED|1
2 Grim Backwoods|C18|1
2 Growing Rites of Itlimoc|P22|1
4 Haunted Mire|DMU|1
4 Indatha Triome|IKO|1
2 Llanowar Wastes|BRO|1
4 Mold Shambler|DDM|1
3 Moldervine Reclamation|KHC|1
4 Mortality Spear|STX|1
4 Mycoid Shepherd|PM10|1
4 Mycoloth|MB1|1
2 Mycoid Shepherd|ARB|1
2 Mycoloth|MB1|1
4 Myconid Spore Tender|CLB|1
4 Nature's Lore|DMR|1
2 Nemata, Grove Guardian|CMM|1
2 Nemata, Primeval Warden|DMU|1
4 Overgrown Tomb|GRN|1
2 Pallid Mycoderm|MMA|1
3 Plains|DMU|1
4 Propagator Primordium|YLCI|1
2 Restless Cottage|WOE|1
2 Rot Shambler|BFZ|1
2 Rot Shambler|CMM|1
2 Saproling Symbiosis|DMR|1
4 Savannah|30A|1
1 Slimefoot's Survey|DMU|1
4 Slimefoot, Thallid Transplant|YDMU|1
4 Slimefoot, the Stowaway|DOM|1
3 Sporecrown Thallid|DOM|1
4 Sproutback Trudge|C21|1
3 Sporemound|SCD|1
2 Sproutback Trudge|C21|1
4 Swamp|DMU|1
1 Swamp|DMU|2
1 Swamp|DMU|3
4 Swarm Shambler|J22|1
4 Tendershoot Dryad|RIX|1
2 Tendershoot Dryad|RIX|1
2 Tendril of the Mycotyrant|LCI|1
4 Thallid|MMA|1
4 The Mycotyrant|LCI|1
3 The Skullspore Nexus|LCI|1
2 Thelon of Havenwood|TSR|1
4 Trudge Garden|C21|1
4 Underground Mortuary|MKM|1
1 Urborg, Tomb of Yawgmoth|M15|1
4 Verdant Catacombs|MM3|1
4 Verdant Force|C15|1
2 Verdant Force|C15|1
2 Verdeloth the Ancient|CMM|1
4 Village Rites|GN3|1
4 Woodland Cemetery|DOM|1
1 Yavimaya Sapherd|DOM|1
1 Yavimaya, Cradle of Growth|MH2|1
[Sideboard]
[Planes]

View File

@@ -0,0 +1,75 @@
[metadata]
Name=sorin
[Avatar]
[Main]
4 Ancient Tomb|UMA|1
4 Anguished Unmaking|SOI|1
2 Anowon, the Ruin Sage|VOC|1
4 Blood Artist|J21|1
4 Bloodline Keeper|V17|1
2 Bloodlord of Vaasgoth|VOC|1
4 Bojuka Bog|WOC|1
2 Butcher of Malakir|VOC|1
2 Call the Bloodline|F16|1
2 Champion of Dusk|VOC|1
2 Charismatic Conqueror|LCC|2
4 Clavileño, First of the Blessed|LCC|3
4 Cordial Vampire|MH1|1
4 Cruel Celebrant|LCC|1
4 Dark Ritual|A25|1
2 Echo of Dusk|LCI|1
2 Edgar, Charmed Groom|VOW|1
4 Feast of Blood|PIDW|1
3 Gifted Aetherborn|AER|1
4 Godless Shrine|RVR|1
4 Guul Draz Assassin|PLIST|1
4 Indulgent Aristocrat|VOC|1
4 Isolated Chapel|LCC|1
2 Legion's Landing|XLN|1
4 Marsh Flats|MH2|1
4 Mortify|E02|1
2 Nullpriest of Oblivion|J21|1
4 Olivia's Wrath|VOC|1
4 Orzhov Basilica|2X2|1
5 Plains|XLN|1
3 Plains|XLN|2
4 Plains|XLN|3
2 Plains|XLN|4
4 Quag Vampires|CNS|1
4 Sanctum Seeker|LCC|1
4 Scrubland|VMA|1
4 Shattered Sanctum|VOW|1
4 Shineshadow Snarl|STX|1
3 Sorin Markov|PLIST|1
2 Sorin of House Markov|MH3|1
3 Sorin the Mirthless|VOW|1
4 Sorin's Thirst|M20|1
4 Sorin's Vengeance|PLIST|1
2 Sorin, Grim Nemesis|SOI|1
3 Sorin, Imperious Bloodlord|M20|1
2 Sorin, Lord of Innistrad|LCC|1
4 Sorin, Solemn Visitor|KTK|1
1 Sorin, Vampire Lord|M20|1
2 Sorin, Vengeful Bloodlord|WAR|1
4 Sunlit Marsh|DMU|1
21 Swamp|XLN|1
4 Swords to Plowshares|CMM|1
2 Tithe Drinker|C17|1
2 Vampire Nocturnus|PDP13|1
4 Vampire of the Dire Moon|M20|1
2 Vanquisher's Banner|XLN|1
4 Vindicate|EMA|1
4 Vito, Thorn of the Dusk Rose|M21|1
2 Vona, Butcher of Magan|MOC|1
4 Welcoming Vampire|LCC|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -1,7 +1,5 @@
[metadata]
Name=aerie_guard
[Avatar]
[Main]
2 Battlefield Raptor|KHM|1
2 Bident of Thassa|A25|1
@@ -41,13 +39,3 @@ Name=aerie_guard
2 Welcoming Vampire|VOW|1
1 Winged Words|J21|1
1 Wrath of God|PZ1|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -1,7 +1,5 @@
[metadata]
Name=Amonkhet - Aven U1
[Avatar]
[Main]
1 Alrund's Epiphany|KHM|1
2 Aven Eternal|WAR|1
@@ -17,13 +15,3 @@ Name=Amonkhet - Aven U1
4 Silent Departure|EMA|1
4 Silver Raven|AFR|1
4 Winged Words|M20|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -1,7 +1,5 @@
[metadata]
Name=Amonkhet - Aven W1
[Avatar]
[Main]
4 Aven Battle Priest|ORI|1
1 Aven Brigadier|ONS|1
@@ -16,12 +14,4 @@ Name=Amonkhet - Aven W1
3 Union of the Third Path|BRO|1
1 Windbrisk Raptor|SHM|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -1,7 +1,5 @@
[metadata]
Name=amonkhet_minotaur
[Avatar]
[Main]
1 Ahn-Crop Crasher|AKH|1
2 Bloodrage Brawler|AKH|1
@@ -42,13 +40,3 @@ Name=amonkhet_minotaur
1 Trial of Zeal|AKH|1
2 Warfire Javelineer|AKH|1
2 Zealot of the God-Pharaoh|HOU|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -1,7 +1,5 @@
[metadata]
Name=salamander
[Avatar]
[Main]
1 Aetherspouts|C21|1
4 Amphin Cutthroat|M12|1
@@ -26,13 +24,3 @@ Name=salamander
3 Vineglimmer Snarl|LTC|1
1 Vronos, Masked Inquisitor|CMM|1
1 Wormfang Newt|JUD|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -1,7 +1,5 @@
[metadata]
Name=ancientvampire
[Avatar]
[Main]
1 Bloodline Keeper|V17|1
1 Bloodlord of Vaasgoth|C17|1
@@ -30,13 +28,3 @@ Name=ancientvampire
2 Vito, Thorn of the Dusk Rose|PRES|1
1 Vraan, Executioner Thane|ONE|1
1 Westgate Regent|AFR|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -1,7 +1,5 @@
[metadata]
Name=angelwarrior
[Avatar]
[Main]
1 Admonition Angel|ZNC|1
1 Akroma, Angel of Wrath|V15|1
@@ -26,13 +24,3 @@ Name=angelwarrior
1 Serra's Emissary|MH2|1
4 Starnheim Aspirant|KHM|1
4 Take Vengeance|M20|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -1,7 +1,5 @@
[metadata]
Name=archaeologist
[Avatar]
[Main]
4 Ancient Den|C21|1
2 Angel of the Ruins|MOC|1
@@ -27,13 +25,3 @@ Name=archaeologist
2 Tempered Steel|2XM|1
2 Teshar, Ancestor's Apostle|DOM|1
1 Traxos, Scourge of Kroog|DMC|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -1,7 +1,5 @@
[metadata]
Name=archerelite
[Avatar]
[Main]
2 Arbalest Elite|M12|1
2 Arcus Acolyte|J21|1
@@ -25,13 +23,3 @@ Name=archerelite
2 Tadeas, Juniper Ascendant|SLX|1
1 Titania's Chosen|C14|1
2 Titania's Chosen|CMA|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -1,7 +1,5 @@
[metadata]
Name=archmage
[Avatar]
[Main]
4 Aether Channeler|DMU|1
1 Azami, Lady of Scrolls|C17|1
@@ -19,13 +17,3 @@ Name=archmage
2 Time Warp|E02|1
2 Venser, Shaper Savant|TSR|1
4 Wizard Class|PLIST|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -1,11 +1,5 @@
[duel]
[metadata]
Name=Prince Valiant 3
Title=Prince Valiant
Difficulty=hard
Description=GW Knight of the Reliquary deck with Terravore and Worm Harvest
Icon=Prince Valiant.jpg
Deck Type=constructed
[main]
4 Savannah
2 Forest
@@ -34,4 +28,3 @@ Deck Type=constructed
1 Courser of Kruphix
2 Valorous Stance
1 Naturalize
[sideboard]

View File

@@ -1,7 +1,5 @@
[metadata]
Name=armored_knight
[Avatar]
[Main]
1 Acclaimed Contender|ELD|1
1 Adeline, Resplendent Cathar|MID|1
@@ -39,13 +37,3 @@ Name=armored_knight
1 Trailblazer's Boots|ONC|1
1 Valiant Endeavor|AFC|1
1 Varchild, Betrayer of Kjeldor|C18|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -1,7 +1,5 @@
[metadata]
Name=Karn
[Avatar]
[Main]
4 Ancient Tomb|UMA|1
2 Automatic Librarian|DMU|1
@@ -22,13 +20,3 @@ Name=Karn
4 Voltaic Servant|DOM|1
4 Wasteland|SLD|1
2 Weatherlight|DOM|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -1,7 +1,5 @@
[metadata]
Name=artificer
[Avatar]
[Main]
1 Aether Chaser|KLR|1
2 Aether Hub|KLD|1
@@ -38,13 +36,3 @@ Name=artificer
4 Welder Automaton|GNT|1
1 Whirler Virtuoso|KLR|1
2 Whirlermaker|KLR|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -1,7 +1,5 @@
[metadata]
Name=axgard_dwarf
[Avatar]
[Main]
4 Alpine Meadow|KHM|1
2 Axgard Armory|KHM|1
@@ -30,13 +28,3 @@ Name=axgard_dwarf
2 Vault Robber|KHM|1
2 Warchanter Skald|KHM|1
1 Warhorn Blast|KHM|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -0,0 +1,29 @@
[metadata]
Name=azoriusangel
[Main]
4 Adarkar Wastes|DMU|1
2 Angel of Mercy|JMP|1
2 Benevolent Bodyguard|EMA|1
2 Celestial Regulator|SNC|1
2 Condemn|RVR|1
1 Errant and Giada|MOM|1
4 Faithful Disciple|YMID|1
2 Firemane Commando|MOC|1
2 Fragment Reality|YNEO|1
1 Geist of Saint Traft|PRM|1
2 Glacial Fortress|XLN|1
2 Holy Armor|30A|1
3 Holy Strength|30A|1
2 Invasion of Xerex|MOM|1
2 Invocation of Saint Traft|SOI|1
2 Iridescent Angel|V15|1
1 Island|SOI|1
2 Island|SOI|2
1 Island|SOI|3
2 Migratory Route|KHC|1
1 Plains|SOI|1
4 Plains|SOI|2
7 Plains|SOI|3
2 Port Town|WHO|4
3 Revitalize|M19|1
2 Seraph of Dawn|J21|1

View File

@@ -1,7 +1,5 @@
[metadata]
Name=badger
[Avatar]
[Main]
4 Beast Within|DMC|1
4 Charging Badger|ANB|1
@@ -15,13 +13,3 @@ Name=badger
4 Nimble Mongoose|MB1|1
4 Prismatic Vista|MH1|1
4 Rysorian Badger|HML|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -1,11 +1,5 @@
[duel]
[metadata]
Name=Carnage 2
Title=Carnage
Difficulty=medium
Description=Mono R Grand Melee deck with Circle of Flame
Icon=Carnage.jpg
Deck Type=constructed
[main]
23 Mountain
4 Grand Melee
@@ -32,4 +26,3 @@ Deck Type=constructed
1 Thran War Machine
1 Ulamog's Crusher
1 Utvara Scalper
[sideboard]

View File

@@ -1,11 +1,4 @@
[duel]
[metadata]
Name=Freddy's Nightmares 2
Title=Freddy Krueger
Difficulty=medium
Description=RB nightmare creature theme with mutate abilities, life drain, and some burn
Icon=Freddy Krueger 2.jpg
Deck Type=constructed
[Main]
2 Banefire
3 Blood Crypt

View File

@@ -1,12 +1,5 @@
[duel]
[metadata]
Name=Frodo 2
Title=Frodo
Difficulty=medium
Description=WRG Zoo deck
Icon=Frodo.jpg
Deck Type=constructed
Profile=Reckless
[main]
3 Plateau
3 Taiga

View File

@@ -1,11 +1,5 @@
[duel]
[metadata]
Name=Cuthbert's Ka-tet 2
Title=Cuthbert Allgood of Roland's Ka-tet
Difficulty=medium
Description=WGR flicker and direct damage theme deck
Icon=Cuthbert Allgood 2.jpg
Deck Type=constructed
[Main]
2 Acidic Slime
2 Ajani's Welcome

View File

@@ -1,11 +1,5 @@
[duel]
[metadata]
Name=Hellboy 2
Title=Hellboy
Difficulty=medium
Description=BR direct damage deck
Icon=Hellboy.jpg
Deck Type=constructed
[main]
5 Swamp
7 Mountain

View File

@@ -1,11 +1,5 @@
[duel]
[metadata]
Name=Bolas's Infernal Reign 3
Title=Nicol Bolas, the Deceiver
Difficulty=hard
Description=URB dragons, counter, burn, and reanimate theme with Bolas planeswalkers and legends
Icon=Nicol Bolas, the Deceiver 3.jpg
Deck Type=constructed
[Main]
1 Ancient Brass Dragon
2 Avatar of Discord

View File

@@ -1,11 +1,5 @@
[duel]
[metadata]
Name=Diablo 3
Title=Diablo
Difficulty=hard
Description=BR Hellbent deck
Icon=Diablo.jpg
Deck Type=constructed
[main]
4 Badlands
4 Bloodstained Mire

View File

@@ -1,12 +1,5 @@
[duel]
[metadata]
Name=Deadpool 2
Title=Deadpool
Difficulty=medium
Description=BR deck with Ashenmoor Liege and Grixis Grimblade
Icon=Deadpool.jpg
Deck Type=constructed
Profile=Reckless
[main]
2 Vicious Kavu
2 Ashenmoor Gouger

View File

@@ -1,11 +1,5 @@
[duel]
[metadata]
Name=Abraham Lincoln 2
Title=Abraham Lincoln
Difficulty=medium
Description=WUR flying creatures deck with Flamebreak and Earthquake
Icon=Abraham Lincoln.jpg
Deck Type=constructed
[main]
1 Plateau
1 Flooded Strand

View File

@@ -1,11 +1,5 @@
[duel]
[metadata]
Name=Blaine the Pain 2
Title=Blaine the Pain
Difficulty=medium
Description=RB vehicles strategic aggro theme deck
Icon=Blaine the Pain 2.jpg
Deck Type=constructed
[Main]
2 Aradara Express
1 Beacon of Unrest

View File

@@ -1,7 +1,5 @@
[metadata]
Name=bandittrapper_hazezon
[Avatar]
[Main]
2 Ancient Greenwarden|ZNR|1
2 Argoth, Sanctum of Nature|BRO|1
@@ -30,13 +28,3 @@ Name=bandittrapper_hazezon
1 Titania, Voice of Gaea|BRO|1
2 Wayward Swordtooth|RIX|1
2 Wooded Foothills|KTK|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -1,7 +1,5 @@
[metadata]
Name=barbarian
[Avatar]
[Main]
4 Balduvian Horde|A25|1
2 Balthor the Stout|TOR|1
@@ -20,13 +18,3 @@ Name=barbarian
4 Plundering Barbarian|AFR|1
4 Sardian Cliffstomper|BRO|1
1 Zalto, Fire Giant Duke|AFR|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -1,11 +1,5 @@
[duel]
[metadata]
Name=Baltrice's Burning Light 3
Title=Baltrice
Difficulty=hard
Description=RW burn and lifegain themed deck with Spitemare and Boros Reckoner
Icon=Baltrice 3.jpg
Deck Type=constructed
[Main]
2 Akroma's Memorial
2 Archaeomancer's Map

View File

@@ -1,7 +1,5 @@
[metadata]
Name=barronlevilain
[Avatar]
[Main]
4 Archon of Coronation|CMR|1
4 Beloved Princess|ELD|1
@@ -23,12 +21,3 @@ Name=barronlevilain
4 Throne of the High City|CN2|1
4 Throne Warden|PZ2|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -1,7 +1,5 @@
[metadata]
Name=bat
[Avatar]
[Main]
4 Basilica Screecher|GTC|1
4 Blight Keeper|J22|1
@@ -15,13 +13,3 @@ Name=bat
8 Swamp|J22|2
10 Swamp|J22|3
4 Unholy Strength|30A|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

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