Merge branch 'master' into code-cleanup

# Conflicts:
#	forge-ai/src/main/java/forge/ai/ComputerUtil.java
#	forge-core/src/main/java/forge/card/DeckHints.java
#	forge-game/src/main/java/forge/game/ability/AbilityFactory.java
#	forge-game/src/main/java/forge/game/cost/CostUntapType.java
#	forge-gui/src/main/java/forge/player/HumanCostDecision.java
This commit is contained in:
Jetz
2024-10-14 22:36:07 -04:00
1902 changed files with 5598 additions and 4096 deletions

View File

@@ -25,7 +25,7 @@ This file is tarball, and may need to be extracted twice depending on which prog
We recommend extracting to a new folder rather than on top of an existing installation.
**For users who have played Forge before all of your user data is stored separately so you don't have to worry about losing it on upgrade.**
Java 8 or later is required to run Forge. Please make sure is the right version is installed in your enviroment. Check the user guide for more info.
Java 8 or later is required to run Forge. Please make sure is the right version is installed in your environment. Check the user guide for more info.
For Android users, download the APK file from [Snapshot Build](https://downloads.cardforge.org/dailysnapshots/) to your device.
On first run, Forge will download all needed data.
@@ -47,4 +47,4 @@ You can also play against the AI in a variety of formats, such as Sealed, Draft,
## Questions
If you have any questions, please join the Discord channel. Read the #rules and the frequently-asked-questions.
If your question is not answered there, feel free to ask in the #help channel.
If your question is not answered there, feel free to ask in the #help channel.

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.65-SNAPSHOT</version>
<version>1.6.66-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.65-SNAPSHOT</version>
<version>1.6.66-SNAPSHOT</version>
</parent>
<artifactId>forge-ai</artifactId>

View File

@@ -919,6 +919,10 @@ public class AiController {
if (checkCurseEffects(sa)) {
return AiPlayDecision.CurseEffects;
}
// TODO maybe other location for this?
if (!sa.isLegalAfterStack()) {
return AiPlayDecision.AnotherTime;
}
Card spellHost = card;
if (sa.isSpell()) {
spellHost = CardCopyService.getLKICopy(spellHost);
@@ -926,10 +930,6 @@ public class AiController {
spellHost.setLastKnownZone(game.getStackZone()); // need to add to stack to make check Restrictions respect stack cmc
spellHost.setCastFrom(card.getZone());
}
// TODO maybe other location for this?
if (!sa.isLegalAfterStack()) {
return AiPlayDecision.AnotherTime;
}
if (!sa.checkRestrictions(spellHost, player)) {
return AiPlayDecision.AnotherTime;
}
@@ -942,7 +942,7 @@ public class AiController {
}
}
if (sa instanceof Spell) {
if (card.isPermanent()) {
if (sa.getApi() == ApiType.PermanentCreature || sa.getApi() == ApiType.PermanentNoncreature) {
return canPlayFromEffectAI((Spell) sa, false, true);
}
if (!player.cantLoseForZeroOrLessLife() && player.canLoseLife() &&

View File

@@ -650,7 +650,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
// TODO sort negatives to remove from best Cards first?
for (final Card crd : negatives) {
for (Map.Entry<CounterType, Integer> e : table.filterToRemove(crd).entrySet()) {
if (ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
if (ComputerUtil.isNegativeCounter(e.getKey(), crd) && crd.canRemoveCounters(e.getKey())) {
int over = Math.min(e.getValue(), c - toRemove);
if (over > 0) {
toRemove += over;
@@ -761,7 +761,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
}
}
// if table is empty, than no counter was removed
// if table is empty, then no counter was removed
return table.isEmpty() ? null : PaymentDecision.counters(table);
}

View File

@@ -827,10 +827,9 @@ public class ComputerUtil {
}
public static CardCollection chooseUntapType(final Player ai, final String type, final Card activate, final boolean untap, final int amount, SpellAbility sa) {
CardCollection typeList =
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
typeList = CardLists.filter(typeList, CardPredicates.TAPPED);
typeList = CardLists.filter(typeList, CardPredicates.TAPPED, c -> c.getCounters(CounterEnumType.STUN) == 0 || c.canRemoveCounters(CounterType.get(CounterEnumType.STUN)));
if (untap) {
typeList.remove(activate);
@@ -842,12 +841,7 @@ public class ComputerUtil {
CardLists.sortByPowerDesc(typeList);
final CardCollection untapList = new CardCollection();
for (int i = 0; i < amount; i++) {
untapList.add(typeList.get(i));
}
return untapList;
return typeList.subList(0, amount);
}
public static CardCollection chooseReturnType(final Player ai, final String type, final Card activate, final Card target, final int amount, SpellAbility sa) {

View File

@@ -213,6 +213,8 @@ public class CountersRemoveAi extends SpellAbilityAi {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiUndyingList));
return true;
}
// TODO stun counters with canRemoveCounters check
// remove P1P1 counters from opposing creatures
CardCollection oppP1P1List = CardLists.filter(list,

View File

@@ -154,7 +154,7 @@ public class SetStateAi extends SpellAbilityAi {
}
// non-permanent facedown can't be turned face up
if (!card.getRules().getType().isPermanent()) {
if (!card.getRules().getType().isPermanent() || !card.canBeTurnedFaceUp()) {
return false;
}
} else {

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.65-SNAPSHOT</version>
<version>1.6.66-SNAPSHOT</version>
</parent>
<artifactId>forge-core</artifactId>

View File

@@ -770,7 +770,6 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
if (ctOther == null) {
return false;
}
for (final CoreType type : getCoreTypes()) {
if (ctOther.hasType(type)) {
return true;
@@ -779,6 +778,23 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
return false;
}
public boolean sharesAllCardTypesWith(final CardTypeView ctOther) {
if (ctOther == null) {
return false;
}
for (final CoreType type : getCoreTypes()) {
if (!ctOther.hasType(type)) {
return false;
}
}
for (final CoreType type : ctOther.getCoreTypes()) {
if (!this.hasType(type)) {
return false;
}
}
return true;
}
public boolean sharesSubtypeWith(final CardTypeView ctOther) {
if (ctOther == null) {
return false;

View File

@@ -30,6 +30,7 @@ public interface CardTypeView extends Iterable<String>, Serializable {
public boolean sharesLandTypeWith(final CardTypeView ctOther);
public boolean sharesPermanentTypeWith(final CardTypeView ctOther);
public boolean sharesCardTypeWith(final CardTypeView ctOther);
public boolean sharesAllCardTypesWith(final CardTypeView ctOther);
boolean isPermanent();
boolean isCreature();

View File

@@ -222,7 +222,7 @@ public class DeckHints {
return true;
}
for (String tok : card.getTokens()) {
if (tdb != null && tdb.containsRule(tok) && predicate.test(tdb.getToken(tok).getRules())) {
if (tdb != null && tdb.containsRule(tok) && rulesWithTokens(predicate).test(tdb.getToken(tok).getRules())) {
return true;
}
}

View File

@@ -106,6 +106,14 @@ public final class MagicColor {
}
}
public static String toSymbol(final byte color) {
return MagicColor.Color.fromByte(color).getSymbol();
}
public static String toSymbol(final String color) {
return toSymbol(fromName(color));
}
/**
* The Interface Color.
*/
@@ -165,6 +173,17 @@ public final class MagicColor {
symbol = symbol0;
}
public static Color fromByte(final byte color) {
switch (color) {
case MagicColor.WHITE: return WHITE;
case MagicColor.BLUE: return BLUE;
case MagicColor.BLACK: return BLACK;
case MagicColor.RED: return RED;
case MagicColor.GREEN: return GREEN;
default: return COLORLESS;
}
}
public String getName() {
return name;
}

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.65-SNAPSHOT</version>
<version>1.6.66-SNAPSHOT</version>
</parent>
<artifactId>forge-game</artifactId>

View File

@@ -1870,6 +1870,8 @@ public class GameAction {
public final CardCollection sacrifice(final Iterable<Card> list, final SpellAbility source, final boolean effect, Map<AbilityKey, Object> params) {
Multimap<Player, Card> lki = MultimapBuilder.hashKeys().arrayListValues().build();
final boolean showRevealDialog = source != null && source.hasParam("ShowSacrificedCards");
CardCollection result = new CardCollection();
for (Card c : list) {
if (c == null) {
@@ -1890,6 +1892,10 @@ public class GameAction {
if (changed != null) {
result.add(changed);
}
if (showRevealDialog) {
final String message = Localizer.getInstance().getMessage("lblSacrifice");
game.getAction().reveal(result, ZoneType.Graveyard, c.getOwner(), false, message, false);
}
}
for (Map.Entry<Player, Collection<Card>> e : lki.asMap().entrySet()) {
// Run triggers

View File

@@ -308,8 +308,10 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
abstract public void setCounters(final Map<CounterType, Integer> allCounters);
abstract public boolean canRemoveCounters(final CounterType type);
abstract public boolean canReceiveCounters(final CounterType type);
abstract public void subtractCounter(final CounterType counterName, final int n, final Player remover);
abstract public int subtractCounter(final CounterType counterName, final int n, final Player remover);
abstract public void clearCounters();
public boolean canReceiveCounters(final CounterEnumType type) {
@@ -330,8 +332,8 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
addCounter(CounterType.get(counterType), n, source, table);
}
public void subtractCounter(final CounterEnumType counterName, final int n, final Player remover) {
subtractCounter(CounterType.get(counterName), n, remover);
public int subtractCounter(final CounterEnumType counterName, final int n, final Player remover) {
return subtractCounter(CounterType.get(counterName), n, remover);
}
abstract public void addCounterInternal(final CounterType counterType, final int n, final Player source, final boolean fireEvents, GameEntityCounterTable table, Map<AbilityKey, Object> params);

View File

@@ -286,7 +286,15 @@ public final class AbilityFactory {
final String key = "Choices";
if (mapParams.containsKey(key)) {
List<String> names = Lists.newArrayList(mapParams.get(key).split(","));
spellAbility.setAdditionalAbilityList(key, names.stream().map(input -> getSubAbility(state, input, sVarHolder)).collect(Collectors.toList()));
spellAbility.setAdditionalAbilityList(key, names.stream().map(input -> {
AbilitySub sub = getSubAbility(state, input, sVarHolder);
if (api == ApiType.GenericChoice) {
// support scripters adding restrictions to filter illegal choices
sub.setRestrictions(new SpellAbilityRestriction());
makeRestrictions(sub);
}
return sub;
}).collect(Collectors.toList()));
}
}

View File

@@ -1295,7 +1295,7 @@ public class AbilityUtils {
}
}
} else if (defined.startsWith("ValidStack")) {
String valid = changedDef.split(" ", 2)[1];
String[] valid = changedDef.split(" ", 2)[1].split(",");
for (SpellAbilityStackInstance stackInstance : game.getStack()) {
SpellAbility instanceSA = stackInstance.getSpellAbility();
if (instanceSA != null && instanceSA.isValid(valid, player, card, sa)) {
@@ -2303,6 +2303,10 @@ public class AbilityUtils {
return doXMath(player.getNumDrawnThisTurn(), expr, c, ctb);
}
if (sq[0].equals("YouDrewLastTurn")) {
return doXMath(player.getNumDrawnLastTurn(), expr, c, ctb);
}
if (sq[0].equals("YouRollThisTurn")) {
return doXMath(player.getNumRollsThisTurn(), expr, c, ctb);
}
@@ -2521,6 +2525,23 @@ public class AbilityUtils {
return doXMath(unlocked, expr, c, ctb);
}
// Count$DistinctUnlockedDoors <Valid>
// Counts the distinct names of unlocked doors. Used for the "Promising Stairs"
if (sq[0].startsWith("DistinctUnlockedDoors")) {
final String[] workingCopy = l[0].split(" ", 2);
final String validFilter = workingCopy[1];
Set<String> viewedNames = new HashSet<>();
for (Card doorCard : CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validFilter, player, c, ctb)) {
for(CardStateName stateName : doorCard.getUnlockedRooms()) {
viewedNames.add(doorCard.getState(stateName).getName());
}
}
int distinctUnlocked = viewedNames.size();
return doXMath(distinctUnlocked, expr, c, ctb);
}
// Manapool
if (sq[0].startsWith("ManaPool")) {
final String color = l[0].split(":")[1];

View File

@@ -3,7 +3,6 @@ package forge.game.ability.effects;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.card.CardStateName;
import forge.card.CardType;
import forge.game.*;
@@ -26,7 +25,9 @@ import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import java.util.*;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class ChangeZoneEffect extends SpellAbilityEffect {
@@ -1084,8 +1085,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
String selectPrompt = sa.hasParam("SelectPrompt") ? sa.getParam("SelectPrompt") : MessageUtil.formatMessage(Localizer.getInstance().getMessage("lblSelectCardFromPlayerZone", "{player's}", Lang.joinHomogenous(origin, ZoneType::getTranslatedName).toLowerCase()), decider, player);
final String totalcmc = sa.getParam("WithTotalCMC");
final String totalpower = sa.getParam("WithTotalPower");
final String totalCardTypes = sa.getParam("WithTotalCardTypes");
int totcmc = AbilityUtils.calculateAmount(source, totalcmc, sa);
int totpower = AbilityUtils.calculateAmount(source, totalpower, sa);
int totCardTypes = AbilityUtils.calculateAmount(source, totalCardTypes, sa);
CardCollection chosenCards = new CardCollection();
if (changeType.startsWith("EACH")) {
@@ -1160,6 +1163,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
}
// If we're choosing multiple cards, only need to show the reveal dialog the first time through.
boolean shouldReveal = (i == 0);
Card c = null;
@@ -1170,6 +1174,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
c = Aggregates.random(fetchList);
} else if (defined && !chooseFromDef) {
c = Iterables.getFirst(fetchList, null);
} else if (totalCardTypes != null) {
String title = selectPrompt;
title += "\nCard types left: " + Math.max(totCardTypes, 0);
c = decider.getController().chooseSingleCardForZoneChange(destination, origin, sa, fetchList, shouldReveal ? delayedReveal : null, title, !mandatory, decider);
} else {
String title = selectPrompt;
if (changeNum > 1) { //indicate progress if multiple cards being chosen
@@ -1202,6 +1210,13 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (totalpower != null) {
totpower -= c.getCurrentPower();
}
if (totalCardTypes != null) {
totCardTypes -= Iterables.size(c.getType().getCoreTypes());
}
}
if (totalCardTypes != null && totCardTypes > 0) {
chosenCards.clear();
}
}
@@ -1537,7 +1552,8 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
&& !sa.hasParam("AtRandom")
&& (!sa.hasParam("Defined") || sa.hasParam("ChooseFromDefined"))
&& !sa.hasParam("WithTotalCMC")
&& !sa.hasParam("WithTotalPower");
&& !sa.hasParam("WithTotalPower")
&& !sa.hasParam("WithTotalCardTypes");
}
/**

View File

@@ -101,8 +101,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
// uses for multi sources -> one defined/target
// this needs given counter type
if (sa.hasParam("ValidSource")) {
CardCollectionView srcCards = game.getCardsIn(ZoneType.Battlefield);
srcCards = CardLists.getValidCards(srcCards, sa.getParam("ValidSource"), activator, host, sa);
CardCollectionView srcCards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("ValidSource"), activator, host, sa);
List<Card> tgtCards = getDefinedCardsOrTargeted(sa);
if (tgtCards.isEmpty()) {
@@ -147,11 +146,6 @@ public class CountersMoveEffect extends SpellAbilityEffect {
Map<CounterType, Integer> countersToAdd = Maps.newHashMap();
for (Card src : srcCards) {
// rule 121.5: If the first and second objects are the same object, nothing happens
if (src.equals(dest)) {
continue;
}
if ("All".equals(counterName)) {
final Map<CounterType, Integer> tgtCounters = Maps.newHashMap(src.getCounters());
for (Map.Entry<CounterType, Integer> e : tgtCounters.entrySet()) {
@@ -183,8 +177,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
params.put("CounterType", cType);
params.put("Source", source);
CardCollectionView tgtCards = game.getCardsIn(ZoneType.Battlefield);
tgtCards = CardLists.getValidCards(tgtCards, sa.getParam("ValidDefined"), activator, host, sa);
CardCollectionView tgtCards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("ValidDefined"), activator, host, sa);
if (counterNum.equals("Any")) {
tgtCards = activator.getController().chooseCardsForEffect(
@@ -203,6 +196,9 @@ public class CountersMoveEffect extends SpellAbilityEffect {
if (!dest.canReceiveCounters(cType)) {
continue;
}
if (!source.canRemoveCounters(cType)) {
continue;
}
Card cur = game.getCardState(dest, null);
if (cur == null || !cur.equalsWithGameTimestamp(dest)) {
@@ -287,7 +283,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
final List<CounterType> typeChoices = Lists.newArrayList();
// get types of counters
for (CounterType ct : tgtCounters.keySet()) {
if (dest.canReceiveCounters(ct)) {
if (dest.canReceiveCounters(ct) && source.canRemoveCounters(cType)) {
typeChoices.add(ct);
}
}
@@ -337,6 +333,9 @@ public class CountersMoveEffect extends SpellAbilityEffect {
if (!dest.canReceiveCounters(cType)) {
return;
}
if (!src.canRemoveCounters(cType)) {
return;
}
int cmax = src.getCounters(cType);
if (cmax <= 0) {

View File

@@ -127,7 +127,18 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
putCounter = !Expressions.compare(value, operator, operandValue);
} else {
putCounter = pc.chooseBinary(sa, prompt, BinaryChoiceType.AddOrRemove, params);
boolean canReceive = tgtCard.canReceiveCounters(ctype);
boolean canRemove = tgtCard.canRemoveCounters(ctype);
if (!canReceive && !canRemove) {
return;
}
if (canReceive && !canRemove) {
putCounter = true;
} else if (!canReceive && canRemove) {
putCounter = false;
} else {
putCounter = pc.chooseBinary(sa, prompt, BinaryChoiceType.AddOrRemove, params);
}
}
if (putCounter) {

View File

@@ -63,8 +63,7 @@ public class CountersRemoveAllEffect extends SpellAbilityEffect {
for (final Card tgtCard : cards) {
if (sa.hasParam("AllCounterTypes")) {
for (Map.Entry<CounterType, Integer> e : Lists.newArrayList(tgtCard.getCounters().entrySet())) {
numberRemoved += e.getValue();
tgtCard.subtractCounter(e.getKey(), e.getValue(), sa.getActivatingPlayer());
numberRemoved += tgtCard.subtractCounter(e.getKey(), e.getValue(), sa.getActivatingPlayer());
}
//tgtCard.getCounters().clear();
continue;
@@ -74,7 +73,7 @@ public class CountersRemoveAllEffect extends SpellAbilityEffect {
}
if (counterAmount > 0) {
tgtCard.subtractCounter(CounterType.getType(type), counterAmount, sa.getActivatingPlayer());
numberRemoved += tgtCard.subtractCounter(CounterType.getType(type), counterAmount, sa.getActivatingPlayer());
game.updateLastStateForCard(tgtCard);
}
}

View File

@@ -109,8 +109,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
// Removing energy
if (type.equals("All")) {
for (Map.Entry<CounterType, Integer> e : Lists.newArrayList(tgtPlayer.getCounters().entrySet())) {
tgtPlayer.subtractCounter(e.getKey(), e.getValue(), activator);
totalRemoved += e.getValue();
totalRemoved += tgtPlayer.subtractCounter(e.getKey(), e.getValue(), activator);
}
} else {
if (num.equals("All")) {
@@ -119,8 +118,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
if (type.equals("Any")) {
totalRemoved += removeAnyType(tgtPlayer, cntToRemove, sa);
} else {
tgtPlayer.subtractCounter(counterType, cntToRemove, activator);
totalRemoved += cntToRemove;
totalRemoved += tgtPlayer.subtractCounter(counterType, cntToRemove, activator);
}
}
}
@@ -164,11 +162,11 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
if (gameCard == null || !tgtCard.equalsWithGameTimestamp(gameCard)) {
continue;
}
final Zone zone = game.getZoneOf(gameCard);
if (type.equals("All")) {
for (Map.Entry<CounterType, Integer> e : Lists.newArrayList(gameCard.getCounters().entrySet())) {
gameCard.subtractCounter(e.getKey(), e.getValue(), activator);
totalRemoved += e.getValue();
totalRemoved += gameCard.subtractCounter(e.getKey(), e.getValue(), activator);
}
game.updateLastStateForCard(gameCard);
continue;
@@ -179,6 +177,9 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
if (type.equals("Any")) {
totalRemoved += removeAnyType(gameCard, cntToRemove, sa);
} else {
if (!tgtCard.canRemoveCounters(counterType)) {
continue;
}
cntToRemove = Math.min(cntToRemove, gameCard.getCounters(counterType));
if (zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Exile)) {
@@ -220,14 +221,18 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
final Player activator = sa.getActivatingPlayer();
final PlayerController pc = activator.getController();
final Map<CounterType, Integer> tgtCounters = Maps.newHashMap(entity.getCounters());
for (CounterType ct : ImmutableList.copyOf(tgtCounters.keySet())) {
if (!entity.canRemoveCounters(ct)) {
tgtCounters.remove(ct);
}
}
while (cntToRemove > 0 && !tgtCounters.isEmpty()) {
Map<String, Object> params = Maps.newHashMap();
params.put("Target", entity);
String prompt = Localizer.getInstance().getMessage("lblSelectCountersTypeToRemove");
CounterType chosenType = pc.chooseCounterType(
ImmutableList.copyOf(tgtCounters.keySet()), sa, prompt, params);
CounterType chosenType = pc.chooseCounterType(ImmutableList.copyOf(tgtCounters.keySet()), sa, prompt, params);
int max = Math.min(cntToRemove, tgtCounters.get(chosenType));
// remove selection so player can't cheat additional trigger by choosing the same type multiple times

View File

@@ -55,11 +55,14 @@ public class ManaEffect extends SpellAbilityEffect {
if (abMana.isComboMana()) {
int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(card, sa.getParam("Amount"), sa) : 1;
if(amount <= 0)
continue;
String express = abMana.getExpressChoice();
String[] colorsProduced = abMana.getComboColors(sa).split(" ");
final StringBuilder choiceString = new StringBuilder();
final StringBuilder choiceSymbols = new StringBuilder();
ColorSet colorOptions = ColorSet.fromNames(colorsProduced);
String[] colorsNeeded = express.isEmpty() ? null : express.split(" ");
boolean differentChoice = abMana.getOrigProduced().contains("Different");
@@ -70,12 +73,14 @@ public class ManaEffect extends SpellAbilityEffect {
for (Map.Entry<Byte, Integer> e : choices.entrySet()) {
Byte chosenColor = e.getKey();
String choice = MagicColor.toShortString(chosenColor);
String symbol = MagicColor.toSymbol(chosenColor);
Integer count = e.getValue();
while (count > 0) {
if (choiceString.length() > 0) {
choiceString.append(" ");
}
choiceString.append(choice);
choiceSymbols.append(symbol);
--count;
}
}
@@ -106,25 +111,26 @@ public class ManaEffect extends SpellAbilityEffect {
choiceString.append(" ");
}
choiceString.append(choice);
choiceSymbols.append(MagicColor.toSymbol(choice));
if (sa.hasParam("TwoEach")) {
choiceString.append(" ").append(choice);
choiceSymbols.append(MagicColor.toSymbol(choice));
}
}
}
if (choiceString.toString().isEmpty() && "Combo ColorIdentity".equals(abMana.getOrigProduced())) {
// No mana could be produced here (non-EDH match?), so cut short
return;
continue;
}
game.getAction().notifyOfValue(sa, card, Localizer.getInstance().getMessage("lblPlayerPickedChosen", p.getName(), choiceString), p);
game.getAction().notifyOfValue(sa, p, choiceSymbols.toString(), p);
abMana.setExpressChoice(choiceString.toString());
}
else if (abMana.isAnyMana()) {
// AI color choice is set in ComputerUtils so only human players need to make a choice
String colorsNeeded = abMana.getExpressChoice();
String choice = "";
ColorSet colorMenu = null;
byte mask = 0;
@@ -137,10 +143,9 @@ public class ManaEffect extends SpellAbilityEffect {
if (0 == val) {
throw new RuntimeException("ManaEffect::resolve() /*any mana*/ - " + p + " color mana choice is empty for " + card.getName());
}
choice = MagicColor.toShortString(val);
game.getAction().notifyOfValue(sa, card, Localizer.getInstance().getMessage("lblPlayerPickedChosen", p.getName(), choice), p);
abMana.setExpressChoice(choice);
game.getAction().notifyOfValue(sa, card, MagicColor.toSymbol(val), p);
abMana.setExpressChoice(MagicColor.toShortString(val));
}
else if (abMana.isSpecialMana()) {
String type = abMana.getOrigProduced().split("Special ")[1];
@@ -183,7 +188,7 @@ public class ManaEffect extends SpellAbilityEffect {
int nMana = 0;
for (Object o : card.getRemembered()) {
if (o instanceof String) {
sb.append(o.toString());
sb.append(o);
nMana++;
}
}

View File

@@ -21,15 +21,6 @@ public class VillainousChoiceEffect extends SpellAbilityEffect {
for (Player p : getDefinedPlayersOrTargeted(sa)) {
int choiceAmount = p.getAdditionalVillainousChoices() + 1;
List<SpellAbility> saToRemove = Lists.newArrayList();
for (SpellAbility saChoice : abilities) {
if (saChoice.getRestrictions() != null && !saChoice.getRestrictions().checkOtherRestrictions(sa.getHostCard(), saChoice, sa.getActivatingPlayer())) {
saToRemove.add(saChoice);
}
}
abilities.removeAll(saToRemove);
// For the AI chooseSAForEffect really should take the least good ability. Currently it just takes the first
List<SpellAbility> chosenSAs = Lists.newArrayList();
for(int i = 0; i < choiceAmount; i++) {

View File

@@ -803,6 +803,11 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
return setState(CardStateName.FaceDown, false);
}
public boolean canBeTurnedFaceUp() {
Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
return !getGame().getReplacementHandler().cantHappenCheck(ReplacementType.TurnFaceUp, repParams);
}
public void forceTurnFaceUp() {
turnFaceUp(false, null);
}
@@ -811,14 +816,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
return turnFaceUp(true, cause);
}
public boolean turnFaceUp(boolean runTriggers, SpellAbility cause) {
if (!isFaceDown()) {
if (!isFaceDown() || !canBeTurnedFaceUp()) {
return false;
}
// Check replacement effects
Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
if (game.getReplacementHandler().cantHappenCheck(ReplacementType.TurnFaceUp, repParams)) return false;
CardCollectionView cards = hasMergedCard() ? getMergedCards() : new CardCollection(this);
boolean retResult = false;
long ts = game.getNextTimestamp();
@@ -853,10 +854,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
triggerHandler.registerActiveTrigger(this, false);
}
if (runTriggers) {
// Run replacement effects
Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
game.getReplacementHandler().run(ReplacementType.TurnFaceUp, repParams);
// Run triggers
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(this);
runParams.put(AbilityKey.Cause, cause);
@@ -1598,6 +1598,21 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
return true;
}
@Override
public final boolean canRemoveCounters(final CounterType type) {
if (isPhasedOut()) {
return false;
}
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
repParams.put(AbilityKey.CounterType, type);
repParams.put(AbilityKey.Result, 0);
repParams.put(AbilityKey.IsDamage, false);
if (game.getReplacementHandler().cantHappenCheck(ReplacementType.RemoveCounter, repParams)) {
return false;
}
return true;
}
@Override
public void addCounterInternal(final CounterType counterType, final int n, final Player source, final boolean fireEvents, GameEntityCounterTable table, Map<AbilityKey, Object> params) {
int addAmount = n;
@@ -1734,11 +1749,11 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
}
@Override
public final void subtractCounter(final CounterType counterName, final int n, final Player remover) {
subtractCounter(counterName, n, remover, false);
public final int subtractCounter(final CounterType counterName, final int n, final Player remover) {
return subtractCounter(counterName, n, remover, false);
}
public final void subtractCounter(final CounterType counterName, final int n, final Player remover, final boolean isDamage) {
public final int subtractCounter(final CounterType counterName, final int n, final Player remover, final boolean isDamage) {
int oldValue = getCounters(counterName);
int newValue = Math.max(oldValue - n, 0);
@@ -1756,12 +1771,14 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
newValue = 0;
}
break;
case Replaced:
return 0;
default:
break;
}
final int delta = oldValue - newValue;
if (delta == 0) { return; }
if (delta == 0) { return 0; }
int powerBonusBefore = getPowerBonusFromCounters();
int toughnessBonusBefore = getToughnessBonusFromCounters();
@@ -1798,6 +1815,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
runParams.put(AbilityKey.CounterAmount, delta);
runParams.put(AbilityKey.NewCounterAmount, newValue);
getGame().getTriggerHandler().runTrigger(TriggerType.CounterRemovedOnce, runParams, false);
return delta;
}
@Override
@@ -6085,6 +6104,13 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
return getType().sharesCardTypeWith(c1.getType());
}
public final boolean sharesAllCardTypesWith(final Card c1) {
if (c1 == null) {
return false;
}
return getType().sharesAllCardTypesWith(c1.getType());
}
public final boolean sharesControllerWith(final Card c1) {
return c1 != null && getController().equals(c1.getController());
}

View File

@@ -119,7 +119,7 @@ public class CardCopyService {
c.setSetCode(in.getSetCode());
for (final CardStateName state : in.getStates()) {
copyState(in, state, c, state);
copyState(in, state, c, state, false);
}
c.setState(in.getCurrentStateName(), false);

View File

@@ -116,6 +116,10 @@ public final class CardPredicates {
return c -> c.sharesCardTypeWith(card);
}
public static Predicate<Card> sharesAllCardTypesWith(final Card card) {
return c -> c.sharesAllCardTypesWith(card);
}
public static Predicate<Card> sharesCreatureTypeWith(final Card card) {
return c -> c.sharesCreatureTypeWith(card);
}

View File

@@ -35,10 +35,7 @@ import forge.util.collect.FCollectionView;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.*;
public class CardProperty {
@@ -847,6 +844,11 @@ public class CardProperty {
}
}
}
} else if (property.startsWith("sharesAllCardTypesWithOther")) {
final String restriction = property.split("sharesAllCardTypesWithOther ")[1];
CardCollection list = AbilityUtils.getDefinedCards(source, restriction, spellAbility);
list.remove(card);
return Iterables.any(list, CardPredicates.sharesAllCardTypesWith(card));
} else if (property.startsWith("sharesLandTypeWith")) {
final String restriction = property.split("sharesLandTypeWith ")[1];
if (!AbilityUtils.getDefinedCards(source, restriction, spellAbility).anyMatch(CardPredicates.sharesLandTypeWith(card))) {
@@ -1504,7 +1506,7 @@ public class CardProperty {
}
} else if (property.startsWith("power") || property.startsWith("toughness") || property.startsWith("cmc")
|| property.startsWith("totalPT") || property.startsWith("numColors")
|| property.startsWith("basePower") || property.startsWith("baseToughness")) {
|| property.startsWith("basePower") || property.startsWith("baseToughness") || property.startsWith("numTypes")) {
int x;
int y = 0;
String rhs = "";
@@ -1530,6 +1532,9 @@ public class CardProperty {
} else if (property.startsWith("numColors")) {
rhs = property.substring(11);
y = card.getColor().countColors();
} else if (property.startsWith("numTypes")) {
rhs = property.substring(10);
y = Iterables.size(card.getType().getCoreTypes());
}
x = AbilityUtils.calculateAmount(source, rhs, spellAbility);
@@ -1920,6 +1925,18 @@ public class CardProperty {
if (!card.isGoaded()) {
return false;
}
} else if (property.equals("FullyUnlocked")) {
if (card.getUnlockedRooms().size() < 2) {
return false;
}
} else if (property.startsWith("canReceiveCounters")) {
if (!card.canReceiveCounters(CounterType.getType(property.split(" ")[1]))) {
return false;
}
} else if (property.equals("canBeTurnedFaceUp")) {
if (!card.canBeTurnedFaceUp()) {
return false;
}
} else if (property.equals("NoAbilities")) {
if (!card.hasNoAbilities()) {
return false;

View File

@@ -69,10 +69,16 @@ public class CostRemoveAnyCounter extends CostPart {
int allCounters = 0;
for (Card c : validCards) {
if (this.counter != null) {
if (!c.canRemoveCounters(this.counter)) {
continue;
}
allCounters += c.getCounters(this.counter);
} else {
for (Integer value : c.getCounters().values()) {
allCounters += value;
for (Map.Entry<CounterType, Integer> entry : c.getCounters().entrySet()) {
if (!c.canRemoveCounters(entry.getKey())) {
continue;
}
allCounters += entry.getValue();
}
}
}

View File

@@ -21,6 +21,8 @@ import com.google.common.collect.Maps;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CounterEnumType;
import forge.game.card.CounterType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
@@ -78,7 +80,8 @@ public class CostUntap extends CostPart {
@Override
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
final Card source = ability.getHostCard();
return source.isTapped() && !source.isAbilitySick();
return source.isTapped() && !source.isAbilitySick() &&
(source.getCounters(CounterEnumType.STUN) == 0 || source.canRemoveCounters(CounterType.get(CounterEnumType.STUN)));
}
@Override

View File

@@ -86,7 +86,7 @@ public class CostUntapType extends CostPartWithList {
if (!canUntapSource) {
typeList.remove(source);
}
typeList = CardLists.filter(typeList, CardPredicates.TAPPED);
typeList = CardLists.filter(typeList, CardPredicates.TAPPED, c -> c.getCounters(CounterEnumType.STUN) == 0 || c.canRemoveCounters(CounterType.get(CounterEnumType.STUN)));
final int amount = this.getAbilityAmount(ability);
return (typeList.size() != 0) && (typeList.size() >= amount);

View File

@@ -1050,6 +1050,7 @@ public class PhaseHandler implements java.io.Serializable {
for (SpellAbility sa : chosenSa) {
Card saHost = sa.getHostCard();
final Zone originZone = saHost.getZone();
final CardZoneTable triggerList = new CardZoneTable(game.getLastStateBattlefield(), game.getLastStateGraveyard());
if (pPlayerPriority.getController().playChosenSpellAbility(sa)) {
// 117.3c If a player has priority when they cast a spell, activate an ability, [play a land]
@@ -1065,7 +1066,6 @@ public class PhaseHandler implements java.io.Serializable {
// Need to check if Zone did change
if (currentZone != null && originZone != null && !currentZone.equals(originZone) && (sa.isSpell() || sa.isLandAbility())) {
// currently there can be only one Spell put on the Stack at once, or Land Abilities be played
final CardZoneTable triggerList = new CardZoneTable(game.getLastStateBattlefield(), game.getLastStateGraveyard());
triggerList.put(originZone.getZoneType(), currentZone.getZoneType(), saHost);
triggerList.triggerChangesZoneAll(game, sa);
}

View File

@@ -103,6 +103,7 @@ public class Player extends GameEntity implements Comparable<Player> {
private int numPowerSurgeLands;
private int numLibrarySearchedOwn; //The number of times this player has searched his library
private int numDrawnThisTurn;
private int numDrawnLastTurn;
private int numDrawnThisDrawStep;
private int numRollsThisTurn;
private int numExploredThisTurn;
@@ -852,6 +853,14 @@ public class Player extends GameEntity implements Comparable<Player> {
return true;
}
public final boolean canRemoveCounters(final CounterType type) {
if (!isInGame()) {
return false;
}
// no RE affecting players currently, skip check for performance
return true;
}
@Override
public void addCounterInternal(final CounterType counterType, final int n, final Player source, final boolean fireEvents, GameEntityCounterTable table, Map<AbilityKey, Object> params) {
int addAmount = n;
@@ -893,12 +902,12 @@ public class Player extends GameEntity implements Comparable<Player> {
}
@Override
public void subtractCounter(CounterType counterName, int num, final Player remover) {
public int subtractCounter(CounterType counterName, int num, final Player remover) {
int oldValue = getCounters(counterName);
int newValue = Math.max(oldValue - num, 0);
final int delta = oldValue - newValue;
if (delta == 0) { return; }
if (delta == 0) { return 0; }
setCounters(counterName, newValue, null, true);
@@ -914,6 +923,7 @@ public class Player extends GameEntity implements Comparable<Player> {
getGame().getTriggerHandler().runTrigger(TriggerType.CounterRemoved, runParams, false);
}
*/
return delta;
}
public final void clearCounters() {
@@ -1442,6 +1452,10 @@ public class Player extends GameEntity implements Comparable<Player> {
return numDrawnThisTurn;
}
public final int getNumDrawnLastTurn() {
return numDrawnLastTurn;
}
public final int numDrawnThisDrawStep() {
return numDrawnThisDrawStep;
}
@@ -2253,6 +2267,9 @@ public class Player extends GameEntity implements Comparable<Player> {
public final void setLandsPlayedLastTurn(int num) {
landsPlayedLastTurn = num;
}
public final void setNumDrawnLastTurn(int num) {
numDrawnLastTurn= num;
}
public final int getInvestigateNumThisTurn() {
return investigatedThisTurn;
@@ -2472,6 +2489,7 @@ public class Player extends GameEntity implements Comparable<Player> {
for (final PlayerZone pz : zones.values()) {
pz.resetCardsAddedThisTurn();
}
setNumDrawnLastTurn(getNumDrawnThisTurn());
resetNumDrawnThisTurn();
resetNumRollsThisTurn();
resetNumExploredThisTurn();

View File

@@ -18,12 +18,8 @@
package forge.game.spellability;
import forge.card.mana.ManaCost;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.replacement.ReplacementType;
import java.util.Map;
/**
* <p>
@@ -58,9 +54,8 @@ public abstract class AbilityStatic extends Ability implements Cloneable {
// Check if ability can't be attempted because of replacement effect
// Initial usage is Karlov Watchdog preventing disguise/morph/cloak/manifest turning face up
if (this.isTurnFaceUp()) {
Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(c);
if (c.getGame().getReplacementHandler().cantHappenCheck(ReplacementType.TurnFaceUp, repParams)) return false;
if (this.isTurnFaceUp() && !c.canBeTurnedFaceUp()) {
return false;
}
return this.getRestrictions().canPlay(c, this);

View File

@@ -41,6 +41,7 @@ public class MessageUtil {
case Seek:
return value;
case ChooseColor:
case Mana:
return sa.hasParam("Random")
? Localizer.getInstance().getMessage("lblRandomColorChosen", value)
: Localizer.getInstance().getMessage("lblPlayerPickedChosen", choser, value);

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="forge.app"
android:versionCode="106650000"
android:versionName="1.6.65" > <!-- versionName should be updated and it's used for Sentry releases tag -->
android:versionCode="106660000"
android:versionName="1.6.66" > <!-- versionName should be updated and it's used for Sentry releases tag -->
<uses-sdk
android:minSdkVersion="26"

View File

@@ -7,7 +7,7 @@
<packaging.type>jar</packaging.type>
<build.min.memory>-Xms1024m</build.min.memory>
<build.max.memory>-Xmx1536m</build.max.memory>
<alpha-version>1.6.65-SNAPSHOT</alpha-version>
<alpha-version>1.6.66-SNAPSHOT</alpha-version>
<sign.keystore>keystore</sign.keystore>
<sign.alias>alias</sign.alias>
<sign.storepass>storepass</sign.storepass>
@@ -20,7 +20,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.65-SNAPSHOT</version>
<version>1.6.66-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-android</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.65-SNAPSHOT</version>
<version>1.6.66-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-desktop</artifactId>
@@ -118,7 +118,7 @@
</goals>
<configuration>
<!-- TODO: insert placeholder for latest version tag -->
<fromRef>forge-1.6.64</fromRef>
<fromRef>forge-1.6.65</fromRef>
<file>../forge-gui/release-files/CHANGES.txt</file>
<templateContent>
<![CDATA[

View File

@@ -6,15 +6,15 @@ import java.awt.FontMetrics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.*;
import javax.swing.text.StyleConstants;
import com.google.common.collect.ImmutableList;
import forge.gui.FThreads;
import forge.localinstance.skin.FSkinProp;
import forge.toolbox.FSkin.SkinImage;
import forge.util.Localizer;
@@ -87,20 +87,25 @@ public class FOptionPane extends FDialog {
if (FView.SINGLETON_INSTANCE.getSplash() != null) {
return 0;
}
final FOptionPane optionPane = new FOptionPane(message, title, icon, null, options, defaultOption);
optionPane.setVisible(true);
final int dialogResult = optionPane.result;
optionPane.dispose();
return dialogResult;
return showOptionDialog(message, title, icon, null, options, defaultOption);
}
public static int showOptionDialog(final String message, final String title, final SkinImage icon, final Component comp, final List<String> options, final int defaultOption) {
final FOptionPane optionPane = new FOptionPane(message, title, icon, comp, options, defaultOption);
optionPane.setVisible(true);
final int dialogResult = optionPane.result;
optionPane.dispose();
return dialogResult;
final Callable<Integer> showChoice = () -> {
final FOptionPane optionPane = new FOptionPane(message, title, icon, comp, options, defaultOption);
optionPane.setVisible(true);
final int dialogResult = optionPane.result;
optionPane.dispose();
return dialogResult;
};
final FutureTask<Integer> future = new FutureTask<>(showChoice);
FThreads.invokeInEdtAndWait(future);
try {
return future.get();
} catch (final Exception e) { // should be no exception here
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static String showInputDialog(final String message, final String title) {
@@ -117,45 +122,56 @@ public class FOptionPane extends FDialog {
@SuppressWarnings("unchecked")
public static <T> T showInputDialog(final String message, final String title, final SkinImage icon, final String initialInput, final List<T> inputOptions) {
final JComponent inputField;
FTextField txtInput = null;
FComboBox<T> cbInput = null;
if (inputOptions == null) {
txtInput = new FTextField.Builder().text(initialInput).build();
inputField = txtInput;
} else {
cbInput = new FComboBox<>(inputOptions);
cbInput.setSelectedItem(initialInput);
inputField = cbInput;
}
final Callable<T> showChoice = () -> {
final JComponent inputField;
FTextField txtInput = null;
FComboBox<T> cbInput = null;
if (inputOptions == null) {
txtInput = new FTextField.Builder().text(initialInput).build();
inputField = txtInput;
} else {
cbInput = new FComboBox<>(inputOptions);
cbInput.setSelectedItem(initialInput);
inputField = cbInput;
}
final FOptionPane optionPane = new FOptionPane(message, title, icon, inputField, ImmutableList.of(Localizer.getInstance().getMessage("lblOK"), Localizer.getInstance().getMessage("lblCancel")), -1);
optionPane.setDefaultFocus(inputField);
inputField.addKeyListener(new KeyAdapter() { //hook so pressing Enter on field accepts dialog
@Override
public void keyPressed(final KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
optionPane.setResult(0);
final FOptionPane optionPane = new FOptionPane(message, title, icon, inputField, ImmutableList.of(Localizer.getInstance().getMessage("lblOK"), Localizer.getInstance().getMessage("lblCancel")), -1);
optionPane.setDefaultFocus(inputField);
inputField.addKeyListener(new KeyAdapter() { //hook so pressing Enter on field accepts dialog
@Override
public void keyPressed(final KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
optionPane.setResult(0);
}
}
});
optionPane.setVisible(true);
final int dialogResult = optionPane.result;
optionPane.dispose();
if (dialogResult == 0) {
if (inputOptions == null) {
return (T)txtInput.getText();
} else {
return cbInput.getSelectedItem();
}
}
});
optionPane.setVisible(true);
final int dialogResult = optionPane.result;
optionPane.dispose();
if (dialogResult == 0) {
if (inputOptions == null) {
return (T)txtInput.getText();
} else {
return cbInput.getSelectedItem();
}
return null;
};
final FutureTask<T> future = new FutureTask<>(showChoice);
FThreads.invokeInEdtAndWait(future);
try {
return future.get();
} catch (final Exception e) { // should be no exception here
e.printStackTrace();
throw new RuntimeException(e);
}
return null;
}
private int result = -1; //default result to -1, indicating dialog closed without choosing option
private final FButton[] buttons;
public FOptionPane(final String message, final String title, final SkinImage icon, final Component comp, final List<String> options, final int defaultOption) {
FThreads.assertExecutedByEdt(true);
this.setTitle(title);
final int padding = 10;
@@ -163,7 +179,7 @@ public class FOptionPane extends FDialog {
final int gapAboveButtons = padding * 3 / 2;
final int gapBottom = comp == null ? gapAboveButtons : padding;
FLabel centeredLabel = null;
FTextPane centeredPrompt = null;
FTextPane prompt = null;
if (icon != null) {
if (icon.getWidth() < 100) {
@@ -179,23 +195,16 @@ public class FOptionPane extends FDialog {
}
}
if (message != null) {
if (centeredLabel == null) {
final FTextArea prompt = new FTextArea(message);
prompt.setFont(FSkin.getFont(14));
prompt.setAutoSize(true);
final Dimension parentSize = JOptionPane.getRootFrame().getSize();
prompt.setMaximumSize(new Dimension(parentSize.width / 2, parentSize.height - 100));
this.add(prompt, "x " + x + ", ay top, wrap, gaptop " + (icon == null ? 0 : 7) + ", gapbottom " + gapBottom);
}
else {
final FTextPane prompt = new FTextPane(message);
prompt.setFont(FSkin.getFont(14));
prompt = new FTextPane();
prompt.setContentType("text/html");
prompt.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
prompt.setText(FSkin.encodeSymbols(message, false));
prompt.setFont(FSkin.getFont(14));
if(centeredLabel != null)
prompt.setTextAlignment(StyleConstants.ALIGN_CENTER);
final Dimension parentSize = JOptionPane.getRootFrame().getSize();
prompt.setMaximumSize(new Dimension(parentSize.width / 2, parentSize.height - 100));
this.add(prompt, "x " + x + ", ay top, wrap, gapbottom " + gapBottom);
centeredPrompt = prompt;
}
final Dimension parentSize = JOptionPane.getRootFrame().getSize();
prompt.setMaximumSize(new Dimension(parentSize.width / 2, parentSize.height - 100));
this.add(prompt, "x " + x + ", ay top, wrap, gaptop " + (icon == null ? 0 : 7) + ", gapbottom " + gapBottom);
x = padding;
}
if (comp != null) {
@@ -277,7 +286,8 @@ public class FOptionPane extends FDialog {
if (centeredLabel != null) {
centeredLabel.setPreferredSize(new Dimension(width - 2 * padding, centeredLabel.getMinimumSize().height));
centeredPrompt.setPreferredSize(new Dimension(width - 2 * padding, centeredPrompt.getPreferredSize().height));
if(prompt != null)
prompt.setPreferredSize(new Dimension(width - 2 * padding, prompt.getPreferredSize().height));
}
this.setSize(width, this.getHeight() + buttonHeight); //resize dialog again to account for buttons

View File

@@ -12,7 +12,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.65-SNAPSHOT</version>
<version>1.6.66-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-ios</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.65-SNAPSHOT</version>
<version>1.6.66-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-mobile-dev</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.65-SNAPSHOT</version>
<version>1.6.66-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-mobile</artifactId>

View File

@@ -1,11 +1,7 @@
package forge;
import com.badlogic.gdx.Application;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.*;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.controllers.Controller;
import com.badlogic.gdx.controllers.ControllerAdapter;
import com.badlogic.gdx.controllers.ControllerListener;
@@ -54,7 +50,7 @@ import java.nio.file.Paths;
import java.util.*;
public class Forge implements ApplicationListener {
public static final String CURRENT_VERSION = "1.6.65-SNAPSHOT";
public static final String CURRENT_VERSION = "1.6.66-SNAPSHOT";
private static ApplicationListener app = null;
static Scene currentScene = null;

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.65-SNAPSHOT</version>
<version>1.6.66-SNAPSHOT</version>
</parent>
<artifactId>forge-gui</artifactId>

View File

@@ -133,4 +133,5 @@ Ravnica Remastered, 3/6/RAV, RVR
Murders at Karlov Manor, 3/6/MKM, MKM
Outlaws of Thunder Junction, 3/6/OTJ, OTJ
Modern Horizons 3, 3/6/MH3, MH3
Bloomburrow, 3/6/BLB, BLB
Bloomburrow, 3/6/BLB, BLB
Duskmourn: House of Horror, 3/6/DSK, DSK

View File

@@ -1,10 +1,10 @@
Name:A Premonition of Your Demise
ManaCost:no cost
Types:Scheme
T:Mode$ SetInMotion | ValidCard$ Card.Self | Execute$ DBDig | TriggerZones$ Command | TriggerDescription$ When you set this scheme in motion, reveal the top two cards of your library and put them into your hand. When you reveal one or more nonland cards this way, this scheme deals damage equal to their total mana value to any target.
SVar:DBDig:DB$ Dig | DigNum$ 2 | Reveal$ True | ChangeNum$ All | ChangeValid$ Card | DestinationZone$ Hand | RememberChanged$ True | SubAbility$ DBImmediateTrigger
SVar:DBImmediateTrigger:DB$ ImmediateTrigger | ConditionDefined$ Remembered | ConditionPresent$ Card.nonLand | Execute$ TrigDamage | SubAbility$ DBCleanup | SpellDescription$ When you reveal one or more nonland cards this way, this scheme deals damage equal to their total mana value to any target.
SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Any | NumDmg$ X
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Count$ValidHand Card.IsRemembered+nonLand$SumCMC
Oracle:When you set this scheme in motion, reveal the top two cards of your library and put them into your hand. When you reveal one or more nonland cards this way, this scheme deals damage equal to their total mana value to any target.
Name:A Premonition of Your Demise
ManaCost:no cost
Types:Scheme
T:Mode$ SetInMotion | ValidCard$ Card.Self | Execute$ DBDig | TriggerZones$ Command | TriggerDescription$ When you set this scheme in motion, reveal the top two cards of your library and put them into your hand. When you reveal one or more nonland cards this way, this scheme deals damage equal to their total mana value to any target.
SVar:DBDig:DB$ Dig | DigNum$ 2 | Reveal$ True | ChangeNum$ All | ChangeValid$ Card | DestinationZone$ Hand | RememberChanged$ True | SubAbility$ DBImmediateTrigger
SVar:DBImmediateTrigger:DB$ ImmediateTrigger | ConditionDefined$ Remembered | ConditionPresent$ Card.nonLand | Execute$ TrigDamage | SubAbility$ DBCleanup | SpellDescription$ When you reveal one or more nonland cards this way, this scheme deals damage equal to their total mana value to any target.
SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Any | NumDmg$ X
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Count$ValidHand Card.IsRemembered+nonLand$SumCMC
Oracle:When you set this scheme in motion, reveal the top two cards of your library and put them into your hand. When you reveal one or more nonland cards this way, this scheme deals damage equal to their total mana value to any target.

View File

@@ -1,8 +1,8 @@
Name:Abandoned Campground
ManaCost:no cost
Types:Land
R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplaceWith$ LandTapped | ReplacementResult$ Updated | Description$ CARDNAME enters tapped unless a player has 13 or less life.
SVar:LandTapped:DB$ Tap | Defined$ Self | ETB$ True | ConditionCheckSVar$ X | ConditionSVarCompare$ GT13
SVar:X:PlayerCountPlayers$LowestLifeTotal
A:AB$ Mana | Cost$ T | Produced$ Combo W U | SpellDescription$ Add {W} or {U}.
Oracle:Abandoned Campground enters tapped unless a player has 13 or less life.\n{T}: Add {W} or {U}.
Name:Abandoned Campground
ManaCost:no cost
Types:Land
R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplaceWith$ LandTapped | ReplacementResult$ Updated | Description$ CARDNAME enters tapped unless a player has 13 or less life.
SVar:LandTapped:DB$ Tap | Defined$ Self | ETB$ True | ConditionCheckSVar$ X | ConditionSVarCompare$ GT13
SVar:X:PlayerCountPlayers$LowestLifeTotal
A:AB$ Mana | Cost$ T | Produced$ Combo W U | SpellDescription$ Add {W} or {U}.
Oracle:Abandoned Campground enters tapped unless a player has 13 or less life.\n{T}: Add {W} or {U}.

View File

@@ -9,6 +9,6 @@ SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Remembered$Amount
SVar:MaxTgts:Count$Valid Permanent.Other+nonLand+YouCtrl
K:Choose a Background
DeckHas:Ability$Token & Type$Soldier
AI:RemoveDeck:Random
DeckHas:Ability$Token & Type$Soldier
Oracle:When Abdel Adrian, Gorion's Ward enters, exile any number of other nonland permanents you control until Abdel Adrian leaves the battlefield. Create a 1/1 white Soldier creature token for each permanent exiled this way.\nChoose a Background (You can have a Background as a second commander.)

View File

@@ -1,9 +1,9 @@
Name:Abhorrent Oculus
ManaCost:2 U
Types:Creature Eye
PT:5/5
A:SP$ PermanentCreature | Cost$ 2 U ExileFromGrave<6/Card>
K:Flying
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Opponent | TriggerZones$ Battlefield | Execute$ TrigDread | TriggerDescription$ At the beginning of each opponent's upkeep, manifest dread. (Look at the top two cards of your library. Put one onto the battlefield face down as a 2/2 creature, and the other into your graveyard. Turn it face up any time for its mana cost if it's a creature card.)
SVar:TrigDread:DB$ ManifestDread
Oracle:As an additional cost to cast this spell, exile six cards from your graveyard.\nFlying\nAt the beginning of each opponent's upkeep, manifest dread. (Look at the top two cards of your library. Put one onto the battlefield face down as a 2/2 creature, and the other into your graveyard. Turn it face up any time for its mana cost if it's a creature card.)
Name:Abhorrent Oculus
ManaCost:2 U
Types:Creature Eye
PT:5/5
A:SP$ PermanentCreature | Cost$ 2 U ExileFromGrave<6/Card>
K:Flying
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Opponent | TriggerZones$ Battlefield | Execute$ TrigDread | TriggerDescription$ At the beginning of each opponent's upkeep, manifest dread. (Look at the top two cards of your library. Put one onto the battlefield face down as a 2/2 creature, and the other into your graveyard. Turn it face up any time for its mana cost if it's a creature card.)
SVar:TrigDread:DB$ ManifestDread
Oracle:As an additional cost to cast this spell, exile six cards from your graveyard.\nFlying\nAt the beginning of each opponent's upkeep, manifest dread. (Look at the top two cards of your library. Put one onto the battlefield face down as a 2/2 creature, and the other into your graveyard. Turn it face up any time for its mana cost if it's a creature card.)

View File

@@ -4,6 +4,6 @@ Types:Instant
K:Devoid
A:SP$ Counter | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | UnlessCost$ 1 | SubAbility$ DBToken | SpellDescription$ Counter target spell unless its controller pays {1}.
SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_1_1_eldrazi_scion_sac | TokenOwner$ You | SpellDescription$ You create a 1/1 colorless Eldrazi Scion creature token. It has "Sacrifice this creature: Add {C}." ({C} represents colorless mana.)
DeckHints:Type$Eldrazi
DeckHas:Ability$Mana.Colorless|Token
DeckHints:Type$Eldrazi
Oracle:Devoid (This card has no color.)\nCounter target spell unless its controller pays {1}. You create a 1/1 colorless Eldrazi Scion creature token. It has "Sacrifice this creature: Add {C}." ({C} represents colorless mana.)

View File

@@ -5,6 +5,6 @@ PT:3/2
K:Outlast:W
S:Mode$ Continuous | Affected$ Creature.YouCtrl+counters_GE1_P1P1 | AddKeyword$ Lifelink | Description$ Each creature you control with a +1/+1 counter on it has lifelink.
SVar:PlayMain1:TRUE
DeckHints:Ability$Counters
DeckHas:Ability$Counters
DeckHints:Ability$Counters
Oracle:Outlast {W} ({W}, {T}: Put a +1/+1 counter on this creature. Outlast only as a sorcery.)\nEach creature you control with a +1/+1 counter on it has lifelink.

View File

@@ -5,6 +5,6 @@ PT:2/3
K:Outlast:W
S:Mode$ Continuous | Affected$ Creature.YouCtrl+counters_GE1_P1P1 | AddKeyword$ Flying | Description$ Each creature you control wth a +1/+1 counter on it has flying.
SVar:PlayMain1:TRUE
DeckHints:Ability$Counters
DeckHas:Ability$Counters
DeckHints:Ability$Counters
Oracle:Outlast {W} ({W}, {T}: Put a +1/+1 counter on this creature. Outlast only as a sorcery.)\nEach creature you control with a +1/+1 counter on it has flying.

View File

@@ -3,6 +3,6 @@ ManaCost:1 G
Types:Legendary Enchantment Background
S:Mode$ Continuous | Affected$ Creature.IsCommander+YouOwn | AddStaticAbility$ DragonReduce | Description$ Commander creatures you own have "The first Dragon spell you cast each turn costs {2} less to cast."
SVar:DragonReduce:Mode$ ReduceCost | EffectZone$ Battlefield | ValidCard$ Card.Dragon | Activator$ You | Type$ Spell | OnlyFirstSpell$ True | Amount$ 2 | Description$ The first Dragon spell you cast each turn costs {2} less to cast.
DeckNeeds:Type$Dragon
AI:RemoveDeck:NonCommander
DeckNeeds:Type$Dragon
Oracle:Commander creatures you own have "The first Dragon spell you cast each turn costs {2} less to cast."

View File

@@ -1,7 +1,7 @@
Name:Acrobatic Cheerleader
ManaCost:1 W
Types:Creature Human Survivor
PT:2/2
T:Mode$ Phase | Phase$ Main | PhaseCount$ 2 | ValidPlayer$ You | PresentDefined$ Self | IsPresent$ Card.tapped | TriggerZones$ Battlefield | Execute$ TrigPutCounter | GameActivationLimit$ 1 | TriggerDescription$ Survival — At the beginning of your second main phase, if CARDNAME is tapped, put a flying counter on it. This ability triggers only once.
SVar:TrigPutCounter:DB$ PutCounter | CounterType$ Flying | CounterNum$ 1
Oracle:Survival — At the beginning of your second main phase, if Acrobatic Cheerleader is tapped, put a flying counter on it. This ability triggers only once.
Name:Acrobatic Cheerleader
ManaCost:1 W
Types:Creature Human Survivor
PT:2/2
T:Mode$ Phase | Phase$ Main | PhaseCount$ 2 | ValidPlayer$ You | PresentDefined$ Self | IsPresent$ Card.tapped | TriggerZones$ Battlefield | Execute$ TrigPutCounter | GameActivationLimit$ 1 | TriggerDescription$ Survival — At the beginning of your second main phase, if CARDNAME is tapped, put a flying counter on it. This ability triggers only once.
SVar:TrigPutCounter:DB$ PutCounter | CounterType$ Flying | CounterNum$ 1
Oracle:Survival — At the beginning of your second main phase, if Acrobatic Cheerleader is tapped, put a flying counter on it. This ability triggers only once.

View File

@@ -4,6 +4,6 @@ Types:Creature Human Soldier
PT:2/1
T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | IsPresent$ Planeswalker.Basri+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ At the beginning of combat on your turn, if you control a Basri planeswalker, put a +1/+1 counter on CARDNAME.
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1
DeckNeeds:Type$Basri
DeckHas:Ability$Counters
DeckNeeds:Type$Basri
Oracle:At the beginning of combat on your turn, if you control a Basri planeswalker, put a +1/+1 counter on Adherent of Hope.

View File

@@ -5,6 +5,6 @@ K:Devoid
A:SP$ Tap | TargetMin$ 0 | TargetMax$ 2 | TgtPrompt$ Choose target creature | ValidTgts$ Creature | SubAbility$ TrigPump | SpellDescription$ Tap up to two target creatures.
SVar:TrigPump:DB$ Pump | Defined$ Targeted | KW$ HIDDEN This card doesn't untap during your next untap step. | Duration$ Permanent | SubAbility$ DBToken | SpellDescription$ Those creatures don't untap during their controller's next untap step.
SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_1_1_eldrazi_scion_sac | TokenOwner$ You | SpellDescription$ Create a 1/1 colorless Eldrazi Scion creature token. It has "Sacrifice this creature: Add {C}."
DeckHints:Type$Eldrazi
DeckHas:Ability$Mana.Colorless|Token
DeckHints:Type$Eldrazi
Oracle:Devoid (This card has no color.)\nTap up to two target creatures. Those creatures don't untap during their controller's next untap step. Create a 1/1 colorless Eldrazi Scion creature token. It has "Sacrifice this creature: Add {C}."

View File

@@ -5,6 +5,6 @@ A:SP$ Dig | DigNum$ 5 | ChangeNum$ 1 | SubAbility$ Dig2 | ConditionCheckSVar$ X
SVar:Dig2:DB$ Dig | DigNum$ 5 | ChangeNum$ 2 | ConditionCheckSVar$ X | ConditionSVarCompare$ GTY
SVar:X:Count$Valid Creature.YouCtrl
SVar:Y:PlayerCountOther$HighestValid Creature.YouCtrl
DeckNeeds:Color$Blue
AI:RemoveDeck:Random
DeckNeeds:Color$Blue
Oracle:({2/U} can be paid with any two mana or with {U}. This card's mana value is 6.)\nLook at the top five cards of your library. If you control more creatures than each other player, put two of those cards into your hand. Otherwise, put one of them into your hand. Then put the rest on the bottom of your library in any order.

View File

@@ -5,6 +5,6 @@ PT:3/4
K:Flying
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPut | TriggerDescription$ When CARDNAME enters, put a +1/+1 counter on another target Soldier you control.
SVar:TrigPut:DB$ PutCounter | ValidTgts$ Soldier.Other+YouCtrl | TgtPrompt$ Select another target Soldier you control | CounterType$ P1P1
DeckHints:Type$Soldier
DeckHas:Ability$Counters
DeckHints:Type$Soldier
Oracle:Flying\nWhen Aeronaut Cavalry enters, put a +1/+1 counter on another target Soldier you control.

View File

@@ -1,7 +1,7 @@
Name:Aether Searcher
ManaCost:7
PT:6/4
Types:Artifact Creature Construct
PT:6/4
Draft:Reveal CARDNAME as you draft it.
Draft:Reveal the next card you draft and note its name.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigSearchHand | TriggerDescription$ When CARDNAME enters, you may search your hand and/or library for a card with a name noted as you drafted cards named Aether Searcher. You may cast it without paying its mana cost. If you searched your library this way, shuffle.

View File

@@ -7,6 +7,6 @@ K:Trample
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self+bargained | Execute$ TrigKicker | TriggerDescription$ When CARDNAME enters, if it was bargained, it fights up to one target creature you don't control. (Each deals damage equal to its power to the other.)
SVar:TrigKicker:DB$ Fight | Defined$ TriggeredCardLKICopy | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select up to one target creature an opponent controls | TargetMin$ 0 | TargetMax$ 1
SVar:PlayMain1:TRUE
DeckHints:Type$Artifact|Enchantment & Ability$Token
DeckHas:Ability$Sacrifice
DeckHints:Type$Artifact|Enchantment & Ability$Token
Oracle:Bargain (You may sacrifice an artifact, enchantment, or token as you cast this spell.)\nTrample\nWhen Agatha's Champion enters, if it was bargained, it fights up to one target creature you don't control. (Each deals damage equal to its power to the other.)

View File

@@ -4,6 +4,6 @@ Types:Legendary Enchantment Background
S:Mode$ Continuous | Affected$ Creature.IsCommander+YouOwn | AddTrigger$ Dies | Description$ Commander creatures you own have "Whenever an artifact or creature you control is put into a graveyard from the battlefield, each opponent loses 1 life."
SVar:Dies:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Artifact.YouCtrl,Creature.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigDrain | TriggerDescription$ Whenever an artifact or creature you control is put into a graveyard from the battlefield, each opponent loses 1 life.
SVar:TrigDrain:DB$ LoseLife | Defined$ Opponent | LifeAmount$ 1
DeckHints:Type$Artifact & Ability$Sacrifice
AI:RemoveDeck:NonCommander
DeckHints:Type$Artifact & Ability$Sacrifice
Oracle:Commander creatures you own have "Whenever an artifact or creature you control is put into a graveyard from the battlefield, each opponent loses 1 life."

View File

@@ -5,6 +5,6 @@ S:Mode$ Continuous | Affected$ Creature.IsCommander+YouOwn | AddTrigger$ Attacks
SVar:AttacksPlayer:Mode$ Attacks | ValidCard$ Card.Self | Attacked$ Player | Condition$ NoOpponentHasMoreLifeThanAttacked | Execute$ TrigPutCounter | TriggerDescription$ Whenever this creature attacks a player, if no opponent has more life than that player, put a +1/+1 counter on this creature. It gains deathtouch and indestructible until end of turn.
SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 1 | Defined$ Self | SubAbility$ DBPump
SVar:DBPump:DB$ Pump | Defined$ Self | KW$ Deathtouch & Indestructible
DeckHas:Ability$Counters
AI:RemoveDeck:NonCommander
DeckHas:Ability$Counters
Oracle:Commander creatures you own have "Whenever this creature attacks a player, if no opponent has more life than that player, put a +1/+1 counter on this creature. It gains deathtouch and indestructible until end of turn."

View File

@@ -5,6 +5,6 @@ PT:2/1
K:Outlast:1 W
S:Mode$ Continuous | Affected$ Creature.YouCtrl+counters_GE1_P1P1 | AddKeyword$ First Strike | Description$ Each creature you control with a +1/+1 counter on it has first strike.
SVar:PlayMain1:TRUE
DeckHints:Ability$Counters
DeckHas:Ability$Counters
DeckHints:Ability$Counters
Oracle:Outlast {1}{W} ({1}{W}, {T}: Put a +1/+1 counter on this creature. Outlast only as a sorcery.)\nEach creature you control with a +1/+1 counter on it has first strike.

View File

@@ -21,10 +21,11 @@ Types:Legendary Planeswalker Ajani
Loyalty:3
A:AB$ PutCounterAll | Cost$ AddCounter<2/LOYALTY> | ValidCards$ Cat.YouCtrl | CounterType$ P1P1 | CounterNum$ 1 | Planeswalker$ True | SpellDescription$ Put a +1/+1 counter on each Cat you control.
A:AB$ Token | Cost$ AddCounter<0/LOYALTY> | TokenAmount$ 1 | TokenScript$ w_2_1_cat_warrior | TokenOwner$ You | RememberOriginalTokens$ True | SubAbility$ DBImmediateTrig1 | Planeswalker$ True | SpellDescription$ Create a 2/1 white Cat Warrior creature token. When you do, if you control a red permanent other than CARDNAME, he deals damage equal to the number of creatures you control to any target.
SVar:DBImmediateTrig1:DB$ ImmediateTrigger | TriggerAmount$ Remembered$Amount | ConditionPresent$ Permanent.Red+YouCtrl+Other | Execute$ TrigDamage | TriggerDescription$ When you do, if you control a red permanent other than CARDNAME, he deals damage equal to the number of creatures you control to any target.
SVar:TrigDamage:DB$ DealDamage | NumDmg$ X | ValidTgts$ Any | ConditionPresent$ Permanent.Red+YouCtrl+Other | SubAbility$ DBCleanup | TgtPrompt$ Select any valid target | SpellDescription$ CARDNAME deals damage equal to the number of creatures you control to any target.
SVar:DBImmediateTrig1:DB$ ImmediateTrigger | TriggerAmount$ Remembered$Amount | ConditionPresent$ Permanent.Red+YouCtrl+Other | Execute$ TrigDamage | SubAbility$ DBCleanup2 | TriggerDescription$ When you do, if you control a red permanent other than CARDNAME, he deals damage equal to the number of creatures you control to any target.
SVar:TrigDamage:DB$ DealDamage | NumDmg$ X | ValidTgts$ Any | ConditionPresent$ Permanent.Red+YouCtrl+Other | TgtPrompt$ Select any valid target | SpellDescription$ CARDNAME deals damage equal to the number of creatures you control to any target.
SVar:X:Count$Valid Creature.YouCtrl
A:AB$ ChooseCard | Cost$ SubCounter<4/LOYALTY> | Planeswalker$ True | Ultimate$ True | Defined$ Opponent | Choices$ Permanent.nonLand | ChooseEach$ Artifact & Creature & Enchantment & Planeswalker | ControlledByPlayer$ Chooser | Mandatory$ True | Reveal$ True | SubAbility$ SacAllOthers | StackDescription$ SpellDescription | SpellDescription$ Each opponent chooses an artifact, a creature, an enchantment, and a planeswalker from among the nonland permanents they control, then sacrifices the rest.
SVar:SacAllOthers:DB$ SacrificeAll | ValidCards$ Permanent.nonLand+OppCtrl+nonChosenCard | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True
SVar:DBCleanup2:DB$ Cleanup | ClearRemembered$ True
Oracle:[+2]: Put a +1/+1 counter on each Cat you control.\n[0]: Create a 2/1 white Cat Warrior creature token. When you do, if you control a red permanent other than Ajani, Nacatl Avenger, he deals damage equal to the number of creatures you control to any target.\n[-4]: Each opponent chooses an artifact, a creature, an enchantment and a planeswalker from among the nonland permanents they control, then sacrifices the rest.

View File

@@ -6,6 +6,6 @@ SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | OriginAlternative$ Graveyard
A:AB$ ChooseCard | Cost$ Sac<1/CARDNAME> | Choices$ Creature | Mandatory$ True | AILogic$ NeedsPrevention | SubAbility$ DBEffect | SpellDescription$ Prevent all combat damage a creature of your choice would deal this turn.
SVar:DBEffect:DB$ Effect | ReplacementEffects$ RPreventNextFromSource | RememberObjects$ ChosenCard | ExileOnMoved$ Battlefield
SVar:RPreventNextFromSource:Event$ DamageDone | IsCombat$ True | ValidSource$ Card.IsRemembered | Prevent$ True | Description$ Prevent all combat damage a creature of your choice would deal this turn.
DeckHints:Name$Ajani, Valiant Protector
DeckHas:Ability$Sacrifice
DeckHints:Name$Ajani, Valiant Protector
Oracle:When Ajani's Aid enters, you may search your library and/or graveyard for a card named Ajani, Valiant Protector, reveal it, and put it into your hand. If you search your library this way, shuffle.\nSacrifice Ajani's Aid: Prevent all combat damage a creature of your choice would deal this turn.

View File

@@ -4,6 +4,6 @@ Types:Creature Cat Soldier
PT:2/2
T:Mode$ LifeGained | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever you gain life, put a +1/+1 counter on CARDNAME.
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1
DeckHints:Ability$LifeGain
DeckHas:Ability$Counters
DeckHints:Ability$LifeGain
Oracle:Whenever you gain life, put a +1/+1 counter on Ajani's Pridemate.

View File

@@ -5,6 +5,6 @@ PT:1/5
T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ GE1 | Execute$ TrigDig | TriggerDescription$ At the beginning of each player's end step, if an artifact entered the battlefield under your control this turn, look at the top two cards of your library. Put one of them into your hand and the other into your graveyard.
SVar:TrigDig:DB$ Dig | DigNum$ 2 | ChangeNum$ 1 | DestinationZone2$ Graveyard | NoReveal$ True
SVar:X:Count$ThisTurnEntered_Battlefield_Artifact.YouCtrl
DeckNeeds:Type$Artifact
DeckHas:Ability$Graveyard
DeckNeeds:Type$Artifact
Oracle:At the beginning of each player's end step, if an artifact entered the battlefield under your control this turn, look at the top two cards of your library. Put one of them into your hand and the other into your graveyard.

View File

@@ -7,7 +7,7 @@ SVar:TrigToken:DB$ Token | TokenScript$ u_2_2_drake_flying
T:Mode$ Drawn | ValidCard$ Card.YouCtrl | Number$ 5 | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever you draw your fifth card each turn, CARDNAME and Drakes you control get +X/+X until end of turn, where X is the number of cards in your hand.
SVar:TrigPump:DB$ PumpAll | ValidCards$ Card.Self,Drake.YouCtrl | NumAtt$ X | NumDef$ X
SVar:X:Count$InYourHand
AI:RemoveDeck:Random
DeckHas:Ability$Token & Type$Drake
DeckHints:Type$Drake
AI:RemoveDeck:Random
Oracle:Whenever you draw your second card each turn, create a 2/2 blue Drake creature token with flying.\nWhenever you draw your fifth card each turn, Alandra, Sky Dreamer and Drakes you control get +X/+X until end of turn, where X is the number of cards in your hand.

View File

@@ -7,9 +7,9 @@ K:Trample
K:Ward:1
T:Mode$ Sacrificed | ValidPlayer$ You | ValidCard$ Card.token | TriggerZones$ Battlefield,Exile | Execute$ TrigPump | TriggerDescription$ Whenever you sacrifice a token, NICKNAME perpetually gets +1/+1. This ability also triggers if NICKNAME is in exile.
SVar:TrigPump:DB$ Pump | PumpZone$ Battlefield,Exile | NumAtt$ 1 | NumDef$ 1 | Duration$ Perpetual
AlternateMode:Adventure
DeckHas:Ability$Discard|Token & Type$Food
DeckHints:Ability$Token & Type$Treasure|Food|Clue
AlternateMode:Adventure
Oracle:Flying, Trample, Ward {1}\nWhenever you sacrifice a token, Albiorix perpetually gets +1/+1. This ability also triggers if Albiorix is in exile.
ALTERNATE

View File

@@ -7,7 +7,7 @@ SVar:TrigToken:DB$ Token | TokenScript$ w_1_1_soldier
T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPumpAll | TriggerDescription$ Whenever NICKNAME attacks, you may pay {8}. If you do, creatures you control get +X/+X until end of turn, where X is the number of historic permanents you control.
SVar:TrigPumpAll:AB$ PumpAll | Cost$ 8 | ValidCards$ Creature.YouCtrl | NumAtt$ X | NumDef$ X
SVar:X:Count$Valid Permanent.YouCtrl+Historic
SVar:HasAttackEffect:TRUE
DeckHas:Ability$Token
DeckHints:Type$Artifact|Legendary|Saga
SVar:HasAttackEffect:TRUE
Oracle:Whenever you cast a historic spell, create a 1/1 white Soldier creature token. (Artifacts, legendaries, and Sagas are historic.)\nWhenever Alistair attacks, you may pay {8}. If you do, creatures you control get +X/+X until end of turn, where X is the number of historic permanents you control.

View File

@@ -8,6 +8,6 @@ SVar:TrigInvestigate:DB$ Investigate
A:AB$ Draw | Cost$ X W U U T Sac<1/Clue> | NumCards$ X | SubAbility$ DBGainLife | SpellDescription$ You draw X cards and gain X life.
SVar:DBGainLife:DB$ GainLife | LifeAmount$ X
SVar:X:Count$xPaid
DeckHints:Ability$Investigate
DeckHas:Ability$Investigate|Token|Sacrifice|LifeGain & Type$Artifact|Clue
DeckHints:Ability$Investigate
Oracle:Vigilance\nWhen Alquist Proft, Master Sleuth enters, investigate. (Create a Clue token. It's an artifact with "{2}, Sacrifice this artifact: Draw a card.")\n{X}{W}{U}{U}, {T}, Sacrifice a Clue: You draw X cards and gain X life.

View File

@@ -9,8 +9,8 @@ SVar:Z:SVar$X/Plus.Y
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigChooseCardType | TriggerDescription$ At the beginning of your end step, choose a card type, then reveal the top two cards of your library. Put all cards of the chosen type revealed this way into your hand and the rest on the bottom of your library in any order.
SVar:TrigChooseCardType:DB$ ChooseType | Defined$ You | Type$ Card | SubAbility$ DBDig
SVar:DBDig:DB$ Dig | DigNum$ 2 | Reveal$ True | ChangeNum$ All | ChangeValid$ Card.ChosenType | DestinationZone2$ Library | LibraryPosition$ -1
DeckHints:Keyword$Foretell
AI:RemoveDeck:All
DeckHints:Keyword$Foretell
AlternateMode:Modal
Oracle:Alrund gets +1/+1 for each card in your hand and each foretold card you own in exile.\nAt the beginning of your end step, choose a card type, then reveal the top two cards of your library. Put all cards of the chosen type revealed this way into your hand and the rest on the bottom of your library in any order.

View File

@@ -1,9 +1,9 @@
Name:Altanak, the Thrice-Called
ManaCost:5 G G
Types:Legendary Creature Insect Beast
PT:9/9
K:Trample
T:Mode$ BecomesTarget | ValidTarget$ Card.Self | ValidSource$ SpellAbility.OppCtrl | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever CARDNAME becomes the target of a spell or ability an opponent controls, draw a card.
SVar:TrigDraw:DB$ Draw
A:AB$ ChangeZone | Cost$ 1 G Discard<1/CARDNAME> | ActivationZone$ Hand | Origin$ Graveyard | Destination$ Battlefield| TgtPrompt$ Select target land card in your graveyard | ValidTgts$ Land.YouOwn | Tapped$ True | SpellDescription$ Return target land card from your graveyard to the battlefield tapped.
Oracle:Trample\nWhenever Altanak, the Thrice-Called becomes the target of a spell or ability an opponent controls, draw a card.\n{1}{G}, Discard Altanak, the Thrice-Called: Return target land card from your graveyard to the battlefield tapped.
Name:Altanak, the Thrice-Called
ManaCost:5 G G
Types:Legendary Creature Insect Beast
PT:9/9
K:Trample
T:Mode$ BecomesTarget | ValidTarget$ Card.Self | ValidSource$ SpellAbility.OppCtrl | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever CARDNAME becomes the target of a spell or ability an opponent controls, draw a card.
SVar:TrigDraw:DB$ Draw
A:AB$ ChangeZone | Cost$ 1 G Discard<1/CARDNAME> | ActivationZone$ Hand | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Select target land card in your graveyard | ValidTgts$ Land.YouOwn | Tapped$ True | SpellDescription$ Return target land card from your graveyard to the battlefield tapped.
Oracle:Trample\nWhenever Altanak, the Thrice-Called becomes the target of a spell or ability an opponent controls, draw a card.\n{1}{G}, Discard Altanak, the Thrice-Called: Return target land card from your graveyard to the battlefield tapped.

View File

@@ -8,9 +8,9 @@ SVar:Y:Sacrificed$CardPower
K:Craft:2 B B XMin1 ExileCtrlOrGrave<X/Creature.Other>
SVar:X:Count$xPaid
A:AB$ ChangeZone | Cost$ 2 B | Origin$ Graveyard | Destination$ Hand | ActivationZone$ Graveyard | SpellDescription$ Return CARDNAME from your graveyard to your hand.
DeckHints:Ability$Discard|Mill|Sacrifice
DeckHas:Ability$Graveyard|Sacrifice|Mill
AI:RemoveDeck:All
DeckHas:Ability$Graveyard|Sacrifice|Mill
DeckHints:Ability$Discard|Mill|Sacrifice
AlternateMode:DoubleFaced
Oracle:When Altar of the Wretched enters, you may sacrifice a nontoken creature. If you do, draw X cards, then mill X cards, where X is that creature's power.\nCraft with one or more creatures {2}{B}{B}\n{2}{B}: Return Altar of the Wretched from your graveyard to your hand.

View File

@@ -10,6 +10,6 @@ SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ bg_1_1_insect | Remembe
SVar:DBPutCounter:DB$ PutCounter | Defined$ Remembered | CounterNum$ X | CounterType$ P1P1 | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:TriggerObjectsCards$GreatestCMC
DeckHints:Type$Insect & Ability$Graveyard
DeckHas:Ability$Token & Ability$Graveyard
DeckHints:Type$Insect & Ability$Graveyard
Oracle:Flying, menace\nOther Insects you control have menace.\nWhenever one or more cards leave your graveyard, you may create a 1/1 black and green Insect creature token, then put a number of +1/+1 counters on it equal to the greatest mana value among those cards. Do this only once each turn.

View File

@@ -11,6 +11,6 @@ SVar:VolverPumped:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNu
SVar:VolverResilience:DB$ Animate | Defined$ Self | Abilities$ ABRegen | Duration$ Permanent
SVar:ABRegen:AB$ Regenerate | Cost$ PayLife<3> | SpellDescription$ Regenerate CARDNAME.
AI:RemoveDeck:Random
DeckNeeds:Color$Blue|Black
DeckHas:Ability$Counters
DeckNeeds:Color$Blue|Black
Oracle:Kicker {1}{U} and/or {B} (You may pay an additional {1}{U} and/or {B} as you cast this spell.)\nIf Anavolver was kicked with its {1}{U} kicker, it enters with two +1/+1 counters on it and with flying.\nIf Anavolver was kicked with its {B} kicker, it enters with a +1/+1 counter on it and with "Pay 3 life: Regenerate Anavolver."

View File

@@ -7,6 +7,6 @@ SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Count$Compare Y LTZ.2.0
SVar:Y:Remembered$CardManaCost
SVar:Z:Sacrificed$CardManaCost
DeckNeeds:Type$Artifact|Creature|Equipment|Vehicle
AI:RemoveDeck:Random
DeckNeeds:Type$Artifact|Creature|Equipment|Vehicle
Oracle:As an additional cost to cast this spell, sacrifice an artifact or creature.\nSearch your library for an Equipment or Vehicle card, put that card onto the battlefield, then shuffle. If it has mana value less than the sacrificed permanent's mana value, scry 2.

View File

@@ -1,11 +1,11 @@
Name:Ancient Cellarspawn
ManaCost:1 B B
Types:Enchantment Creature Horror
PT:3/3
S:Mode$ ReduceCost | ValidCard$ Demon,Horror,Nightmare | Type$ Spell | Activator$ You | Amount$ 1 | Description$ Each spell you cast that's a Demon, Horror, or Nightmare costs {1} less to cast.
T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ You | Execute$ TrigLoseLife | TriggerZones$ Battlefield | ValidSAonCard$ Spell.ManaSpent LTX | TriggerDescription$ Whenever you cast a spell, if the amount of mana spent to cast it was less than its mana value, target opponent loses life equal to the difference.
SVar:TrigLoseLife:DB$ LoseLife | ValidTgts$ Opponent | LifeAmount$ SVar$Y/Minus.Z
SVar:X:Count$CardManaCost
SVar:Y:TriggeredCard$CardManaCost
SVar:Z:TriggeredCard$CastTotalManaSpent
Oracle:Each spell you cast that's a Demon, Horror, or Nightmare costs {1} less to cast.\nWhenever you cast a spell, if the amount of mana spent to cast it was less than its mana value, target opponent loses life equal to the difference.
Name:Ancient Cellarspawn
ManaCost:1 B B
Types:Enchantment Creature Horror
PT:3/3
S:Mode$ ReduceCost | ValidCard$ Demon,Horror,Nightmare | Type$ Spell | Activator$ You | Amount$ 1 | Description$ Each spell you cast that's a Demon, Horror, or Nightmare costs {1} less to cast.
T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ You | Execute$ TrigLoseLife | TriggerZones$ Battlefield | ValidSAonCard$ Spell.ManaSpent LTX | TriggerDescription$ Whenever you cast a spell, if the amount of mana spent to cast it was less than its mana value, target opponent loses life equal to the difference.
SVar:TrigLoseLife:DB$ LoseLife | ValidTgts$ Opponent | LifeAmount$ SVar$Y/Minus.Z
SVar:X:Count$CardManaCost
SVar:Y:TriggeredCard$CardManaCost
SVar:Z:TriggeredCard$CastTotalManaSpent
Oracle:Each spell you cast that's a Demon, Horror, or Nightmare costs {1} less to cast.\nWhenever you cast a spell, if the amount of mana spent to cast it was less than its mana value, target opponent loses life equal to the difference.

View File

@@ -6,6 +6,6 @@ K:Flying
T:Mode$ DamageDealtOnce | ValidSource$ Card.Self | Execute$ TrigChange | Delirium$ True | TriggerZones$ Battlefield | TriggerDescription$ Delirium — Whenever CARDNAME deals damage, if there are four or more card types among cards in your graveyard, exile target creature an opponent controls.
SVar:TrigChange:DB$ ChangeZone | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | Origin$ Battlefield | Destination$ Exile
SVar:HasCombatEffect:TRUE
DeckHints:Ability$Graveyard|Discard
DeckHas:Ability$Delirium
DeckHints:Ability$Graveyard|Discard
Oracle:Flying\nDelirium — Whenever Angel of Deliverance deals damage, if there are four or more card types among cards in your graveyard, exile target creature an opponent controls.

View File

@@ -7,6 +7,6 @@ K:Vigilance
K:Lifelink
K:Fabricate:2
S:Mode$ Continuous | Affected$ Creature.Other+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Other creatures you control get +1/+1.
DeckHas:Ability$Counters|Token
SVar:PlayMain1:TRUE
DeckHas:Ability$Counters|Token
Oracle:Flying, vigilance, lifelink\nFabricate 2 (When this creature enters, put two +1/+1 counters on it or create two 1/1 colorless Servo artifact creature tokens.)\nOther creatures you control get +1/+1.

View File

@@ -9,7 +9,7 @@ T:Mode$ SpellCast | ValidCard$ Card.Party | ValidActivatingPlayer$ You | Execute
SVar:TrigChoose:DB$ ChooseCard | ChoiceZone$ Hand | Choices$ Creature.Party+YouOwn | ChoiceTitle$ Choose a party creature card in your hand | Amount$ 1 | SubAbility$ DBPump
SVar:DBPump:DB$ Pump | Defined$ ChosenCard | PumpZone$ Hand | NumAtt$ 1 | NumDef$ 1 | Duration$ Perpetual | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True
DeckHas:Ability$Party|LifeGain
SVar:BuffedBy:Cleric,Rogue,Warrior,Wizard
DeckHas:Ability$Party|LifeGain
DeckHints:Type$Rogue|Warrior|Wizard
Oracle:Flying, lifelink\nWhenever Angel of Unity enters or you cast a party spell, choose a party creature card in your hand. It perpetually gets +1/+1. (A party card or spell is a Cleric, Rogue, Warrior, or Wizard.)

View File

@@ -3,6 +3,6 @@ ManaCost:3 W
Types:Instant
S:Mode$ AlternativeCost | ValidSA$ Spell.Self | EffectZone$ All | Cost$ tapXType<1/Creature> | IsPresent$ Plains.YouCtrl | Description$ If you control a Plains, you may tap an untapped creature you control rather than pay this spell's mana cost.
A:SP$ Token | TokenScript$ w_4_4_angel_flying | AtEOT$ Exile | ActivationPhases$ BeginCombat->EndCombat | StackDescription$ {p:You} creates a 4/4 white Angel creature token with flying. Exile it at the beginning of the next end step. | SpellDescription$ Cast this spell only during combat. Create a 4/4 white Angel creature token with flying. Exile it at the beginning of the next end step.
DeckHas:Ability$Token
AI:RemoveDeck:All
DeckHas:Ability$Token
Oracle:If you control a Plains, you may tap an untapped creature you control rather than pay this spell's mana cost.\nCast this spell only during combat.\nCreate a 4/4 white Angel creature token with flying. Exile it at the beginning of the next end step.

View File

@@ -5,6 +5,6 @@ PT:2/3
K:Flying
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Permanent.YouCtrl+Other+HasCounters | TriggerZones$ Battlefield | Execute$ TrigInvestigate | TriggerDescription$ Whenever another permanent you control leaves the battlefield, if it had counters on it, investigate.
SVar:TrigInvestigate:DB$ Investigate
DeckHints:Ability$Counters
DeckHas:Ability$Investigate|Token|Sacrifice & Type$Artifact|Clue
DeckHints:Ability$Counters
Oracle:Flying\nWhenever another permanent you control leaves the battlefield, if it had counters on it, investigate.

View File

@@ -9,8 +9,8 @@ T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigExile | Secondary$ True |
SVar:TrigExile:DB$ ChangeZone | ValidTgts$ Enchantment.nonAura+YouCtrl | Origin$ Graveyard | TargetMin$ 0 | TargetMax$ 1 | Destination$ Exile | TgtPrompt$ Select up to one target non-Aura enchantment card from your graveyard | RememberChanged$ True | SubAbility$ DBCopy
SVar:DBCopy:DB$ CopyPermanent | Defined$ Remembered | SetPower$ 3 | SetToughness$ 3 | AddTypes$ Creature & Zombie | SetColor$ Black | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
DeckHas:Ability$Token|Graveyard
DeckNeeds:Type$Enchantment
DeckHints:Ability$Graveyard|Mill
SVar:HasAttackEffect:TRUE
DeckHas:Ability$Token|Graveyard
DeckHints:Ability$Graveyard|Mill
DeckNeeds:Type$Enchantment
Oracle:Menace\nOther enchantment creatures you control have menace.\nWhenever Anikthea enters or attacks, exile up to one target non-Aura enchantment card from your graveyard. Create a token that's a copy of that card, except it's a 3/3 black Zombie creature in addition to its other types.

View File

@@ -4,6 +4,6 @@ Types:Artifact
T:Mode$ CounterAddedOnce | ValidCard$ Permanent.YouCtrl | TriggerZones$ Battlefield | CounterType$ P1P1 | Execute$ TrigToken | TriggerDescription$ Whenever one or more +1/+1 counters are put on a permanent you control, you may pay {1}. If you do, create a 1/1 colorless Servo artifact creature token.
SVar:TrigToken:AB$ Token | Cost$ 1 | TokenAmount$ 1 | TokenScript$ c_1_1_a_servo | TokenOwner$ You
A:AB$ PutCounter | Cost$ 3 T | ValidTgts$ Permanent,Player | TgtPrompt$ Select target player or permanent | CounterType$ ExistingCounter | CounterNum$ 1 | AILogic$ AtOppEOT | SpellDescription$ Choose a counter on target permanent or player. Give that permanent or player another counter of that kind.
DeckHints:Ability$Counters
AI:RemoveDeck:All
DeckHints:Ability$Counters
Oracle:Whenever one or more +1/+1 counters are put on a permanent you control, you may pay {1}. If you do, create a 1/1 colorless Servo artifact creature token.\n{3}, {T}: Choose a counter on target permanent or player. Give that permanent or player another counter of that kind.

View File

@@ -3,6 +3,6 @@ ManaCost:1 B
Types:Instant
A:SP$ ChangeZone | Defined$ Targeted | ValidTgts$ Creature | ConditionCheckSVar$ X | ConditionSVarCompare$ GE3 | Origin$ Battlefield | Destination$ Exile | SubAbility$ NotPoisoned | SpellDescription$ Exile target creature if it has mana value 3 or less. Corrupted — Exile that creature instead if its controller has three or more poison counters.
SVar:NotPoisoned:DB$ ChangeZone | Defined$ Targeted | Origin$ Battlefield | Destination$ Exile | ConditionDefined$ Targeted | ConditionPresent$ Creature.cmcLE3
DeckHints:Ability$Proliferate & Keyword$Infect|Toxic
SVar:X:TargetedController$Counters.Poison
DeckHints:Ability$Proliferate & Keyword$Infect|Toxic
Oracle:Exile target creature if it has mana value 3 or less.\nCorrupted — Exile that creature instead if its controller has three or more poison counters.

View File

@@ -8,6 +8,6 @@ SVar:TrigMill:DB$ Mill | Defined$ TriggeredTarget | NumCards$ X | RememberMilled
SVar:DBDraw:DB$ Draw | Defined$ You | ConditionDefined$ Remembered | ConditionPresent$ Creature | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:TriggerCount$DamageAmount
DeckNeeds:Type$Rogue
DeckHas:Ability$Mill
DeckNeeds:Type$Rogue
Oracle:Other Rogues you control get +1/+1.\nWhenever one or more Rogues you control deal combat damage to a player, that player mills a card for each 1 damage dealt to them. If the player mills at least one creature card this way, you draw a card. (To mill a card, a player puts the top card of their library into their graveyard.)

View File

@@ -0,0 +1,11 @@
Name:Anthropede
ManaCost:3 G
Types:Creature Insect
PT:3/4
K:Reach
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChoice | TriggerDescription$ When CARDNAME enters, you may discard a card or pay {2}. When you do, destroy target Room.
SVar:TrigChoice:DB$ GenericChoice | Choices$ PayDiscard,Pay2
SVar:Pay2:DB$ ImmediateTrigger | UnlessCost$ 2 | UnlessPayer$ You | UnlessSwitched$ True | Execute$ TrigDestroy | SpellDescription$ pay {2}: When you do, destroy target Room.
SVar:PayDiscard:DB$ ImmediateTrigger | UnlessCost$ Discard<1/Card> | UnlessPayer$ You | UnlessSwitched$ True | Execute$ TrigDestroy | SpellDescription$ discard a card: When you do, destroy target Room.
SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Room | TgtPrompt$ Select target Room
Oracle:Reach\nWhen Anthropede enters, you may discard a card or pay {2}. When you do, destroy target Room.

View File

@@ -1,10 +1,10 @@
Name:Appendage Amalgam
ManaCost:2 B
Types:Enchantment Creature Horror
PT:3/2
K:Flash
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigSurveil | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME attacks, surveil 1. (Look at the top card of your library. You may put that card into your graveyard.)
SVar:TrigSurveil:DB$ Surveil | Amount$ 1
SVar:HasAttackEffect:TRUE
DeckHas:Ability$Surveil|Graveyard
Oracle:Flash\nWhenever Appendage Amalgam attacks, surveil 1. (Look at the top card of your library. You may put that card into your graveyard.)
Name:Appendage Amalgam
ManaCost:2 B
Types:Enchantment Creature Horror
PT:3/2
K:Flash
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigSurveil | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME attacks, surveil 1. (Look at the top card of your library. You may put that card into your graveyard.)
SVar:TrigSurveil:DB$ Surveil | Amount$ 1
SVar:HasAttackEffect:TRUE
DeckHas:Ability$Surveil|Graveyard
Oracle:Flash\nWhenever Appendage Amalgam attacks, surveil 1. (Look at the top card of your library. You may put that card into your graveyard.)

View File

@@ -4,7 +4,7 @@ Types:Creature Spider Mutant
PT:0/0
K:Graft:2
A:AB$ Pump | Cost$ G | ValidTgts$ Creature.counters_GE1_P1P1 | TgtPrompt$ Select target creature with a +1/+1 counter | KW$ Reach | SpellDescription$ Target creature with a +1/+1 counter on it gains reach until end of turn. (It can block creatures with flying.)
DeckNeeds:Ability$Counters
DeckHas:Ability$Counters
SVar:AIGraftPreference:DontMoveCounterIfLethal
DeckHas:Ability$Counters
DeckNeeds:Ability$Counters
Oracle:Graft 2 (This creature enters with two +1/+1 counters on it. Whenever another creature enters, you may move a +1/+1 counter from this creature onto it.)\n{G}: Target creature with a +1/+1 counter on it gains reach until end of turn. (It can block creatures with flying.)

View File

@@ -4,8 +4,8 @@ Types:Creature Elemental
PT:1/3
T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | ActivatorThisTurnCast$ EQ1 | Execute$ TrigPump | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast your first instant or sorcery spell each turn, CARDNAME gets +2/+0 until end of turn.
SVar:TrigPump:DB$ Pump | Defined$ Self | NumAtt$ 2
DeckHints:Type$Instant|Sorcery
DeckHas:Ability$Graveyard
DeckHints:Type$Instant|Sorcery
AlternateMode:Adventure
Oracle:Whenever you cast your first instant or sorcery spell each turn, Aquatic Alchemist gets +2/+0 until end of turn.

View File

@@ -1,9 +1,9 @@
Name:Arabella, Abandoned Doll
ManaCost:R W
Types:Legendary Artifact Creature Toy
PT:1/3
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigDamageAll | TriggerDescription$ Whenever CARDNAME attacks, it deals X damage to each opponent and you gain X life, where X is the number of creatures you control with power 2 or less.
SVar:TrigDamageAll:DB$ DamageAll | ValidPlayers$ Player.Opponent | NumDmg$ X | SubAbility$ DBGainLife
SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ X
SVar:X:Count$Valid Creature.YouCtrl+powerLE2
Oracle:Whenever Arabella, Abandoned Doll attacks, it deals X damage to each opponent and you gain X life, where X is the number of creatures you control with power 2 or less.
Name:Arabella, Abandoned Doll
ManaCost:R W
Types:Legendary Artifact Creature Toy
PT:1/3
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigDamageAll | TriggerDescription$ Whenever CARDNAME attacks, it deals X damage to each opponent and you gain X life, where X is the number of creatures you control with power 2 or less.
SVar:TrigDamageAll:DB$ DamageAll | ValidPlayers$ Player.Opponent | NumDmg$ X | SubAbility$ DBGainLife
SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ X
SVar:X:Count$Valid Creature.YouCtrl+powerLE2
Oracle:Whenever Arabella, Abandoned Doll attacks, it deals X damage to each opponent and you gain X life, where X is the number of creatures you control with power 2 or less.

View File

@@ -8,6 +8,6 @@ T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPutCounterAll | Secondary$
SVar:TrigPutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl+StrictlyOther | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBGainLife
SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ X
SVar:X:Count$Valid Creature.YouCtrl+StrictlyOther
DeckHas:Ability$Counters|LifeGain
SVar:HasAttackEffect:TRUE
DeckHas:Ability$Counters|LifeGain
Oracle:Vigilance\nWhenever Aragorn and Arwen, Wed enters or attacks, put a +1/+1 counter on each other creature you control. You gain 1 life for each other creature you control.

View File

@@ -3,6 +3,6 @@ ManaCost:U R
Types:Instant
A:SP$ Dig | DigNum$ 4 | ChangeNum$ 1 | Optional$ True | ForceRevealToController$ True | ChangeValid$ Card.Instant,Card.Sorcery | RestRandomOrder$ True | StackDescription$ SpellDescription | SpellDescription$ Look at the top four cards of your library. You may reveal an instant or sorcery card from among them and put it into your hand. Put the rest on the bottom of your library in a random order.
K:Flashback:3 U R
DeckNeeds:Type$Instant|Sorcery
DeckHas:Ability$Graveyard
DeckNeeds:Type$Instant|Sorcery
Oracle:Look at the top four cards of your library. You may reveal an instant or sorcery card from among them and put it into your hand. Put the rest on the bottom of your library in a random order.\nFlashback {3}{U}{R} (You may cast this card from your graveyard for its flashback cost. Then exile it.)

View File

@@ -2,6 +2,6 @@ Name:Arcane Melee
ManaCost:4 U
Types:Enchantment
S:Mode$ ReduceCost | ValidCard$ Instant,Sorcery | Type$ Spell | Amount$ 2 | Description$ Instant and sorcery spells cost {2} less to cast.
DeckNeeds:Type$Instant|Sorcery
AI:RemoveDeck:Random
DeckNeeds:Type$Instant|Sorcery
Oracle:Instant and sorcery spells cost {2} less to cast.

View File

@@ -7,6 +7,6 @@ T:Mode$ ChangesZone | ValidCard$ Card.Self+wasCastByYou | Destination$ Battlefie
SVar:TrigExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Select target instant or sorcery card with mana value less than or equal to CARDNAME's power | ValidTgts$ Instant.YouOwn+cmcLEX,Sorcery.YouOwn+cmcLEX | RememberChanged$ True | SubAbility$ DBPlay
SVar:DBPlay:DB$ Play | Valid$ Card.IsRemembered | ValidZone$ Exile | Controller$ You | CopyCard$ True | WithoutManaCost$ True | ValidSA$ Spell | Optional$ True | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
DeckHints:Type$Instant|Sorcery & Color$Blue
SVar:X:Count$CardPower
DeckHints:Type$Instant|Sorcery & Color$Blue
Oracle:Prototype {1}{U}{U} — 2/1 (You may cast this spell with different mana cost, color, and size. It keeps its abilities and types.)\nWhen Arcane Proxy enters, if you cast it, exile target instant or sorcery card with mana value less than or equal to Arcane Proxy's power from your graveyard. Copy that card. You may cast the copy without paying its mana cost.

View File

@@ -6,6 +6,6 @@ K:First Strike
T:Mode$ ChangesZone | ValidCard$ Card.Self | Destination$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ When CARDNAME enters, put a +1/+1 counter on each other artifact creature you control.
SVar:TrigPutCounter:DB$ PutCounterAll | ValidCards$ Creature.Artifact+StrictlyOther+YouCtrl | CounterType$ P1P1 | CounterNum$ 1
K:Modular:2
DeckHas:Ability$Counters
SVar:PlayMain1:TRUE
DeckHas:Ability$Counters
Oracle:First strike\nWhen Arcbound Shikari enters, put a +1/+1 counter on each other artifact creature you control.\nModular 2 (This creature enters with two +1/+1 counters on it. When it dies, you may put its +1/+1 counters on target artifact creature.)

View File

@@ -3,7 +3,7 @@ ManaCost:6
Types:Artifact Creature Golem
PT:0/0
K:Modular:Sunburst
AI:RemoveDeck:Random
DeckHas:Ability$Counters
DeckHints:Ability$Proliferate
AI:RemoveDeck:Random
Oracle:Modular—Sunburst (This creature enters with a +1/+1 counter on it for each color of mana spent to cast it. When it dies, you may put its +1/+1 counters on target artifact creature.)

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