mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 02:38:02 +00:00
Merge branch 'coremaster' into rnabuild
This commit is contained in:
@@ -95,9 +95,15 @@ public class ComputerUtilAbility {
|
||||
|
||||
public static List<SpellAbility> getOriginalAndAltCostAbilities(final List<SpellAbility> originList, final Player player) {
|
||||
final List<SpellAbility> newAbilities = Lists.newArrayList();
|
||||
for (SpellAbility sa : originList) {
|
||||
sa.setActivatingPlayer(player);
|
||||
|
||||
List<SpellAbility> originListWithAddCosts = Lists.newArrayList();
|
||||
for (SpellAbility sa : originList) {
|
||||
// If this spell has alternative additional costs, add them instead of the unmodified SA itself
|
||||
sa.setActivatingPlayer(player);
|
||||
originListWithAddCosts.addAll(GameActionUtil.getAdditionalCostSpell(sa));
|
||||
}
|
||||
|
||||
for (SpellAbility sa : originListWithAddCosts) {
|
||||
// determine which alternative costs are cheaper than the original and prioritize them
|
||||
List<SpellAbility> saAltCosts = GameActionUtil.getAlternativeCosts(sa, player);
|
||||
List<SpellAbility> priorityAltSa = Lists.newArrayList();
|
||||
|
||||
@@ -102,6 +102,7 @@ public class DeckRecognizer {
|
||||
// Pattern.compile("(.*)[^A-Za-wyz]*\\s+([\\d]{1,2})");
|
||||
private static final Pattern SEARCH_NUMBERS_IN_FRONT = Pattern.compile("([\\d]{1,2})[^A-Za-wyz]*\\s+(.*)");
|
||||
//private static final Pattern READ_SEPARATED_EDITION = Pattern.compile("[[\\(\\{]([a-zA-Z0-9]){1,3})[]*\\s+(.*)");
|
||||
private static final Pattern SEARCH_SINGLE_SLASH = Pattern.compile("(?<=[^/])\\s*/\\s*(?=[^/])");
|
||||
|
||||
private final SetPreference useLastSet;
|
||||
private final ICardDatabase db;
|
||||
@@ -125,7 +126,10 @@ public class DeckRecognizer {
|
||||
return new Token(TokenType.Comment, 0, rawLine);
|
||||
}
|
||||
final char smartQuote = (char) 8217;
|
||||
final String line = rawLine.trim().replace(smartQuote, '\'');
|
||||
String line = rawLine.trim().replace(smartQuote, '\'');
|
||||
|
||||
// Some websites export split card names with a single slash. Replace with double slash.
|
||||
line = SEARCH_SINGLE_SLASH.matcher(line).replaceFirst(" // ");
|
||||
|
||||
Token result = null;
|
||||
final Matcher foundNumbersInFront = DeckRecognizer.SEARCH_NUMBERS_IN_FRONT.matcher(line);
|
||||
|
||||
@@ -29,6 +29,7 @@ import forge.game.ability.effects.AttachEffect;
|
||||
import forge.game.card.*;
|
||||
import forge.game.event.*;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.keyword.KeywordsChange;
|
||||
import forge.game.player.GameLossReason;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
@@ -292,6 +293,33 @@ public class GameAction {
|
||||
copied.getOwner().addInboundToken(copied);
|
||||
}
|
||||
|
||||
if (toBattlefield) {
|
||||
// HACK for making the RIOT enchantment look into the Future
|
||||
// need to check the Keywords what it would have on the Battlefield
|
||||
Card riotLKI = CardUtil.getLKICopy(copied);
|
||||
riotLKI.setLastKnownZone(zoneTo);
|
||||
CardCollection preList = new CardCollection(riotLKI);
|
||||
checkStaticAbilities(false, Sets.newHashSet(riotLKI), preList);
|
||||
|
||||
List<Long> changedTimeStamps = Lists.newArrayList();
|
||||
for(Map.Entry<Long, KeywordsChange> e : riotLKI.getChangedCardKeywords().entrySet()) {
|
||||
if (!copied.hasChangedCardKeywords(e.getKey())) {
|
||||
KeywordsChange o = e.getValue();
|
||||
o.setHostCard(copied);
|
||||
for (KeywordInterface k : o.getKeywords()) {
|
||||
for (ReplacementEffect re : k.getReplacements()) {
|
||||
// this param need to be set, otherwise in ReplaceMoved it fails
|
||||
re.getMapParams().put("BypassEtbCheck", "True");
|
||||
}
|
||||
}
|
||||
copied.addChangedCardKeywordsInternal(o, e.getKey());
|
||||
changedTimeStamps.add(e.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
checkStaticAbilities(false);
|
||||
}
|
||||
|
||||
Map<String, Object> repParams = Maps.newHashMap();
|
||||
repParams.put("Event", "Moved");
|
||||
repParams.put("Affected", copied);
|
||||
@@ -1099,7 +1127,7 @@ public class GameAction {
|
||||
|
||||
if (c.isAttachedToEntity()) {
|
||||
final GameEntity ge = c.getEntityAttachedTo();
|
||||
if (!ge.canBeAttached(c)) {
|
||||
if (!ge.canBeAttached(c, true)) {
|
||||
c.unattachFromEntity(ge);
|
||||
checkAgain = true;
|
||||
}
|
||||
|
||||
@@ -150,14 +150,14 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
return "";
|
||||
}
|
||||
|
||||
public static void makeChoices(SpellAbility sa) {
|
||||
public static boolean makeChoices(SpellAbility sa) {
|
||||
//this resets all previous choices
|
||||
sa.setSubAbility(null);
|
||||
|
||||
// Entwine does use all Choices
|
||||
if (sa.isEntwine()) {
|
||||
chainAbilities(sa, makePossibleOptions(sa));
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
final int num = sa.hasParam("CharmNumOnResolve") ?
|
||||
@@ -181,6 +181,7 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
|
||||
List<AbilitySub> chosen = chooser.getController().chooseModeForAbility(sa, min, num, sa.hasParam("CanRepeatModes"));
|
||||
chainAbilities(sa, chosen);
|
||||
return chosen != null && !chosen.isEmpty();
|
||||
}
|
||||
|
||||
private static void chainAbilities(SpellAbility sa, List<AbilitySub> chosen) {
|
||||
|
||||
@@ -266,8 +266,8 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
valid.removeAll(andOrCards); //pfps remove andOr cards to get two two choices set up correctly
|
||||
chosen = chooser.getController().chooseFromTwoListsForEffect(valid, andOrCards, optional, delayedReveal, sa, prompt, p);
|
||||
} else {
|
||||
int min = (anyNumber || optional) ? 0 : numToDig;
|
||||
int max = Math.max(destZone1ChangeNum, anyNumber ? valid.size() : 0);
|
||||
int max = anyNumber ? valid.size() : Math.min(valid.size(),destZone1ChangeNum);
|
||||
int min = (anyNumber || optional) ? 0 : max;
|
||||
chosen = chooser.getController().chooseEntitiesForEffect(valid, min, max, delayedReveal, sa, prompt, p);
|
||||
}
|
||||
chooser.getController().endTempShowCards();
|
||||
|
||||
@@ -1465,7 +1465,8 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (keyword.startsWith("CantBeCounteredBy")) {
|
||||
if (keyword.startsWith("CantBeCounteredBy") || keyword.startsWith("Panharmonicon")
|
||||
|| keyword.startsWith("Dieharmonicon")) {
|
||||
final String[] p = keyword.split(":");
|
||||
sbLong.append(p[2]).append("\r\n");
|
||||
} else if (keyword.startsWith("etbCounter")) {
|
||||
@@ -3404,6 +3405,15 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
return change;
|
||||
}
|
||||
|
||||
public final boolean hasChangedCardKeywords(final long timestamp) {
|
||||
return changedCardKeywords.containsKey(timestamp);
|
||||
}
|
||||
|
||||
public final void addChangedCardKeywordsInternal(final KeywordsChange change, final long timestamp) {
|
||||
changedCardKeywords.put(timestamp, change);
|
||||
updateKeywordsCache(currentState);
|
||||
}
|
||||
|
||||
// Hidden keywords will be left out
|
||||
public final Collection<KeywordInterface> getUnhiddenKeywords() {
|
||||
return getUnhiddenKeywords(currentState);
|
||||
@@ -5731,7 +5741,7 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
public void setChangedCardKeywords(Map<Long, KeywordsChange> changedCardKeywords) {
|
||||
this.changedCardKeywords.clear();
|
||||
for (Entry<Long, KeywordsChange> entry : changedCardKeywords.entrySet()) {
|
||||
this.changedCardKeywords.put(entry.getKey(), entry.getValue());
|
||||
this.changedCardKeywords.put(entry.getKey(), entry.getValue().copy(this, true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,6 @@ import forge.card.mana.ManaCost;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.CharmEffect;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementHandler;
|
||||
@@ -680,9 +678,6 @@ public class CardFactory {
|
||||
}
|
||||
|
||||
trig.setStackDescription(trig.toString());
|
||||
if (trig.getApi() == ApiType.Charm && !trig.isWrapper()) {
|
||||
CharmEffect.makeChoices(trig);
|
||||
}
|
||||
|
||||
WrappedAbility wrapperAbility = new WrappedAbility(t, trig, ((WrappedAbility) sa).getDecider());
|
||||
wrapperAbility.setTrigger(true);
|
||||
|
||||
@@ -1239,7 +1239,7 @@ public class CardProperty {
|
||||
} else if (property.startsWith("greatestPower")) {
|
||||
CardCollectionView cards = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES);
|
||||
if (property.contains("ControlledBy")) {
|
||||
FCollectionView<Player> p = AbilityUtils.getDefinedPlayers(source, property.split("ControlledBy")[1], null);
|
||||
FCollectionView<Player> p = AbilityUtils.getDefinedPlayers(source, property.split("ControlledBy")[1], spellAbility);
|
||||
cards = CardLists.filterControlledBy(cards, p);
|
||||
if (!cards.contains(card)) {
|
||||
return false;
|
||||
|
||||
@@ -43,6 +43,9 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.sentry.Sentry;
|
||||
import io.sentry.event.BreadcrumbBuilder;
|
||||
|
||||
public class CardState extends GameObject {
|
||||
private String name = "";
|
||||
private CardType type = new CardType();
|
||||
@@ -213,7 +216,19 @@ public class CardState extends GameObject {
|
||||
if (s.trim().length() == 0) {
|
||||
return null;
|
||||
}
|
||||
KeywordInterface inst = intrinsicKeywords.add(s);
|
||||
KeywordInterface inst = null;
|
||||
try {
|
||||
inst = intrinsicKeywords.add(s);
|
||||
} catch (Exception e) {
|
||||
String msg = "CardState:addIntrinsicKeyword: failed to parse Keyword";
|
||||
Sentry.getContext().recordBreadcrumb(
|
||||
new BreadcrumbBuilder().setMessage(msg)
|
||||
.withData("Card", card.getName()).withData("Keyword", s).build()
|
||||
);
|
||||
|
||||
//rethrow
|
||||
throw new RuntimeException("Error in Keyword " + s + " for card " + card.getName(), e);
|
||||
}
|
||||
if (inst != null && initTraits) {
|
||||
inst.createTraits(card, true);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Table;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
@@ -45,4 +46,39 @@ public class CardZoneTable extends ForwardingTable<ZoneType, ZoneType, CardColle
|
||||
game.getTriggerHandler().runTrigger(TriggerType.ChangesZoneAll, runParams, false);
|
||||
}
|
||||
}
|
||||
|
||||
public CardCollection filterCards(Iterable<ZoneType> origin, ZoneType destination, String valid, Card host, SpellAbility sa) {
|
||||
CardCollection allCards = new CardCollection();
|
||||
if (destination != null) {
|
||||
if (!containsColumn(destination)) {
|
||||
return allCards;
|
||||
}
|
||||
}
|
||||
if (origin != null) {
|
||||
for (ZoneType z : origin) {
|
||||
if (containsRow(z)) {
|
||||
if (destination != null) {
|
||||
allCards.addAll(row(z).get(destination));
|
||||
} else {
|
||||
for (CardCollection c : row(z).values()) {
|
||||
allCards.addAll(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (destination != null) {
|
||||
for (CardCollection c : column(destination).values()) {
|
||||
allCards.addAll(c);
|
||||
}
|
||||
} else {
|
||||
for (CardCollection c : values()) {
|
||||
allCards.addAll(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (valid != null) {
|
||||
allCards = CardLists.getValidCards(allCards, valid.split(","), host.getController(), host, sa);
|
||||
}
|
||||
return allCards;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import java.util.Iterator;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.MultimapBuilder;
|
||||
|
||||
import forge.game.card.Card;
|
||||
|
||||
public class KeywordCollection implements Iterable<String>, Serializable {
|
||||
private static final long serialVersionUID = -2882986558147844702L;
|
||||
|
||||
@@ -151,6 +153,12 @@ public class KeywordCollection implements Iterable<String>, Serializable {
|
||||
return map.get(keyword);
|
||||
}
|
||||
|
||||
public void setHostCard(final Card host) {
|
||||
for (KeywordInterface k : map.values()) {
|
||||
k.setHostCard(host);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return new Iterator<String>() {
|
||||
|
||||
@@ -202,7 +202,7 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
|
||||
public Collection<StaticAbility> getStaticAbilities() {
|
||||
return staticAbilities;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see forge.game.keyword.KeywordInterface#copy()
|
||||
@@ -233,7 +233,7 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
|
||||
|
||||
return result;
|
||||
} catch (final Exception ex) {
|
||||
throw new RuntimeException("KeywordInstance : clone() error, " + ex);
|
||||
throw new RuntimeException("KeywordInstance : clone() error", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,4 +252,26 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
|
||||
public boolean redundant(Collection<KeywordInterface> list) {
|
||||
return !list.isEmpty() && keyword.isMultipleRedundant;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.keyword.KeywordInterface#setHostCard(forge.game.card.Card)
|
||||
*/
|
||||
@Override
|
||||
public void setHostCard(Card host) {
|
||||
for (SpellAbility sa : this.abilities) {
|
||||
sa.setHostCard(host);
|
||||
}
|
||||
|
||||
for (Trigger tr : this.triggers) {
|
||||
tr.setHostCard(host);
|
||||
}
|
||||
|
||||
for (ReplacementEffect re : this.replacements) {
|
||||
re.setHostCard(host);
|
||||
}
|
||||
|
||||
for (StaticAbility sa : this.staticAbilities) {
|
||||
sa.setHostCard(host);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ public interface KeywordInterface extends Cloneable {
|
||||
public void addSpellAbility(final SpellAbility s);
|
||||
public void addStaticAbility(final StaticAbility st);
|
||||
|
||||
public void setHostCard(final Card host);
|
||||
|
||||
/**
|
||||
* @return the triggers
|
||||
|
||||
@@ -30,12 +30,11 @@ import forge.game.card.Card;
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id: KeywordsChange.java 27095 2014-08-17 07:32:24Z elcnesh $
|
||||
*/
|
||||
public class KeywordsChange {
|
||||
private final KeywordCollection keywords = new KeywordCollection();
|
||||
private final List<KeywordInterface> removeKeywordInterfaces = Lists.newArrayList();
|
||||
private final List<String> removeKeywords = Lists.newArrayList();
|
||||
public class KeywordsChange implements Cloneable {
|
||||
private KeywordCollection keywords = new KeywordCollection();
|
||||
private List<KeywordInterface> removeKeywordInterfaces = Lists.newArrayList();
|
||||
private List<String> removeKeywords = Lists.newArrayList();
|
||||
private boolean removeAllKeywords;
|
||||
private boolean removeIntrinsicKeywords;
|
||||
|
||||
@@ -63,7 +62,7 @@ public class KeywordsChange {
|
||||
this.removeAllKeywords = removeAll;
|
||||
this.removeIntrinsicKeywords = removeIntrinsic;
|
||||
}
|
||||
|
||||
|
||||
public KeywordsChange(
|
||||
final Collection<KeywordInterface> keywordList,
|
||||
final Collection<KeywordInterface> removeKeywordInterfaces,
|
||||
@@ -172,4 +171,49 @@ public class KeywordsChange {
|
||||
removeIntrinsicKeywords = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void setHostCard(final Card host) {
|
||||
keywords.setHostCard(host);
|
||||
for (KeywordInterface k : removeKeywordInterfaces) {
|
||||
k.setHostCard(host);
|
||||
}
|
||||
}
|
||||
|
||||
public KeywordsChange copy(final Card host, final boolean lki) {
|
||||
try {
|
||||
KeywordsChange result = (KeywordsChange)super.clone();
|
||||
|
||||
result.keywords = new KeywordCollection();
|
||||
for (KeywordInterface ki : this.keywords.getValues()) {
|
||||
result.keywords.insert(ki.copy(host, lki));
|
||||
}
|
||||
|
||||
result.removeKeywords = Lists.newArrayList(removeKeywords);
|
||||
|
||||
result.removeKeywordInterfaces = Lists.newArrayList();
|
||||
for (KeywordInterface ki : this.removeKeywordInterfaces) {
|
||||
removeKeywordInterfaces.add(ki.copy(host, lki));
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (final Exception ex) {
|
||||
throw new RuntimeException("KeywordsChange : clone() error", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("<+");
|
||||
sb.append(this.keywords);
|
||||
sb.append("|-");
|
||||
sb.append(this.removeKeywordInterfaces);
|
||||
sb.append("|-");
|
||||
sb.append(this.removeKeywords);
|
||||
sb.append(">");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ public class ReplaceMoved extends ReplacementEffect {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (zt.equals(ZoneType.Battlefield) && getHostCard().equals(affected)) {
|
||||
if (zt.equals(ZoneType.Battlefield) && getHostCard().equals(affected) && !hasParam("BypassEtbCheck")) {
|
||||
// would be an etb replacement effect that enters the battlefield
|
||||
Card lki = CardUtil.getLKICopy(affected);
|
||||
lki.setLastKnownZone(lki.getController().getZone(zt));
|
||||
|
||||
@@ -144,6 +144,8 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
|
||||
if (lkicheck) {
|
||||
game.getAction().checkStaticAbilities(false);
|
||||
game.getTracker().unfreeze();
|
||||
// reset owner for lki
|
||||
card.setController(null, 0);
|
||||
}
|
||||
|
||||
if (!(isInstant || activator.canCastSorcery() || flash || getRestrictions().isInstantSpeed()
|
||||
|
||||
@@ -42,48 +42,19 @@ public class TriggerChangesZoneAll extends Trigger {
|
||||
}
|
||||
|
||||
private CardCollection filterCards(CardZoneTable table) {
|
||||
CardCollection allCards = new CardCollection();
|
||||
ZoneType destination = null;
|
||||
List<ZoneType> origin = null;
|
||||
|
||||
if (hasParam("Destination")) {
|
||||
if (!getParam("Destination").equals("Any")) {
|
||||
destination = ZoneType.valueOf(getParam("Destination"));
|
||||
if (!table.containsColumn(destination)) {
|
||||
return allCards;
|
||||
}
|
||||
}
|
||||
if (hasParam("Destination") && !getParam("Destination").equals("Any")) {
|
||||
destination = ZoneType.valueOf(getParam("Destination"));
|
||||
}
|
||||
|
||||
if (hasParam("Origin") && !getParam("Origin").equals("Any")) {
|
||||
if (getParam("Origin") == null) {
|
||||
return allCards;
|
||||
}
|
||||
final List<ZoneType> origin = ZoneType.listValueOf(getParam("Origin"));
|
||||
for (ZoneType z : origin) {
|
||||
if (table.containsRow(z)) {
|
||||
if (destination != null) {
|
||||
allCards.addAll(table.row(z).get(destination));
|
||||
} else {
|
||||
for (CardCollection c : table.row(z).values()) {
|
||||
allCards.addAll(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (destination != null) {
|
||||
for (CardCollection c : table.column(destination).values()) {
|
||||
allCards.addAll(c);
|
||||
}
|
||||
} else {
|
||||
for (CardCollection c : table.values()) {
|
||||
allCards.addAll(c);
|
||||
}
|
||||
origin = ZoneType.listValueOf(getParam("Origin"));
|
||||
}
|
||||
|
||||
if (hasParam("ValidCards")) {
|
||||
allCards = CardLists.getValidCards(allCards, getParam("ValidCards").split(","),
|
||||
getHostCard().getController(), getHostCard(), null);
|
||||
}
|
||||
return allCards;
|
||||
final String valid = this.getParamOrDefault("ValidCards", null);
|
||||
|
||||
return table.filterCards(origin, destination, valid, getHostCard(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,10 @@ import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.CharmEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.Ability;
|
||||
@@ -42,6 +45,7 @@ import io.sentry.event.BreadcrumbBuilder;
|
||||
import java.util.*;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
@@ -376,11 +380,7 @@ public class TriggerHandler {
|
||||
// Static triggers
|
||||
for (final Trigger t : Lists.newArrayList(activeTriggers)) {
|
||||
if (t.isStatic() && canRunTrigger(t, mode, runParams)) {
|
||||
int x = 1 + handlePanharmonicon(t, runParams);
|
||||
|
||||
for (int i = 0; i < x; ++i) {
|
||||
runSingleTrigger(t, runParams);
|
||||
}
|
||||
runSingleTrigger(t, runParams);
|
||||
|
||||
checkStatics = true;
|
||||
}
|
||||
@@ -448,7 +448,7 @@ public class TriggerHandler {
|
||||
}
|
||||
}
|
||||
|
||||
int x = 1 + handlePanharmonicon(t, runParams);;
|
||||
int x = 1 + handlePanharmonicon(t, runParams, player);
|
||||
|
||||
for (int i = 0; i < x; ++i) {
|
||||
runSingleTrigger(t, runParams);
|
||||
@@ -636,7 +636,10 @@ public class TriggerHandler {
|
||||
|
||||
sa.setStackDescription(sa.toString());
|
||||
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
|
||||
CharmEffect.makeChoices(sa);
|
||||
if (!CharmEffect.makeChoices(sa)) {
|
||||
// 603.3c If no mode is chosen, the ability is removed from the stack.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Player decider = null;
|
||||
@@ -692,46 +695,81 @@ public class TriggerHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private int handlePanharmonicon(final Trigger t, final Map<String, Object> runParams) {
|
||||
// Need to get the last info from the trigger host
|
||||
final Card host = game.getChangeZoneLKIInfo(t.getHostCard());
|
||||
final Player p = host.getController();
|
||||
private int handlePanharmonicon(final Trigger t, final Map<String, Object> runParams, final Player p) {
|
||||
Card host = t.getHostCard();
|
||||
|
||||
// not a changesZone trigger
|
||||
if (t.getMode() != TriggerType.ChangesZone) {
|
||||
// not a changesZone trigger or changesZoneAll
|
||||
if (t.getMode() != TriggerType.ChangesZone && t.getMode() != TriggerType.ChangesZoneAll) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// leave battlefield trigger, might be dying
|
||||
// only real changeszone look back for this
|
||||
if (t.getMode() == TriggerType.ChangesZone && "Battlefield".equals(t.getParam("Origin"))) {
|
||||
// Need to get the last info from the trigger host
|
||||
host = game.getChangeZoneLKIInfo(host);
|
||||
}
|
||||
|
||||
// not a Permanent you control
|
||||
if (!host.isPermanent() || !host.isInZone(ZoneType.Battlefield)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int n = 0;
|
||||
for (final String kw : p.getKeywords()) {
|
||||
if (kw.startsWith("Panharmonicon")) {
|
||||
// Enter the Battlefield Trigger
|
||||
if (runParams.get("Destination") instanceof String) {
|
||||
final String dest = (String) runParams.get("Destination");
|
||||
if ("Battlefield".equals(dest) && runParams.get("Card") instanceof Card) {
|
||||
final Card card = (Card) runParams.get("Card");
|
||||
final String valid = kw.split(":")[1];
|
||||
if (card.isValid(valid.split(","), p, host, null)) {
|
||||
n++;
|
||||
if (t.getMode() == TriggerType.ChangesZone) {
|
||||
// iterate over all cards
|
||||
final List<Card> lastCards = CardLists.filterControlledBy(p.getGame().getLastStateBattlefield(), p);
|
||||
for (final Card ck : lastCards) {
|
||||
for (final KeywordInterface ki : ck.getKeywords()) {
|
||||
final String kw = ki.getOriginal();
|
||||
if (kw.startsWith("Panharmonicon")) {
|
||||
// Enter the Battlefield Trigger
|
||||
if (runParams.get("Destination") instanceof String) {
|
||||
final String dest = (String) runParams.get("Destination");
|
||||
if ("Battlefield".equals(dest) && runParams.get("Card") instanceof Card) {
|
||||
final Card card = (Card) runParams.get("Card");
|
||||
final String valid = kw.split(":")[1];
|
||||
if (card.isValid(valid.split(","), p, ck, null)) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (kw.startsWith("Dieharmonicon")) {
|
||||
// 700.4. The term dies means “is put into a graveyard from the battlefield.”
|
||||
if (runParams.get("Origin") instanceof String) {
|
||||
final String origin = (String) runParams.get("Origin");
|
||||
if ("Battlefield".equals(origin) && runParams.get("Destination") instanceof String) {
|
||||
final String dest = (String) runParams.get("Destination");
|
||||
if ("Graveyard".equals(dest) && runParams.get("Card") instanceof Card) {
|
||||
final Card card = (Card) runParams.get("Card");
|
||||
final String valid = kw.split(":")[1];
|
||||
if (card.isValid(valid.split(","), p, ck, null)) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (kw.startsWith("Dieharmonicon")) {
|
||||
// 700.4. The term dies means “is put into a graveyard from the battlefield.”
|
||||
if (runParams.get("Origin") instanceof String) {
|
||||
final String origin = (String) runParams.get("Origin");
|
||||
if ("Battlefield".equals(origin) && runParams.get("Destination") instanceof String) {
|
||||
final String dest = (String) runParams.get("Destination");
|
||||
if ("Graveyard".equals(dest) && runParams.get("Card") instanceof Card) {
|
||||
final Card card = (Card) runParams.get("Card");
|
||||
if (card.isCreature()) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
} else if (t.getMode() == TriggerType.ChangesZoneAll) {
|
||||
final CardZoneTable table = (CardZoneTable) runParams.get("Cards");
|
||||
// iterate over all cards
|
||||
for (final Card ck : p.getCardsIn(ZoneType.Battlefield)) {
|
||||
for (final KeywordInterface ki : ck.getKeywords()) {
|
||||
final String kw = ki.getOriginal();
|
||||
if (kw.startsWith("Panharmonicon")) {
|
||||
// currently there is no ChangesZoneAll that would trigger on etb
|
||||
final String valid = kw.split(":")[1];
|
||||
if (!table.filterCards(null, ZoneType.Battlefield, valid, ck, null).isEmpty()) {
|
||||
n++;
|
||||
}
|
||||
} else if (kw.startsWith("Dieharmonicon")) {
|
||||
// 700.4. The term dies means “is put into a graveyard from the battlefield.”
|
||||
final String valid = kw.split(":")[1];
|
||||
if (!table.filterCards(ImmutableList.of(ZoneType.Battlefield), ZoneType.Graveyard,
|
||||
valid, ck, null).isEmpty()) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ public class TriggerWaiting {
|
||||
private Map<String, Object> params;
|
||||
private List<Trigger> triggers = null;
|
||||
|
||||
public TriggerWaiting(TriggerType m, Map<String, Object> p) {
|
||||
public TriggerWaiting(TriggerType m, Map<String, Object> p) {
|
||||
mode = m;
|
||||
params = p;
|
||||
}
|
||||
@@ -25,7 +25,6 @@ public class TriggerWaiting {
|
||||
public Map<String, Object> getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
|
||||
public List<Trigger> getTriggers() {
|
||||
return triggers;
|
||||
@@ -35,7 +34,7 @@ public class TriggerWaiting {
|
||||
this.triggers = triggers;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public String toString() {
|
||||
return TextUtil.concatWithSpace("Waiting trigger:", mode.toString(),"with", params.toString());
|
||||
}
|
||||
|
||||
@@ -397,6 +397,7 @@ public final class CMatchUI
|
||||
case Hand:
|
||||
updateHand = true;
|
||||
updateZones = true;
|
||||
FloatingZone.refresh(owner, zone);
|
||||
break;
|
||||
default:
|
||||
updateZones = true;
|
||||
@@ -524,6 +525,45 @@ public final class CMatchUI
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelectables(final Iterable<CardView> cards) {
|
||||
super.setSelectables(cards);
|
||||
// update zones on tabletop and floating zones - non-selectable cards may be rendered differently
|
||||
FThreads.invokeInEdtNowOrLater(new Runnable() {
|
||||
@Override public final void run() {
|
||||
for (final PlayerView p : getGameView().getPlayers()) {
|
||||
if ( p.getCards(ZoneType.Battlefield) != null ) {
|
||||
updateCards(p.getCards(ZoneType.Battlefield));
|
||||
}
|
||||
if ( p.getCards(ZoneType.Hand) != null ) {
|
||||
updateCards(p.getCards(ZoneType.Hand));
|
||||
}
|
||||
}
|
||||
FloatingZone.refreshAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearSelectables() {
|
||||
super.clearSelectables();
|
||||
// update zones on tabletop and floating zones - non-selectable cards may be rendered differently
|
||||
FThreads.invokeInEdtNowOrLater(new Runnable() {
|
||||
@Override public final void run() {
|
||||
for (final PlayerView p : getGameView().getPlayers()) {
|
||||
if ( p.getCards(ZoneType.Battlefield) != null ) {
|
||||
updateCards(p.getCards(ZoneType.Battlefield));
|
||||
}
|
||||
if ( p.getCards(ZoneType.Hand) != null ) {
|
||||
updateCards(p.getCards(ZoneType.Hand));
|
||||
}
|
||||
}
|
||||
FloatingZone.refreshAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<JMenu> getMenus() {
|
||||
return menus.getMenus();
|
||||
|
||||
@@ -253,7 +253,7 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
g2d.rotate(getTappedAngle(), cardXOffset + edgeOffset, (cardYOffset + cardHeight)
|
||||
- edgeOffset);
|
||||
}
|
||||
super.paint(g2d);
|
||||
super.paint(g2d);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -268,25 +268,21 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
final int cornerSize = noBorderPref && !cardImgHasAlpha ? 0 : Math.max(4, Math.round(cardWidth * CardPanel.ROUNDED_CORNER_SIZE));
|
||||
final int offset = isTapped() && (!noBorderPref || cardImgHasAlpha) ? 1 : 0;
|
||||
|
||||
// Magenta outline for when card was chosen to pay
|
||||
// Magenta outline for when card is chosen
|
||||
if (matchUI.isUsedToPay(getCard())) {
|
||||
g2d.setColor(Color.magenta);
|
||||
final int n2 = Math.max(4, Math.round(2 * cardWidth * CardPanel.SELECTED_BORDER_SIZE));
|
||||
g2d.fillRoundRect(cardXOffset - n2, (cardYOffset - n2) + offset, cardWidth + (n2 * 2), cardHeight + (n2 * 2), cornerSize + n2, cornerSize + n2);
|
||||
} else if (matchUI.isSelectable(getCard())) { // Cyan outline for selectable cards
|
||||
g2d.setColor(Color.cyan);
|
||||
final int n2 = Math.max(4, Math.round(2 * cardWidth * CardPanel.SELECTED_BORDER_SIZE));
|
||||
final int n2 = Math.max(1, Math.round(2 * cardWidth * CardPanel.SELECTED_BORDER_SIZE));
|
||||
g2d.fillRoundRect(cardXOffset - n2, (cardYOffset - n2) + offset, cardWidth + (n2 * 2), cardHeight + (n2 * 2), cornerSize + n2, cornerSize + n2);
|
||||
}
|
||||
|
||||
// Green outline for hover
|
||||
if (isSelected) {
|
||||
g2d.setColor(Color.green);
|
||||
final int n = Math.max(4, Math.round(cardWidth * CardPanel.SELECTED_BORDER_SIZE));
|
||||
final int n = Math.max(1, Math.round(cardWidth * CardPanel.SELECTED_BORDER_SIZE));
|
||||
g2d.fillRoundRect(cardXOffset - n, (cardYOffset - n) + offset, cardWidth + (n * 2), cardHeight + (n * 2), cornerSize + n , cornerSize + n);
|
||||
}
|
||||
|
||||
// Black fill - (will become outline for white bordered cards)
|
||||
// Black fill - (will become an outline for white bordered cards)
|
||||
g2d.setColor(Color.black);
|
||||
g2d.fillRoundRect(cardXOffset, cardYOffset + offset, cardWidth, cardHeight, cornerSize, cornerSize);
|
||||
|
||||
@@ -309,6 +305,12 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
g2d.fillRoundRect(cardXOffset + ins, cardYOffset + ins, cardWidth - ins*2, cardHeight - ins*2, cornerSize-ins, cornerSize-ins);
|
||||
}
|
||||
}
|
||||
|
||||
if (matchUI.isSelectable(getCard())) { // White border for selectable cards to further highlight them
|
||||
g2d.setColor(Color.WHITE);
|
||||
final int ins = 1;
|
||||
g2d.fillRoundRect(cardXOffset+ins, cardYOffset+ins, cardWidth-ins*2, cardHeight-ins*2, cornerSize-ins, cornerSize-ins);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawManaCost(final Graphics g, final ManaCost cost, final int deltaY) {
|
||||
@@ -332,6 +334,17 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
drawFoilEffect(g, card, cardXOffset, cardYOffset,
|
||||
cardWidth, cardHeight, Math.round(cardWidth * BLACK_BORDER_SIZE));
|
||||
}
|
||||
|
||||
boolean nonselectable = matchUI.isSelecting() && !matchUI.isSelectable(getCard());
|
||||
// if selecting, darken non-selectable cards
|
||||
if ( nonselectable ) {
|
||||
boolean noBorderPref = !isPreferenceEnabled(FPref.UI_RENDER_BLACK_BORDERS);
|
||||
boolean cardImgHasAlpha = imagePanel != null && imagePanel.getSrcImage() != null && imagePanel.getSrcImage().getColorModel().hasAlpha();
|
||||
final int cornerSize = noBorderPref && !cardImgHasAlpha ? 0 : Math.max(4, Math.round(cardWidth * CardPanel.ROUNDED_CORNER_SIZE));
|
||||
final int offset = isTapped() && (!noBorderPref || cardImgHasAlpha) ? 1 : 0;
|
||||
g.setColor(new Color(0.0f,0.0f,0.0f,0.6f));
|
||||
g.fillRoundRect(cardXOffset, cardYOffset + offset, cardWidth, cardHeight, cornerSize, cornerSize);
|
||||
}
|
||||
}
|
||||
|
||||
public static void drawFoilEffect(final Graphics g, final CardView card2, final int x, final int y, final int width, final int height, final int borderSize) {
|
||||
@@ -783,6 +796,7 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
return FModel.getPreferences().getPrefBoolean(preferenceName);
|
||||
}
|
||||
|
||||
// don't show overlays on non-selectable cards when selecting
|
||||
private boolean isShowingOverlays() {
|
||||
return isPreferenceEnabled(FPref.UI_SHOW_CARD_OVERLAYS) && card != null;
|
||||
}
|
||||
|
||||
@@ -94,6 +94,11 @@ public class FloatingZone extends FloatingCardArea {
|
||||
}
|
||||
floatingAreas.clear();
|
||||
}
|
||||
public static void refreshAll() {
|
||||
for (final FloatingZone cardArea : floatingAreas.values()) {
|
||||
cardArea.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private final ZoneType zone;
|
||||
private PlayerView player;
|
||||
|
||||
@@ -1503,4 +1503,197 @@ public class GameSimulatorTest extends SimulationTestCase {
|
||||
}
|
||||
|
||||
|
||||
public void testRiotEnchantment() {
|
||||
Game game = initAndCreateGame();
|
||||
Player p = game.getPlayers().get(0);
|
||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
|
||||
|
||||
final String goblinName = "Zhur-Taa Goblin";
|
||||
|
||||
addCard("Rhythm of the Wild", p);
|
||||
|
||||
Card goblin = addCardToZone(goblinName, p, ZoneType.Hand);
|
||||
|
||||
addCard("Mountain", p);
|
||||
addCard("Forest", p);
|
||||
|
||||
SpellAbility goblinSA = goblin.getFirstSpellAbility();
|
||||
assertNotNull(goblinSA);
|
||||
|
||||
GameSimulator sim = createSimulator(game, p);
|
||||
int score = sim.simulateSpellAbility(goblinSA).value;
|
||||
assertTrue(score > 0);
|
||||
|
||||
Game simGame = sim.getSimulatedGameState();
|
||||
|
||||
Card simGoblin = findCardWithName(simGame, goblinName);
|
||||
|
||||
assertNotNull(simGoblin);
|
||||
int effects = simGoblin.getCounters(CounterType.P1P1) + simGoblin.getKeywordMagnitude(Keyword.HASTE);
|
||||
assertTrue(effects == 2);
|
||||
}
|
||||
|
||||
public void testTeysaKarlovXathridNecromancer() {
|
||||
// Teysa Karlov and Xathrid Necromancer dying at the same time makes 4 token
|
||||
|
||||
Game game = initAndCreateGame();
|
||||
Player p = game.getPlayers().get(0);
|
||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
|
||||
|
||||
addCard("Teysa Karlov", p);
|
||||
addCard("Xathrid Necromancer", p);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
addCardToZone("Plains", p, ZoneType.Battlefield);
|
||||
}
|
||||
|
||||
Card wrathOfGod = addCardToZone("Wrath of God", p, ZoneType.Hand);
|
||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
|
||||
|
||||
SpellAbility wrathSA = wrathOfGod.getFirstSpellAbility();
|
||||
assertNotNull(wrathSA);
|
||||
|
||||
GameSimulator sim = createSimulator(game, p);
|
||||
int score = sim.simulateSpellAbility(wrathSA).value;
|
||||
assertTrue(score > 0);
|
||||
Game simGame = sim.getSimulatedGameState();
|
||||
|
||||
int numZombies = countCardsWithName(simGame, "Zombie");
|
||||
assertTrue(numZombies == 4);
|
||||
}
|
||||
|
||||
public void testDoubleTeysaKarlovXathridNecromancer() {
|
||||
// Teysa Karlov dieing because of Legendary rule will make Xathrid Necromancer trigger 3 times
|
||||
|
||||
Game game = initAndCreateGame();
|
||||
Player p = game.getPlayers().get(0);
|
||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
|
||||
|
||||
addCard("Teysa Karlov", p);
|
||||
addCard("Xathrid Necromancer", p);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
addCard("Plains", p);
|
||||
}
|
||||
addCard("Swamp", p);
|
||||
|
||||
Card second = addCardToZone("Teysa Karlov", p, ZoneType.Hand);
|
||||
|
||||
SpellAbility secondSA = second.getFirstSpellAbility();
|
||||
|
||||
GameSimulator sim = createSimulator(game, p);
|
||||
int score = sim.simulateSpellAbility(secondSA).value;
|
||||
assertTrue(score > 0);
|
||||
Game simGame = sim.getSimulatedGameState();
|
||||
|
||||
int numZombies = countCardsWithName(simGame, "Zombie");
|
||||
assertTrue(numZombies == 3);
|
||||
}
|
||||
|
||||
|
||||
public void testTeysaKarlovGitrogMonster() {
|
||||
|
||||
Game game = initAndCreateGame();
|
||||
Player p = game.getPlayers().get(0);
|
||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
|
||||
|
||||
addCard("Teysa Karlov", p);
|
||||
addCard("The Gitrog Monster", p);
|
||||
addCard("Dryad Arbor", p);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
addCard("Plains", p);
|
||||
addCardToZone("Plains", p, ZoneType.Library);
|
||||
}
|
||||
|
||||
Card armageddon = addCardToZone("Armageddon", p, ZoneType.Hand);
|
||||
|
||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
|
||||
|
||||
SpellAbility armageddonSA = armageddon.getFirstSpellAbility();
|
||||
|
||||
GameSimulator sim = createSimulator(game, p);
|
||||
int score = sim.simulateSpellAbility(armageddonSA).value;
|
||||
assertTrue(score > 0);
|
||||
Game simGame = sim.getSimulatedGameState();
|
||||
|
||||
// Two cards drawn
|
||||
assertTrue(simGame.getPlayers().get(0).getZone(ZoneType.Hand).size() == 2);
|
||||
}
|
||||
|
||||
public void testTeysaKarlovGitrogMonsterGitrogDies() {
|
||||
|
||||
Game game = initAndCreateGame();
|
||||
Player p = game.getPlayers().get(0);
|
||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
|
||||
|
||||
Card teysa = addCard("Teysa Karlov", p);
|
||||
addCard("The Gitrog Monster", p);
|
||||
addCard("Dryad Arbor", p);
|
||||
|
||||
String indestructibilityName = "Indestructibility";
|
||||
Card indestructibility = addCard(indestructibilityName, p);
|
||||
|
||||
indestructibility.attachToEntity(teysa);
|
||||
|
||||
// update Indestructible state
|
||||
game.getAction().checkStateEffects(true);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
addCard("Plains", p);
|
||||
addCardToZone("Plains", p, ZoneType.Library);
|
||||
}
|
||||
|
||||
Card armageddon = addCardToZone("Wrath of God", p, ZoneType.Hand);
|
||||
|
||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
|
||||
|
||||
SpellAbility armageddonSA = armageddon.getFirstSpellAbility();
|
||||
|
||||
GameSimulator sim = createSimulator(game, p);
|
||||
int score = sim.simulateSpellAbility(armageddonSA).value;
|
||||
assertTrue(score > 0);
|
||||
Game simGame = sim.getSimulatedGameState();
|
||||
|
||||
// One cards drawn
|
||||
assertTrue(simGame.getPlayers().get(0).getZone(ZoneType.Hand).size() == 1);
|
||||
}
|
||||
|
||||
public void testTeysaKarlovGitrogMonsterTeysaDies() {
|
||||
|
||||
Game game = initAndCreateGame();
|
||||
Player p = game.getPlayers().get(0);
|
||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
|
||||
|
||||
addCard("Teysa Karlov", p);
|
||||
Card gitrog = addCard("The Gitrog Monster", p);
|
||||
addCard("Dryad Arbor", p);
|
||||
|
||||
String indestructibilityName = "Indestructibility";
|
||||
Card indestructibility = addCard(indestructibilityName, p);
|
||||
|
||||
indestructibility.attachToEntity(gitrog);
|
||||
|
||||
// update Indestructible state
|
||||
game.getAction().checkStateEffects(true);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
addCard("Plains", p);
|
||||
addCardToZone("Plains", p, ZoneType.Library);
|
||||
}
|
||||
|
||||
Card armageddon = addCardToZone("Wrath of God", p, ZoneType.Hand);
|
||||
|
||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
|
||||
|
||||
SpellAbility armageddonSA = armageddon.getFirstSpellAbility();
|
||||
|
||||
GameSimulator sim = createSimulator(game, p);
|
||||
int score = sim.simulateSpellAbility(armageddonSA).value;
|
||||
assertTrue(score > 0);
|
||||
Game simGame = sim.getSimulatedGameState();
|
||||
|
||||
// One cards drawn
|
||||
assertTrue(simGame.getPlayers().get(0).getZone(ZoneType.Hand).size() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1406,4 +1406,27 @@ Golgari Guildgate|GRN
|
||||
Izzet Guildgate|GRN
|
||||
Selesnya Guildgate|GRN
|
||||
Dimir Guildgate|GRN
|
||||
Boros Guildgate|GRN
|
||||
Boros Guildgate|GRN
|
||||
|
||||
[RNA Lands]
|
||||
10 Azorius Guildgate|RNA
|
||||
10 Gruul Guildgate|RNA
|
||||
10 Orzhov Guildgate|RNA
|
||||
10 Rakdos Guildgate|RNA
|
||||
10 Simic Guildgate|RNA
|
||||
|
||||
[RNA Secret Cards]
|
||||
Dovin, Architect of Law
|
||||
Elite Arrester
|
||||
Dovin's Dismissal
|
||||
Dovin's Automaton
|
||||
Domri, City Smasher
|
||||
Ragefire
|
||||
Charging War Boar
|
||||
Domri's Nodorog
|
||||
The Haunt of Hightower
|
||||
Azorius Guildgate|RNA
|
||||
Gruul Guildgate|RNA
|
||||
Orzhov Guildgate|RNA
|
||||
Rakdos Guildgate|RNA
|
||||
Simic Guildgate|RNA
|
||||
@@ -3,6 +3,10 @@ ManaCost:2 R R
|
||||
Types:Creature Elemental
|
||||
PT:1/1
|
||||
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDig | TriggerDescription$ At the beginning of your upkeep, reveal cards from the top of your library until you reveal a creature card. Until your next turn, CARDNAME's base power becomes twice that card's power and its toughness. Put the revealed cards on the bottom of your library in a random order.
|
||||
SVar:TrigDig:DB$ DigUntil | Valid$ Creature | ValidDescription$ creature card | FoundDestination$ Library | RevealedDestination$ Library | RevealedLibraryPosition$ -1 | RememberFound$ True | SubAbility$ DBAnimate
|
||||
SVar:DBAnimate:DB$ Animate | Power$ X | Toughness$ Y
|
||||
SVar:TrigDig:DB$ DigUntil | Reveal$ True | Valid$ Creature | ValidDescription$ creature card | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | ImprintRevealed$ True | SubAbility$ DBAnimate
|
||||
SVar:DBAnimate:DB$ Animate | Power$ X | Toughness$ Y | SubAbility$ DBMovetoLib
|
||||
SVar:DBMovetoLib:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered,Card.IsImprinted | Origin$ Exile | Destination$ Library | RandomOrder$ True | LibraryPosition$ -1 | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True
|
||||
SVar:X:Remembered$CardPower/Times.2
|
||||
SVar:Y:Remembered$CardToughness/Times.2
|
||||
Oracle:At the beginning of your upkeep, reveal cards from the top of your library until you reveal a creature card. Until your next turn, Amplifire's base power becomes twice that card's power and its base toughness becomes twice that card's toughness. Put the revealed cards on the bottom of your library in a random order.
|
||||
@@ -2,6 +2,7 @@ Name:Awaken the Erstwhile
|
||||
ManaCost:3 B B
|
||||
Types:Sorcery
|
||||
A:SP$ RepeatEach | Cost$ 3 B B | RepeatPlayers$ Player | RepeatSubAbility$ DBDiscard | SpellDescription$ Each player discards all the cards in their hand, then creates that many 2/2 black Zombie creature tokens.
|
||||
SVar:DBToken:DB$ Token | TokenAmount$ X | TokenName$ Zombie | TokenTypes$ Creature,Zombie | TokenOwner$ You | TokenColors$ Black | TokenPower$ 2 | TokenToughness$ 2 | References$ X
|
||||
SVar:DBToken:DB$ Token | TokenAmount$ X | TokenScript$ b_2_2_zombie | TokenOwner$ You | LegacyImage$ b 2 2 zombie rna | References$ X
|
||||
SVar:X:Remembered$Amount
|
||||
DeckHas:Ability$Token
|
||||
Oracle:Each player discards all the cards in their hand, then creates that many 2/2 black Zombie creature tokens.
|
||||
@@ -2,7 +2,7 @@ Name:Blade Juggler
|
||||
ManaCost:4 B
|
||||
Types:Creature Human Rogue
|
||||
PT:3/2
|
||||
K:Spectacle:2
|
||||
K:Spectacle:2 B
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDealDamage | TriggerDescription$ When CARDNAME enters the battlefield, it deals 1 damage to you and you draw a card.
|
||||
SVar:TrigDealDamage:DB$DealDamage | Defined$ You | NumDmg$ 1 | SubAbility$ DBDraw
|
||||
SVar:DBDraw:DB$Draw | Defined$ You | NumCards$ 1
|
||||
@@ -3,7 +3,5 @@ ManaCost:2 B
|
||||
Types:Creature Vampire
|
||||
PT:3/1
|
||||
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPump | TriggerDescription$ Whenever CARDNAME attacks, you may sacrifice another creature. If you do, CARDNAME can't be blocked this turn.
|
||||
SVar:TrigPump:AB$ Pump | Cost$ Sac<1/Creature.Other/another creature> | Defined$ Self | KW$ HIDDEN Unblockable | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ1 | SubAbility$ DBCleanup | References$ X
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
SVar:X:Remembered$Amount
|
||||
SVar:TrigPump:AB$ Pump | Cost$ Sac<1/Creature.Other/another creature> | Defined$ Self | KW$ HIDDEN Unblockable
|
||||
Oracle:Whenever Bloodmist Infiltrator attacks, you may sacrifice another creature. If you do, Bloodmist Infiltrator can't be blocked this turn.
|
||||
@@ -1,3 +1,4 @@
|
||||
# TODO: -- THIS SCRIPT NEEDS REWRITING (non-functional) --
|
||||
Name:Captive Audience
|
||||
ManaCost:5 B R
|
||||
Types:Enchantment
|
||||
@@ -21,4 +22,5 @@ T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | E
|
||||
SVar:TrigChoose:DB$ ChooseCard | Defined$ You | Choices$ Player.Opponent | Mandatory$ True | SubAbility$ DBChangeZone
|
||||
SVar:DBChangeZone:DB$ ChangeZone | Defined$ Self | Origin$ All | Destination$ Battlefield | GainControl$ True
|
||||
SVar:RemRandomDeck:True
|
||||
DeckHas:Ability$Token
|
||||
Oracle:Captive Audience enters the battlefield under the control of an opponent of your choice.\nAt the beginning of your upkeep, choose one that hasn't been chosen —\n• Your life total becomes 4.\n• Discard your hand.\n• Each opponent creates five 2/2 black Zombie creature tokens.
|
||||
7
forge-gui/res/cardsfolder/c/cavalcade_of_calamity.txt
Normal file
7
forge-gui/res/cardsfolder/c/cavalcade_of_calamity.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Name:Cavalcade of Calamity
|
||||
ManaCost:1 R
|
||||
Types:Enchantment
|
||||
T:Mode$ Attacks | ValidCard$ Creature.powerLE1+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigDamage | TriggerDescription$ Whenever a creature you control with power 1 or less attacks, CARDNAME deals 1 damage to the player or planeswalker that creature is attacking
|
||||
SVar:TrigDamage:DB$DealDamage | Defined$ TriggeredDefender | NumDmg$ 1
|
||||
SVar:PlayMain1:TRUE
|
||||
Oracle:Whenever a creature you control with power 1 or less attacks, Cavalcade of Calamity deals 1 damage to the player or planeswalker that creature is attacking.
|
||||
8
forge-gui/res/cardsfolder/c/charging_war_boar.txt
Normal file
8
forge-gui/res/cardsfolder/c/charging_war_boar.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Name:Charging War Boar
|
||||
ManaCost:1 R G
|
||||
Types:Creature Boar
|
||||
PT:3/1
|
||||
K:Haste
|
||||
S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Trample | AddPower$ 1 | AddToughness$ 1 | IsPresent$ Planeswalker.Domri+YouCtrl | Description$ As long as you control a Domri planeswalker, CARDNAME gets +1/+1 and has trample.
|
||||
SVar:BuffedBy:Domri
|
||||
Oracle:Haste (This creature can attack and {T} as soon as it comes under your control.)\nAs long as you control a Domri planeswalker, Charging War Boar gets +1/+1 and has trample. (It can deal excess damage to the player or planeswalker it’s attacking.)
|
||||
8
forge-gui/res/cardsfolder/c/cindervines.txt
Normal file
8
forge-gui/res/cardsfolder/c/cindervines.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Name:Cindervines
|
||||
ManaCost:R G
|
||||
Types:Enchantment
|
||||
T:Mode$ SpellCast | ValidCard$ Card.nonCreature | ValidActivatingPlayer$ Opponent | TriggerZones$ Battlefield | Execute$ TrigDealOneDamage | TriggerDescription$ Whenever an opponent casts a noncreature spell, CARDNAME deals 1 damage to that player.
|
||||
SVar:TrigDealOneDamage:DB$DealDamage | Defined$ TriggeredActivator | NumDmg$ 1
|
||||
A:AB$ Destroy | Cost$ 1 Sac<1/CARDNAME> | ValidTgts$ Artifact,Enchantment | TgtPrompt$ Select target artifact or enchantment | SubAbility$ DBDealTwoDamage | SpellDescription$ Destroy target artifact or enchantment. CARDNAME deals 2 damage to that permanent’s controller.
|
||||
SVar:DBDealTwoDamage:DB$ DealDamage | Defined$ TargetedController | NumDmg$ 2
|
||||
Oracle:Whenever an opponent casts a noncreature spell, Cindervines deals 1 damage to that player.\n{1}, Sacrifice Cindervines: Destroy target artifact or enchantment. Cindervines deals 2 damage to that permanent’s controller.
|
||||
8
forge-gui/res/cardsfolder/c/clamor_shaman.txt
Normal file
8
forge-gui/res/cardsfolder/c/clamor_shaman.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Name:Clamor Shaman
|
||||
ManaCost:2 R
|
||||
Types:Creature Shaman
|
||||
PT:1/1
|
||||
K:Riot
|
||||
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigCanNotBlock | TriggerDescription$ Whenever CARDNAME attacks, target creature an opponent controls can't block this turn.
|
||||
SVar:TrigCanNotBlock:DB$ Pump | ValidTgts$ Creature.OppCtrl | KW$ HIDDEN CARDNAME can't block. | TgtPrompt$ Select target creature an opponent controls | IsCurse$ True
|
||||
Oracle:Riot (This creature enters the battlefield with your choice of a +1/+1 counter or haste.)\nWhenever Clamor Shaman attacks, target creature an opponent controls can’t block this turn.
|
||||
@@ -4,6 +4,6 @@ Types:Instant
|
||||
A:SP$ Pump | Cost$ 2 U | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ -4 | IsCurse$ True | SubAbility$ DBDraw | SpellDescription$ Target creature gets -4/-0 until end of turn.
|
||||
SVar:DBDraw:DB$ Draw | NumCards$ 1 | SpellDescription$ Draw a card. | SubAbility$ DBAddendum
|
||||
SVar:DBAddendum:DB$ Tap | Defined$ Targeted | ConditionPlayerTurn$ True | ConditionPhases$ Main1,Main2 | SubAbility$ DBPump | SpellDescription$ Addendum - If you cast this spell during your main phase, tap that creature and it doesn't untap during its controller's next untap step.
|
||||
SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ HIDDEN This card doesn't untap during your next untap step. | Permanent$ True
|
||||
SVar:DBPump:DB$ Pump | Defined$ Targeted | ConditionPlayerTurn$ True | ConditionPhases$ Main1,Main2 | KW$ HIDDEN This card doesn't untap during your next untap step. | Permanent$ True
|
||||
SVar:PlayMain1:TRUE
|
||||
Oracle:Target creature gets -4/-0 until end of turn.\nDraw a card.\nAddendum — If you cast this spell during your main phase, tap that creature and it doesn't untap during its controller's next untap step.
|
||||
@@ -10,5 +10,5 @@ ALTERNATE
|
||||
Name:Colossus
|
||||
ManaCost:R G
|
||||
Types:Instant
|
||||
A:SP$ Pump | Cost$ 1 R | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +4 | NumDef$ +2 | KW$ Trample | SpellDescription$ Target creature gets +3/+1 and gains trample until end of turn.
|
||||
A:SP$ Pump | Cost$ R G | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +4 | NumDef$ +2 | KW$ Trample | SpellDescription$ Target creature gets +4/+2 and gains trample until end of turn.
|
||||
Oracle:Target creature gets +4/+2 and gains trample until end of turn.
|
||||
20
forge-gui/res/cardsfolder/c/consecrate_consume.txt
Normal file
20
forge-gui/res/cardsfolder/c/consecrate_consume.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
Name:Consecrate
|
||||
ManaCost:1 WB
|
||||
AlternateMode: Split
|
||||
Types:Instant
|
||||
A:SP$ ChangeZone | Cost$ 1 WB | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose target card in a graveyard | ValidTgts$ Card | SpellDescription$ Exile target card from a graveyard. | SubAbility$ DBDraw
|
||||
SVar:DBDraw:DB$ Draw | NumCards$ 1 | SpellDescription$ Draw a card.
|
||||
Oracle:Exile target card from a graveyard.\nDraw a card.
|
||||
|
||||
ALTERNATE
|
||||
|
||||
Name:Consume
|
||||
ManaCost:2 W B
|
||||
Types:Sorcery
|
||||
A:SP$ Pump | Cost$ 2 W B | ValidTgts$ Player | RememberTargets$ True | SubAbility$ DBChooseCard | SpellDescription$ Target player sacrifices a creature with the greatest power among creatures they control. You gain life equal to its power.
|
||||
SVar:DBChooseCard:DB$ ChooseCard | Defined$ Player.IsRemembered | Choices$ Creature.greatestPowerControlledByRemembered | Mandatory$ True | SubAbility$ DBSac
|
||||
SVar:DBSac:DB$ Sacrifice | Defined$ Player.IsRemembered | SacValid$ Card.ChosenCard | RememberSacrificed$ True | SubAbility$ DBGainLife | SacMessage$ the creature with the highest power
|
||||
SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ X | References$ X | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
SVar:X:RememberedLKI$CardPower
|
||||
Oracle:Target player sacrifices a creature with the greatest power among creatures they control. You gain life equal to its power.
|
||||
@@ -1,7 +1,7 @@
|
||||
Name:Cosmotronic Wave
|
||||
ManaCost:3 R
|
||||
Types:Sorcery
|
||||
A:SP$ DamageAll | Cost$ 3 R | ValidCards$ Creature.OppCtrl | NumDmg$ 1 | SubAbility$ DBPumpAll | SpellDescription$ CARDNAME deals 1 damage to each creature your opponents control. Creatures your opponents control can't block this turn.
|
||||
SVar:DBPumpAll:DB$ PumpAll | ValidCards$ Creature.OppCtrl | KW$ HIDDEN CARDNAME can't block. | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
A:SP$ DamageAll | Cost$ 3 R | ValidCards$ Creature.OppCtrl | NumDmg$ 1 | SubAbility$ CantBlock | SpellDescription$ CARDNAME deals 1 damage to each creature your opponents control. Creatures your opponents control can't block this turn.
|
||||
SVar:CantBlock:DB$ Effect | Name$ Cosmotronic Wave Effect | StaticAbilities$ KWPump | SpellDescription$ Creatures your opponents control can't block this turn.
|
||||
SVar:KWPump:Mode$ Continuous | EffectZone$ Command | Affected$ Creature.OppCtrl | AddHiddenKeyword$ CARDNAME can't block. | Description$ Creatures your opponents control can't block this turn.
|
||||
Oracle:Cosmotronic Wave deals 1 damage to each creature your opponents control. Creatures your opponents control can't block this turn.
|
||||
|
||||
@@ -11,7 +11,8 @@ ALTERNATE
|
||||
Name:Deploy
|
||||
ManaCost:2 W U
|
||||
Types:Instant
|
||||
A:SP$ Token | Cost$ 2 W U | TokenAmount$ 2 | TokenOwner$ You | TokenScript$ c_1_1_a_thopter_flying | SubAbility$ DBGainLife | SpellDescription$ Create two 1/1 colorless Thopter artifact creature tokens with flying, then you gain 1 life for each creature you control.
|
||||
A:SP$ Token | Cost$ 2 W U | TokenAmount$ 2 | TokenOwner$ You | TokenScript$ c_1_1_a_thopter_flying | LegacyImage$ c 1 1 a thopter flying rna | SubAbility$ DBGainLife | SpellDescription$ Create two 1/1 colorless Thopter artifact creature tokens with flying, then you gain 1 life for each creature you control.
|
||||
SVar:DBGainLife:DB$ GainLife | LifeAmount$ X | References$ X
|
||||
SVar:X:Count$TypeYouCtrl.Creature
|
||||
DeckHas:Ability$Token
|
||||
Oracle:Create two 1/1 colorless Thopter artifact creature tokens with flying, then you gain 1 life for each creature you control.
|
||||
13
forge-gui/res/cardsfolder/d/deputy_of_detention.txt
Normal file
13
forge-gui/res/cardsfolder/d/deputy_of_detention.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
Name:Deputy of Detention
|
||||
ManaCost:1 W U
|
||||
Types:Creature Vedalken Wizard
|
||||
PT:1/3
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When CARDNAME enters the battlefield, exile target nonland permanent an opponent controls and all other nonland permanents that player controls with the same name as that permanent until CARDNAME leaves the battlefield.
|
||||
SVar:TrigExile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Permanent.nonLand+OppCtrl | TgtPrompt$ Select target nonland permanent an opponent controls | RememberTargets$ True | SubAbility$ DBChangeZoneAll
|
||||
SVar:DBChangeZoneAll:DB$ ChangeZoneAll | Origin$ Battlefield | Destination$ Exile | ChangeType$ Remembered.sameName+OppCtrl | RememberChanged$ True | SubAbility$ DBEffect
|
||||
SVar:DBEffect:DB$ Effect | Triggers$ ComeBack | RememberObjects$ Remembered | ImprintCards$ Self | SVars$ TrigReturn,ExileSelf | ConditionPresent$ Card.Self | Duration$ Permanent | ForgetOnMoved$ Exile | SubAbility$ DBCleanup
|
||||
SVar:ComeBack:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.IsImprinted | Execute$ TrigReturn | TriggerZones$ Command | TriggerController$ TriggeredCardController | Static$ True | TriggerDescription$ Those permanents are exiled until EFFECTSOURCE leaves the battlefield
|
||||
SVar:TrigReturn:DB$ ChangeZoneAll | Origin$ Exile | Destination$ Battlefield | ChangeType$ Card.IsRemembered | SubAbility$ ExileSelf
|
||||
SVar:ExileSelf:DB$ ChangeZone | Origin$ Command | Destination$ Exile | Defined$ Self
|
||||
SVar:DBCleanup:DB$Cleanup | ClearRemembered$ True
|
||||
Oracle:When Deputy of Detention enters the battlefield, exile target nonland permanent an opponent controls and all other nonland permanents that player controls with the same name as that permanent until Deputy of Detention leaves the battlefield.
|
||||
10
forge-gui/res/cardsfolder/d/domri_chaos_bringer.txt
Normal file
10
forge-gui/res/cardsfolder/d/domri_chaos_bringer.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
Name:Domri, Chaos Bringer
|
||||
ManaCost:2 R G
|
||||
Types:Legendary Planeswalker Domri
|
||||
Loyalty:5
|
||||
A:AB$ Mana | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | Produced$ Combo R G | Amount$ 1 | AddsKeywords$ Riot | AddsKeywordsType$ Creature | SpellDescription$ Add {R} or {G}. If that mana is spent on a creature spell, it gains riot. (It enters the battlefield with your choice of a +1/+1 counter or haste.)
|
||||
A:AB$ Dig | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | ForceRevealToController$ True | DigNum$ 4 | ChangeNum$ 2 | Optional$ True | ChangeValid$ Creature | RestRandomOrder$ True | SpellDescription$ Look at the top four cards of your library. You may reveal up to two creature cards from among them and put them into your hand. Put the rest on the bottom of your library in a random order.
|
||||
A:AB$ Effect | Cost$ SubCounter<8/LOYALTY> | Planeswalker$ True | Ultimate$ True | Name$ Emblem - Domri, Chaos Bringer | Image$ emblem_domri_chaos_bringer | Triggers$ EffPhase | SVars$ EmblemTrigToken | Duration$ Permanent | SpellDescription$ You get an emblem with “At the beginning of each end step, create a 4/4 red and green Beast creature token with trample.”
|
||||
SVar:EffPhase:Mode$ Phase | Phase$ End of Turn | Execute$ EmblemTrigToken | TriggerDescription$ At the beginning of each end step, create a 4/4 red and green Beast creature token with trample.
|
||||
SVar:EmblemTrigToken:DB$ Token | TokenOwner$ You | TokenAmount$ 1 | TokenPower$ 4 | TokenToughness$ 4 | TokenColors$ Red,Green | TokenTypes$ Creature,Beast | TokenKeywords$ Trample | TokenImage$ rg 4 4 beast rna
|
||||
Oracle:+1: Add {R} or {G}. If that mana is spent on a creature spell, it gains riot. (It enters the battlefield with your choice of a +1/+1 counter or haste.)\n−3: Look at the top four cards of your library. You may reveal up to two creature cards from among them and put them into your hand. Put the rest on the bottom of your library in a random order.\n−8: You get an emblem with “At the beginning of each end step, create a 4/4 red and green Beast creature token with trample.”
|
||||
9
forge-gui/res/cardsfolder/d/domri_city_smasher.txt
Normal file
9
forge-gui/res/cardsfolder/d/domri_city_smasher.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
Name:Domri, City Smasher
|
||||
ManaCost:4 R G
|
||||
Types:Legendary Planeswalker Domri
|
||||
Loyalty:4
|
||||
A:AB$ PumpAll | Cost$ AddCounter<2/LOYALTY> | ValidCards$ Creature.YouCtrl | KW$ Haste | NumAtt$ +1 | NumDef$ +1 | Planeswalker$ True | AILogic$ Main1 | SpellDescription$ Creatures you control get +1/+1 and gain haste until end of turn.
|
||||
A:AB$ DealDamage | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 3 | SpellDescription$ CARDNAME deals 3 damage to any target.
|
||||
A:AB$ PutCounterAll | Cost$ SubCounter<8/LOYALTY> | Planeswalker$ True | ValidCards$ Creature.YouCtrl | CounterType$ P1P1 | CounterNum$ 3 | SubAbility$ DBPumpAll | SpellDescription$ Put three +1/+1 counters on each creature you control. Those creatures gain trample until end of turn.
|
||||
SVar:DBPumpAll:DB$ PumpAll | KW$ Trample | ValidCards$ Creature.YouCtrl
|
||||
Oracle:+2: Creatures you control get +1/+1 and gain haste until end of turn.\n−3: Domri, City Smasher deals 3 damage to any target.\n−8: Put three +1/+1 counters on each creature you control. Those creatures gain trample until end of turn.
|
||||
9
forge-gui/res/cardsfolder/d/domris_nodorog.txt
Normal file
9
forge-gui/res/cardsfolder/d/domris_nodorog.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
Name:Domri's Nodorog
|
||||
ManaCost:3 R G
|
||||
Types:Creature Beast
|
||||
PT:5/2
|
||||
K:Trample
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigSearch | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield, you may search your library and/or graveyard for a card named Domri, City Smasher, reveal it, and put it into your hand. If you search your library this way, shuffle it.
|
||||
SVar:TrigSearch:DB$ ChangeZone | Origin$ Library,Graveyard | Destination$ Hand | ChangeType$ Card.namedDomri; City Smasher | ChangeNum$ 1 | Optional$ True
|
||||
DeckHints:Name$Domri, City Smasher
|
||||
Oracle:Trample\nWhen Domri’s Nodorog enters the battlefield, you may search your library and/or graveyard for a card named Domri, City Smasher, reveal it, and put it into your hand. If you search your library this way, shuffle it.
|
||||
11
forge-gui/res/cardsfolder/d/dovin_architect_of_law.txt
Normal file
11
forge-gui/res/cardsfolder/d/dovin_architect_of_law.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
Name:Dovin, Architect of Law
|
||||
ManaCost:4 W U
|
||||
Types:Legendary Planeswalker Dovin
|
||||
Loyalty:5
|
||||
A:AB$ GainLife | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | Defined$ You | LifeAmount$ 2 | SubAbility$ DBDraw | SpellDescription$ You gain 2 life and draw a card.
|
||||
SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 1
|
||||
A:AB$ Tap | Cost$ SubCounter<1/LOYALTY> | ValidTgts$ Creature | TgtPrompt$ Choose target creature to tap. | Planeswalker$ True | SubAbility$ DovinPump | SpellDescription$ Tap target creature. It doesn't untap during its controller's next untap step.
|
||||
SVar:DovinPump:DB$ Pump | Defined$ Targeted | Permanent$ True | KW$ HIDDEN This card doesn't untap during your next untap step.
|
||||
A:AB$ TapAll | Cost$ SubCounter<9/LOYALTY> | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | ValidCards$ Permanent | Planeswalker$ True | SubAbility$ NoUntap | SpellDescription$ Tap all permanents target opponent controls. That player skips their next untap step.
|
||||
SVar:NoUntap:DB$ Pump | Defined$ TargetedPlayer | IsCurse$ True | KW$ Skip your next untap step. | Permanent$ True
|
||||
Oracle:+1: You gain 2 life and draw a card.\n−1: Tap target creature. It doesn’t untap during its controller’s next untap step.\n−9: Tap all permanents target opponent controls. That player skips their next untap step.
|
||||
12
forge-gui/res/cardsfolder/d/dovin_grand_arbiter.txt
Normal file
12
forge-gui/res/cardsfolder/d/dovin_grand_arbiter.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
Name:Dovin, Grand Arbiter
|
||||
ManaCost:1 W U
|
||||
Types:Legendary Planeswalker Dovin
|
||||
Loyalty:3
|
||||
A:AB$ Effect | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | Name$ Dovin, Grand Arbiter Effect | Triggers$ TrigDamage | SVars$ TrigPutCounter | RememberObjects$ Self | SpellDescription$ Until end of turn, whenever a creature you control deals combat damage to a player, put a loyalty counter on CARDNAME.
|
||||
SVar:TrigDamage:Mode$ DamageDone | ValidSource$ Creature.YouCtrl | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigPutCounter | TriggerDescription$ Until end of turn, whenever a creature you control deals combat damage to a player, put a loyalty counter on CARDNAME.
|
||||
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Remembered | CounterType$ LOYALTY | CounterNum$ 1
|
||||
A:AB$ Token | Cost$ SubCounter<1/LOYALTY> | Planeswalker$ True | TokenAmount$ 1 | TokenScript$ c_1_1_a_thopter_flying | TokenOwner$ You | LegacyImage$ c 1 1 a thopter flying rna | SubAbility$ DBGainLife | SpellDescription$ Create a 1/1 colorless Thopter artifact creature token with flying.
|
||||
SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 1 | SpellDescription$ You gain 1 life.
|
||||
DeckHas:Ability$Token
|
||||
A:AB$ Dig | Cost$ SubCounter<7/LOYALTY> | Planeswalker$ True | DigNum$ 10 | ChangeNum$ 3 | DestinationZone$ Hand | DestinationZone2$ Library | LibraryPosition$ -1 | RestRandomOrder$ True | SpellDescription$ Look at the top ten cards of your library. Put three of them into your hand and the rest on the bottom of your library in a random order.
|
||||
Oracle:[+1]: Until end of turn, whenever a creature you control deals combat damage to a player, put a loyalty counter on Dovin, Grand Arbiter.\n[-1]: Create a 1/1 colorless Thopter artifact creature token with flying. You gain 1 life.\n[-7]: Look at the top ten cards of your library. Put three of them into your hand and the rest on the bottom of your library in a random order.
|
||||
@@ -2,8 +2,10 @@ Name:Dovin's Acuity
|
||||
ManaCost:1 W U
|
||||
Types:Enchantment
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigGainLife | TriggerDescription$ When CARDNAME enters the battlefield, you gain 2 life and draw a card.
|
||||
T:Mode$ SpellCast | ValidCard$ Instant | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigChangeZone | OptionalDecider$ You | TriggerDescription$ Whenever you cast an instant spell during your main phase, you may return CARDNAME to its owner's hand.
|
||||
SVar:TrigGainLife:DB$GainLife | Defined$ You | LifeAmount$ 2 | SubAbility$ DBDraw
|
||||
SVar:DBDraw:DB$Draw | Defined$ You | NumCards$ 1
|
||||
SVar:X:Count$IfMainPhase
|
||||
T:Mode$ SpellCast | ValidCard$ Instant | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | PlayerTurn$ True | CheckSVar$ X | SVarCompare$ GE1 | Execute$ TrigChangeZone | OptionalDecider$ You | TriggerDescription$ Whenever you cast an instant spell during your main phase, you may return CARDNAME to its owner's hand.
|
||||
SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Battlefield | Destination$ Hand | Defined$ Self
|
||||
SVar:X:Count$IfMainPhase.1.0
|
||||
DeckHas:Ability$LifeGain
|
||||
Oracle:When Dovin's Acuity enters the battlefield, you gain 2 life and draw a card.\nWhenever you cast an instant spell during your main phase, you may return Dovin's Acuity to its owner's hand.
|
||||
@@ -2,7 +2,7 @@ Name:Dovin's Automaton
|
||||
ManaCost:4
|
||||
Types:Artifact Creature Homunculus
|
||||
PT:3/3
|
||||
S:Mode$ Continuous | Affected$ Card.Self | AddPower$ 2 | AddToughness$ 2 | AddKeyword$ Vigilance | IsPresent$ Planeswalker.Dovin+YouCtrl | Description$ As long as you control a planeswalker planeswalker, CARDNAME gets +2/+2 and has vigilance. (Attacking doesn't cause it to tap.)
|
||||
S:Mode$ Continuous | Affected$ Card.Self | AddPower$ 2 | AddToughness$ 2 | AddKeyword$ Vigilance | IsPresent$ Planeswalker.Dovin+YouCtrl | Description$ As long as you control a Dovin planeswalker, CARDNAME gets +2/+2 and has vigilance. (Attacking doesn't cause it to tap.)
|
||||
SVar:BuffedBy:Dovin
|
||||
DeckNeeds:Type$Dovin
|
||||
Oracle:As long as you control a Dovin planeswalker, Dovin's Automaton gets +2/+2 and has vigilance. (Attacking doesn't cause it to tap.)
|
||||
7
forge-gui/res/cardsfolder/d/dovins_dismissal.txt
Normal file
7
forge-gui/res/cardsfolder/d/dovins_dismissal.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Name:Dovin's Dismissal
|
||||
ManaCost:2 W U
|
||||
Types:Instant
|
||||
A:SP$ ChangeZone | Cost$ 2 W U | ValidTgts$ Creature.tapped | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select target tapped creature | Origin$ Battlefield | Destination$ Library | LibraryPosition$ 0 | SubAbility$ DBSearch | SpellDescription$ Put up to one target tapped creature on top of its owner's library. You may search your library and/or graveyard for a card named Dovin, Architect of Law, reveal it, and put it into your hand. If you search your library this way, shuffle it.
|
||||
SVar:DBSearch:DB$ ChangeZone | Origin$ Library,Graveyard | Destination$ Hand | ChangeType$ Card.namedDovin; Architect of Law | ChangeNum$ 1 | Optional$ True
|
||||
DeckNeeds:Name$Dovin, Architect of Law
|
||||
Oracle:Put up to one target tapped creature on top of its owner’s library. You may search your library and/or graveyard for a card named Dovin, Architect of Law, reveal it, and put it into your hand. If you search your library this way, shuffle it.
|
||||
@@ -2,8 +2,8 @@ Name:Dross Scorpion
|
||||
ManaCost:4
|
||||
Types:Artifact Creature Scorpion
|
||||
PT:3/1
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Artifact.Creature+Other | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigUntap | TriggerDescription$ Whenever CARDNAME or another artifact creature dies,
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | OptionalDecider$ You | Execute$ TrigUntap | TriggerDescription$ you may untap target artifact.
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Artifact.Creature+Other | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigUntap | TriggerDescription$ Whenever CARDNAME or another artifact creature dies, you may untap target artifact.
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | OptionalDecider$ You | Execute$ TrigUntap | Secondary$ True | TriggerDescription$ Whenever CARDNAME or another artifact creature dies, you may untap target artifact.
|
||||
SVar:TrigUntap:DB$Untap | ValidTgts$ Artifact | TgtPrompt$ Choose target artifact.
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/dross_scorpion.jpg
|
||||
Oracle:Whenever Dross Scorpion or another artifact creature dies, you may untap target artifact.
|
||||
|
||||
7
forge-gui/res/cardsfolder/e/elite_arrester.txt
Normal file
7
forge-gui/res/cardsfolder/e/elite_arrester.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Name:Elite Arrester
|
||||
ManaCost:W
|
||||
Types:Creature Human Soldier
|
||||
PT:0/3
|
||||
A:AB$ Tap | Cost$ 1 U T | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Tap target creature.
|
||||
SVar:NonCombatPriority:5
|
||||
Oracle:{1}{U}, {T}: Tap target creature.
|
||||
@@ -5,8 +5,8 @@ S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddPower$ 1 | AddToughness$ 1
|
||||
S:Mode$ Continuous | Affected$ Creature.OppCtrl | AddPower$ -1 | AddToughness$ -1 | Description$ Creatures your opponents control get -1/-1.
|
||||
SVar:PlayMain1:TRUE
|
||||
SVar:RemRandomDeck:True
|
||||
A:AB$ ChangeZone | Cost$ 4 W B | Origin$ Graveyard | Destination$ Exile | ValidTgts$ Card.OppOwn | SubAbility$ DBToken | SpellDescription$ Exile target card from an opponent's graveyard. If it was a creature card, you create a 1/1 white and black Spirit creature token with flying.
|
||||
SVar:DBToken:DB$ Token | ConditionDefined$ Targeted | ConditionPresent$ Creature | ConditionCompare$ EQ1 | TokenAmount$ 1 | TokenScript$ wb_1_1_spirit_flying | TokenOwner$ You | SubAbility$ DBCleanup
|
||||
A:AB$ ChangeZone | Cost$ 2 W B | Origin$ Graveyard | Destination$ Exile | ValidTgts$ Card.OppOwn | SubAbility$ DBToken | SpellDescription$ Exile target card from an opponent's graveyard. If it was a creature card, you create a 1/1 white and black Spirit creature token with flying.
|
||||
SVar:DBToken:DB$ Token | ConditionDefined$ Targeted | ConditionPresent$ Creature | ConditionCompare$ EQ1 | TokenAmount$ 1 | TokenScript$ wb_1_1_spirit_flying | TokenOwner$ You | SubAbility$ DBCleanup | LegacyImage$ wb 1 1 spirit flying rna
|
||||
SVar:DBCleanup:DB$Cleanup | ClearRemembered$ True
|
||||
DeckHas:Ability$Token
|
||||
Oracle:Creatures you control get +1/+1.\nCreatures your opponents control get -1/-1.\n{2}{W}{B}: Exile target card from an opponent's graveyard. If it was a creature card, you create a 1/1 white and black Spirit creature token with flying.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user