Merge branch 'master' of https://github.com/Card-Forge/forge into adventure

This commit is contained in:
Grimm
2022-04-19 01:42:37 +02:00
111 changed files with 27813 additions and 12231 deletions

View File

@@ -1,8 +1,8 @@
# Forge
[Official GitLab repo](https://git.cardforge.org/core-developers/forge).
[Official repo](https://github.com/Card-Forge/forge.git).
Dev instructions here: [Getting Started](https://git.cardforge.org/core-developers/forge/-/wikis/(SM-autoconverted)-how-to-get-started-developing-forge) (Somewhat outdated)
Dev instructions here: [Getting Started](https://github.com/Card-Forge/forge/wiki) (Somewhat outdated)
Discord channel [here](https://discord.gg/fWfNgCUNRq)
@@ -13,14 +13,14 @@ Discord channel [here](https://discord.gg/fWfNgCUNRq)
- Git
- Git client (optional)
- Maven
- Gitlab account
- GitHub account
- Libgdx (optional: familiarity with this library is helpful for mobile platform development)
- Android SDK (optional: for Android releases)
- RoboVM (optional: for iOS releases) (TBD: Current status of support by libgdx)
## Project Quick Setup
- Log in to gitlab with your user account and fork the project.
- Login into GitHub with your user account and fork the project.
- Clone your forked project to your local machine
@@ -32,13 +32,13 @@ Eclipse includes Maven integration so a separate install is not necessary. For
### Project Setup
- Follow the instructions for cloning from Gitlab. You'll need a Gitlab account setup and an SSH key defined.
- Follow the instructions for cloning from GitHub. You'll need to setup an account and your SSH key.
If you are on a Windows machine you can use Putty with TortoiseGit for SSH keys. Run puttygen.exe to generate the key -- save the private key and export
the OpenSSH public key. If you just leave the dialog open, you can copy and paste the key from it to your Gitlab profile under
"SSH keys". Run pageant.exe and add the private key generated earlier. TortoiseGit will use this for accessing Gitlab.
the OpenSSH public key. If you just leave the dialog open, you can copy and paste the key from it to your GitHub profile under
"SSH keys". Run pageant.exe and add the private key generated earlier. TortoiseGit will use this for accessing GitHub.
- Fork the Forge git repo to your Gitlab account.
- Fork the Forge git repo to your GitHub account.
- Clone your forked repo to your local machine.

View File

@@ -1,4 +0,0 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

View File

@@ -1,4 +0,0 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

View File

@@ -22,6 +22,8 @@ import java.util.List;
import java.util.Map;
import com.google.common.collect.*;
import forge.game.card.*;
import forge.util.Aggregates;
import org.apache.commons.lang3.StringUtils;
import forge.card.MagicColor;
@@ -30,17 +32,10 @@ import forge.card.mana.ManaCostParser;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardPlayOption;
import forge.game.card.CardPlayOption.PayManaCost;
import forge.game.card.CounterType;
import forge.game.cost.Cost;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.keyword.KeywordsChange;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerController;
@@ -567,7 +562,11 @@ public final class GameActionUtil {
if (tr != null) {
String n = o.split(":")[1];
if (host.wasCast() && n.equals("X")) {
n = Integer.toString(pc.announceRequirements(sa, "X for Casualty"));
CardCollectionView creatures = CardLists.filter(CardLists.filterControlledBy(game.getCardsIn
(ZoneType.Battlefield), activator), CardPredicates.Presets.CREATURES);
int max = Aggregates.max(creatures, CardPredicates.Accessors.fnGetNetPower);
int min = Aggregates.min(creatures, CardPredicates.Accessors.fnGetNetPower);
n = Integer.toString(pc.chooseNumber(sa, "Choose X for Casualty", min, max));
}
final String casualtyCost = "Sac<1/Creature.powerGE" + n + "/creature with power " + n +
" or greater>";
@@ -584,6 +583,8 @@ public final class GameActionUtil {
result.getPayCosts().add(cost);
tr.getOverridingAbility().setSVar("Casualty", n);
reset = true;
} else {
tr.getOverridingAbility().setSVar("Casualty", "0");
}
}
} else if (o.equals("Conspire")) {
@@ -721,7 +722,7 @@ public final class GameActionUtil {
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, null, null);
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
return eff;
}
@@ -820,24 +821,8 @@ public final class GameActionUtil {
}
public static void checkStaticAfterPaying(Card c) {
Table<Long, Long, KeywordsChange> oldKW = TreeBasedTable.create((TreeBasedTable<Long, Long, KeywordsChange>) c.getChangedCardKeywords());
// this should be the last time checkStaticAbilities is called before SpellCast triggers to
// - setup Cascade dependent on high enough X (Imoti)
// - remove Replicate if Djinn Illuminatus gets sacrificed as payment
// because this will remove the payment SVars for Replicate we need to restore them
c.getGame().getAction().checkStaticAbilities(false);
Table<Long, Long, KeywordsChange> updatedKW = c.getChangedCardKeywords();
for (Table.Cell<Long, Long, KeywordsChange> entry : oldKW.cellSet()) {
for (KeywordInterface ki : entry.getValue().getKeywords()) {
// check if this keyword existed previously
if ((ki.getOriginal().startsWith("Replicate") || ki.getOriginal().startsWith("Conspire")
|| ki.getOriginal().startsWith("Casualty"))
&& updatedKW.get(entry.getRowKey(), entry.getColumnKey()) != null) {
updatedKW.put(entry.getRowKey(), entry.getColumnKey(), oldKW.get(entry.getRowKey(), entry.getColumnKey()));
}
}
}
c.updateKeywords();
c.getGame().getTriggerHandler().resetActiveTriggers();

View File

@@ -252,6 +252,11 @@ public class StaticEffect {
affectedCard.removeColor(getTimestamp(), ability.getId());
}
// remove changed name
if (hasParam("SetName") || hasParam("AddNames")) {
affectedCard.removeChangedName(timestamp, ability.getId());
}
// remove may look at
if (hasParam("MayLookAt")) {
affectedCard.removeMayLookAt(getTimestamp());

View File

@@ -1822,6 +1822,12 @@ public class AbilityUtils {
return root == null ? 0 : root.getTotalManaSpent();
}
// Count$ManaColorsPaid
if (sq[0].equals("ManaColorsPaid")) {
final SpellAbility root = sa.getRootAbility();
return doXMath(root == null ? 0 : root.getPayingColors().countColors(), expr, c, ctb);
}
// Count$Adamant.<Color>.<True>.<False>
if (sq[0].startsWith("Adamant")) {
final String payingMana = StringUtils.join(sa.getRootAbility().getPayingMana());
@@ -1981,9 +1987,9 @@ public class AbilityUtils {
final int lifeTotal = calculateAmount(c, sq[1], ctb);
int number = 0;
for (final Player opp : player.getOpponents()) {
if (opp.getLife() == lifeTotal) {
number++;
}
if (opp.getLife() == lifeTotal) {
number++;
}
}
return doXMath(number, expr, c, ctb);
}
@@ -2828,23 +2834,6 @@ public class AbilityUtils {
return doXMath(ColorSet.fromMask(n).countColors(), expr, c, ctb);
}
if (sq[0].startsWith("CreatureType")) {
String[] sqparts = l[0].split(" ", 2);
final String[] rest = sqparts[1].split(",");
final CardCollectionView cardsInZones = sqparts[0].length() > 12
? game.getCardsIn(ZoneType.listValueOf(sqparts[0].substring(12)))
: game.getCardsIn(ZoneType.Battlefield);
CardCollection cards = CardLists.getValidCards(cardsInZones, rest, player, c, ctb);
final Set<String> creatTypes = Sets.newHashSet();
for (Card card : cards) {
Iterables.addAll(creatTypes, card.getType().getCreatureTypes());
}
return doXMath(creatTypes.size(), expr, c, ctb);
}
// TODO move below to handlePaid
if (sq[0].startsWith("SumPower")) {
final String[] restrictions = l[0].split("_");

View File

@@ -165,18 +165,23 @@ public abstract class SpellAbilityEffect {
if ("}".equals(t)) { isPlainText = true; continue; }
if (!isPlainText) {
final List<? extends GameObject> objs;
if (t.startsWith("p:")) {
objs = AbilityUtils.getDefinedPlayers(sa.getHostCard(), t.substring(2), sa);
} else if (t.startsWith("s:")) {
objs = AbilityUtils.getDefinedSpellAbilities(sa.getHostCard(), t.substring(2), sa);
} else if (t.startsWith("c:")) {
objs = AbilityUtils.getDefinedCards(sa.getHostCard(), t.substring(2), sa);
if (t.startsWith("n:")) { // {n:<SVar> <noun(opt.)>}
String parts[] = t.substring(2).split(" ", 2);
int n = AbilityUtils.calculateAmount(sa.getHostCard(), parts[0], sa);
sb.append(parts.length == 1 ? Lang.getNumeral(n) : Lang.nounWithNumeral(n, parts[1]));
} else {
objs = AbilityUtils.getDefinedObjects(sa.getHostCard(), t, sa);
final List<? extends GameObject> objs;
if (t.startsWith("p:")) {
objs = AbilityUtils.getDefinedPlayers(sa.getHostCard(), t.substring(2), sa);
} else if (t.startsWith("s:")) {
objs = AbilityUtils.getDefinedSpellAbilities(sa.getHostCard(), t.substring(2), sa);
} else if (t.startsWith("c:")) {
objs = AbilityUtils.getDefinedCards(sa.getHostCard(), t.substring(2), sa);
} else {
objs = AbilityUtils.getDefinedObjects(sa.getHostCard(), t, sa);
}
sb.append(StringUtils.join(objs, ", "));
}
sb.append(StringUtils.join(objs, ", "));
} else {
sb.append(t);
}

View File

@@ -172,7 +172,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
final String cardTag = type.contains("card") ? "" : " card";
sb.append(Lang.nounWithNumeralExceptOne(num, type + cardTag)).append(", ");
if (!sa.hasParam("NoReveal") && !destination.equals("Battlefield")) {
if (!sa.hasParam("NoReveal") && ZoneType.smartValueOf(destination).isHidden()) {
if (choosers.size() == 1) {
sb.append(num > 1 ? "reveals them, " : "reveals it, ");
} else {
@@ -359,6 +359,8 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (destination.equals(ZoneType.Hand)) {
if (ZoneType.Graveyard.equals(origin)) {
sb.append("Return").append(targetname).append(fromGraveyard).append(" to");
} else if (ZoneType.Battlefield.equals(origin)) {
sb.append("Return").append(targetname).append(" to");
} else {
sb.append("Put").append(targetname).append(" in");
}

View File

@@ -124,7 +124,7 @@ public class DamageDealEffect extends DamageBaseEffect {
break;
}
}
stringBuilder.append(statement);
stringBuilder.append(" ").append(statement);
}
return stringBuilder.toString();
}

View File

@@ -269,6 +269,11 @@ public class ManaEffect extends SpellAbilityEffect {
String mana = !sa.hasParam("Amount") || StringUtils.isNumeric(sa.getParam("Amount"))
? GameActionUtil.generatedMana(sa) : "mana";
sb.append("Add ").append(toManaString(mana)).append(".");
if (sa.hasParam("RestrictValid")) {
String desc = sa.getDescription();
int i = desc.indexOf("Spend this");
sb.append(" ").append(desc, i, desc.indexOf(".", i) + 1);
}
return sb.toString();
}
}

View File

@@ -120,7 +120,6 @@ public class PumpAllEffect extends SpellAbilityEffect {
@Override
public void resolve(final SpellAbility sa) {
final PlayerCollection tgtPlayers = getTargetPlayers(sa);
final List<ZoneType> affectedZones = Lists.newArrayList();
final Game game = sa.getActivatingPlayer().getGame();
@@ -134,6 +133,7 @@ public class PumpAllEffect extends SpellAbilityEffect {
if (!sa.usesTargeting() && !sa.hasParam("Defined")) {
list = game.getCardsIn(affectedZones);
} else {
final PlayerCollection tgtPlayers = getTargetPlayers(sa);
list = tgtPlayers.getCardsIn(affectedZones);
}

View File

@@ -50,6 +50,7 @@ import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.replacement.ReplaceMoved;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler;
import forge.game.replacement.ReplacementResult;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.*;
@@ -60,6 +61,7 @@ import forge.game.staticability.StaticAbilityCantSacrifice;
import forge.game.staticability.StaticAbilityCantTarget;
import forge.game.staticability.StaticAbilityCantTransform;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
import forge.game.trigger.TriggerType;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
@@ -139,13 +141,22 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
private final Table<Long, Long, CardChangedName> changedCardNames = TreeBasedTable.create(); // Layer 3
private final Table<Long, Long, KeywordsChange> changedCardKeywordsByText = TreeBasedTable.create(); // Layer 3 by Text Change
protected KeywordsChange changedCardKeywordsByWord = new KeywordsChange(ImmutableList.<String>of(), null, false); // Layer 3 by Word Change
protected KeywordsChange changedCardKeywordsByWord = new KeywordsChange(ImmutableList.<KeywordInterface>of(), ImmutableList.<KeywordInterface>of(), false); // Layer 3 by Word Change
private final Table<Long, Long, KeywordsChange> changedCardKeywords = TreeBasedTable.create(); // Layer 6
// stores the keywords created by static abilities
private final Table<Long, String, KeywordInterface> storedKeywords = TreeBasedTable.create();
// x=timestamp y=StaticAbility id
private final Table<Long, Long, CardTraitChanges> changedCardTraitsByText = TreeBasedTable.create(); // Layer 3 by Text Change
private final Table<Long, Long, CardTraitChanges> changedCardTraits = TreeBasedTable.create(); // Layer 6
// stores the card traits created by static abilities
private final Table<StaticAbility, String, SpellAbility> storedSpellAbilility = TreeBasedTable.create();
private final Table<StaticAbility, String, Trigger> storedTrigger = TreeBasedTable.create();
private final Table<StaticAbility, String, ReplacementEffect> storedReplacementEffect = TreeBasedTable.create();
private final Table<StaticAbility, String, StaticAbility> storedStaticAbility = TreeBasedTable.create();
// x=timestamp y=StaticAbility id
private final Table<Long, Long, CardColor> changedCardColorsByText = TreeBasedTable.create(); // Layer 3 by Text Change
private final Table<Long, Long, CardColor> changedCardColorsCharacterDefining = TreeBasedTable.create(); // Layer 5 CDA
@@ -179,7 +190,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
private List<Pair<Card, Integer>> receivedDamageFromThisTurn = Lists.newArrayList();
private Map<Player, Integer> receivedDamageFromPlayerThisTurn = Maps.newHashMap();
private final Map<Card, Integer> assignedDamageMap = Maps.newTreeMap();
private boolean isCommander = false;
@@ -3680,7 +3691,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public Iterable<KeywordsChange> getChangedCardKeywordsList() {
return Iterables.concat(
changedCardKeywordsByText.values(), // Layer 3
ImmutableList.of(new KeywordsChange(ImmutableList.<String>of(), null, this.hasRemoveIntrinsic())), // Layer 4
ImmutableList.of(new KeywordsChange(ImmutableList.<KeywordInterface>of(), ImmutableList.<KeywordInterface>of(), this.hasRemoveIntrinsic())), // Layer 4
changedCardKeywords.values() // Layer 6
);
}
@@ -4260,6 +4271,45 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
updateAbilityTextForView();
}
public final SpellAbility getSpellAbilityForStaticAbility(final String str, final StaticAbility stAb) {
SpellAbility result = storedSpellAbilility.get(stAb, str);
if (result == null) {
result = AbilityFactory.getAbility(str, this, stAb);
result.setIntrinsic(false);
result.setGrantorStatic(stAb);
storedSpellAbilility.put(stAb, str, result);
}
return result;
}
public final Trigger getTriggerForStaticAbility(final String str, final StaticAbility stAb) {
Trigger result = storedTrigger.get(stAb, str);
if (result == null) {
result = TriggerHandler.parseTrigger(str, this, false, stAb);
storedTrigger.put(stAb, str, result);
}
return result;
}
public final ReplacementEffect getReplacementEffectForStaticAbility(final String str, final StaticAbility stAb) {
ReplacementEffect result = storedReplacementEffect.get(stAb, str);
if (result == null) {
result = ReplacementHandler.parseReplacement(str, this, false, stAb);
storedReplacementEffect.put(stAb, str, result);
}
return result;
}
public final StaticAbility getStaticAbilityForStaticAbility(final String str, final StaticAbility stAb) {
StaticAbility result = storedStaticAbility.get(stAb, str);
if (result == null) {
result = StaticAbility.create(str, this, stAb.getCardState(), false);
storedStaticAbility.put(stAb, str, result);
}
return result;
}
public final void addChangedCardTraits(Collection<SpellAbility> spells, Collection<SpellAbility> removedAbilities,
Collection<Trigger> trigger, Collection<ReplacementEffect> replacements, Collection<StaticAbility> statics,
boolean removeAll, boolean removeNonMana, long timestamp, long staticId) {
@@ -4367,8 +4417,14 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
public final void addChangedCardKeywords(final List<String> keywords, final List<String> removeKeywords,
final boolean removeAllKeywords, final long timestamp, final long staticId, final boolean updateView) {
final KeywordsChange newCks = new KeywordsChange(keywords, removeKeywords, removeAllKeywords);
newCks.addKeywordsToCard(this);
List<KeywordInterface> kws = Lists.newArrayList();
if (keywords != null) {
for(String kw : keywords) {
kws.add(getKeywordForStaticAbility(kw, staticId));
}
}
final KeywordsChange newCks = new KeywordsChange(kws, removeKeywords, removeAllKeywords);
changedCardKeywords.put(timestamp, staticId, newCks);
if (updateView) {
@@ -4378,10 +4434,24 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
}
public final KeywordInterface getKeywordForStaticAbility(String kw, final long staticId) {
KeywordInterface result;
if (staticId < 1 || !storedKeywords.contains(staticId, kw)) {
result = Keyword.getInstance(kw);
result.createTraits(this, false);
if (staticId > 0) {
storedKeywords.put(staticId, kw, result);
}
} else {
result = storedKeywords.get(staticId, kw);
}
return result;
}
public final void addChangedCardKeywordsByText(final List<KeywordInterface> keywords, final long timestamp, final long staticId, final boolean updateView) {
// keywords should already created for Card, so no addKeywordsToCard
// this one is done for Volrath's Shapeshifter which replaces all the card text
changedCardKeywordsByText.put(timestamp, staticId, new KeywordsChange(keywords, null, true));
changedCardKeywordsByText.put(timestamp, staticId, new KeywordsChange(keywords, ImmutableList.<KeywordInterface>of(), true));
if (updateView) {
updateKeywords();
@@ -4394,7 +4464,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
final long timestamp, final long staticId, final boolean updateView) {
final KeywordsChange newCks = new KeywordsChange(keywords, removeKeywords, removeAllKeywords);
newCks.addKeywordsToCard(this);
changedCardKeywords.put(timestamp, staticId, newCks);
if (updateView) {

View File

@@ -202,12 +202,12 @@ public class CardFactory {
copySA = getCopiedTriggeredAbility((WrappedAbility)targetSA, c, controller);
} else {
copySA = targetSA.copy(c, controller, false);
c.setCastSA(copySA);
}
copySA.setCopied(true);
// 707.10b
copySA.setOriginalAbility(targetSA);
c.setCastSA(copySA);
if (targetSA.usesTargeting()) {
// do for SubAbilities too?

View File

@@ -1417,8 +1417,8 @@ public class CardProperty {
if (card.getCMC() != source.getChosenNumber()) {
return false;
}
} else if (property.startsWith("power") || property.startsWith("toughness")
|| property.startsWith("cmc") || property.startsWith("totalPT")) {
} else if (property.startsWith("power") || property.startsWith("toughness") || property.startsWith("cmc")
|| property.startsWith("totalPT") || property.startsWith("numColors")) {
int x;
int y = 0;
String rhs = "";
@@ -1435,6 +1435,9 @@ public class CardProperty {
} else if (property.startsWith("totalPT")) {
rhs = property.substring(10);
y = card.getNetPower() + card.getNetToughness();
} else if (property.startsWith("numColors")) {
rhs = property.substring(11);
y = card.getColor().countColors();
}
x = AbilityUtils.calculateAmount(source, rhs, spellAbility);

View File

@@ -23,7 +23,6 @@ import java.util.List;
import com.google.common.collect.Lists;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility;
@@ -51,11 +50,11 @@ public class KeywordsChange implements Cloneable {
* @param removeAll whether to remove all keywords.
*/
public KeywordsChange(
final Iterable<String> keywordList,
final Iterable<KeywordInterface> keywordList,
final Collection<String> removeKeywordList,
final boolean removeAll) {
if (keywordList != null) {
this.keywords.addAll(keywordList);
this.keywords.insertAll(keywordList);
}
if (removeKeywordList != null) {
@@ -64,7 +63,6 @@ public class KeywordsChange implements Cloneable {
this.removeAllKeywords = removeAll;
}
public KeywordsChange(
final Collection<KeywordInterface> keywordList,
final Collection<KeywordInterface> removeKeywordInterfaces,
@@ -122,18 +120,6 @@ public class KeywordsChange implements Cloneable {
&& this.removeKeywords.isEmpty();
}
public final void addKeywordsToCard(final Card host) {
for (KeywordInterface inst : keywords.getValues()) {
inst.createTraits(host, false, true);
}
}
public final void addKeywordsToPlayer(final Player player) {
for (KeywordInterface inst : keywords.getValues()) {
inst.createTraits(player, true);
}
}
public void setHostCard(final Card host) {
keywords.setHostCard(host);
for (KeywordInterface k : removeKeywordInterfaces) {

View File

@@ -441,9 +441,6 @@ public class PhaseHandler implements java.io.Serializable {
// Rule 514.3a - state-based actions
game.getAction().checkStateEffects(true);
// done this after check state effects, so it only has effect next check
game.getCleanup().executeUntil(getNextTurn());
break;
default:
@@ -529,6 +526,10 @@ public class PhaseHandler implements java.io.Serializable {
// set previous player
playerPreviousTurn = this.getPlayerTurn();
setPlayerTurn(handleNextTurn());
// done this after check state effects, so it only has effect next check
game.getCleanup().executeUntil(playerTurn);
// "Trigger" for begin turn to get around a phase skipping
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Player, playerTurn);

View File

@@ -196,6 +196,8 @@ public class Player extends GameEntity implements Comparable<Player> {
private CardCollection inboundTokens = new CardCollection();
private KeywordCollection keywords = new KeywordCollection();
// stores the keywords created by static abilities
private final Table<Long, String, KeywordInterface> storedKeywords = TreeBasedTable.create();
private Map<Card, DetachedCardEffect> staticAbilities = Maps.newHashMap();
@@ -991,9 +993,13 @@ public class Player extends GameEntity implements Comparable<Player> {
}
// ================ POISON Merged =================================
public final void addChangedKeywords(final List<String> addKeywords, final List<String> removeKeywords, final Long timestamp, final long staticId) {
// if the key already exists - merge entries
KeywordsChange cks = new KeywordsChange(addKeywords, removeKeywords, false);
cks.addKeywordsToPlayer(this);
List<KeywordInterface> kws = Lists.newArrayList();
if (addKeywords != null) {
for(String kw : addKeywords) {
kws.add(getKeywordForStaticAbility(kw, staticId));
}
}
KeywordsChange cks = new KeywordsChange(kws, removeKeywords, false);
if (!cks.getAbilities().isEmpty() || !cks.getTriggers().isEmpty() || !cks.getReplacements().isEmpty() || !cks.getStaticAbilities().isEmpty()) {
getKeywordCard().addChangedCardTraits(
cks.getAbilities(), null, cks.getTriggers(), cks.getReplacements(), cks.getStaticAbilities(), false, false, timestamp, staticId);
@@ -1003,6 +1009,17 @@ public class Player extends GameEntity implements Comparable<Player> {
game.fireEvent(new GameEventPlayerStatsChanged(this, true));
}
public final KeywordInterface getKeywordForStaticAbility(String kw, final long staticId) {
KeywordInterface result;
if (staticId < 1 || !storedKeywords.contains(staticId, kw)) {
result = Keyword.getInstance(kw);
result.createTraits(this, false);
} else {
result = storedKeywords.get(staticId, kw);
}
return result;
}
public final KeywordsChange removeChangedKeywords(final Long timestamp, final long staticId) {
KeywordsChange change = changedKeywords.remove(timestamp, staticId);
if (change != null) {
@@ -1179,7 +1196,7 @@ public class Player extends GameEntity implements Comparable<Player> {
public final boolean canDraw() {
return canDrawAmount(1);
}
public final boolean canDrawAmount(int amount) {
return StaticAbilityCantDraw.canDrawThisAmount(this, amount);
}

View File

@@ -142,7 +142,8 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
layers.add(StaticAbilityLayer.CONTROL);
}
if (hasParam("ChangeColorWordsTo") || hasParam("GainTextOf") || hasParam("AddNames")) {
if (hasParam("ChangeColorWordsTo") || hasParam("GainTextOf") || hasParam("AddNames") ||
hasParam("SetName")) {
layers.add(StaticAbilityLayer.TEXT);
}

View File

@@ -56,11 +56,9 @@ import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler;
import forge.game.spellability.AbilityStatic;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
import forge.game.zone.ZoneType;
import forge.util.TextUtil;
@@ -675,6 +673,10 @@ public final class StaticAbilityContinuous {
if (stAb.hasParam("AddNames")) { // currently only for AllNonLegendaryCreatureNames
affectedCard.addChangedName(null, true, se.getTimestamp(), stAb.getId());
}
if (stAb.hasParam("SetName")) {
affectedCard.addChangedName(stAb.getParam("SetName"), false,
se.getTimestamp(), stAb.getId());
}
// Change color words
if (params.containsKey("ChangeColorWordsTo")) {
@@ -724,8 +726,6 @@ public final class StaticAbilityContinuous {
}
// add keywords
// TODO regular keywords currently don't try to use keyword multiplier
// (Although nothing uses it at this time)
if (addKeywords != null || removeKeywords != null || removeAllAbilities) {
List<String> newKeywords = null;
if (addKeywords != null) {
@@ -806,10 +806,7 @@ public final class StaticAbilityContinuous {
abilty = TextUtil.fastReplace(abilty, "ConvertedManaCost", costcmc);
}
if (abilty.startsWith("AB") || abilty.startsWith("ST")) { // grant the ability
final SpellAbility sa = AbilityFactory.getAbility(abilty, affectedCard, stAb);
sa.setIntrinsic(false);
sa.setGrantorStatic(stAb);
addedAbilities.add(sa);
addedAbilities.add(affectedCard.getSpellAbilityForStaticAbility(abilty, stAb));
}
}
}
@@ -855,15 +852,14 @@ public final class StaticAbilityContinuous {
// add Replacement effects
if (addReplacements != null) {
for (String rep : addReplacements) {
final ReplacementEffect actualRep = ReplacementHandler.parseReplacement(rep, affectedCard, false, stAb);
addedReplacementEffects.add(actualRep);
addedReplacementEffects.add(affectedCard.getReplacementEffectForStaticAbility(rep, stAb));
}
}
// add triggers
if (addTriggers != null) {
for (final String trigger : addTriggers) {
final Trigger actualTrigger = TriggerHandler.parseTrigger(trigger, affectedCard, false, stAb);
final Trigger actualTrigger = affectedCard.getTriggerForStaticAbility(trigger, stAb);
// if the trigger has Execute param, which most trigger gained by Static Abilties should have
// turn them into SpellAbility object before adding to card
// with that the TargetedCard does not need the Svars added to them anymore
@@ -888,7 +884,7 @@ public final class StaticAbilityContinuous {
s = TextUtil.fastReplace(s, "ConvertedManaCost", costcmc);
}
addedStaticAbility.add(StaticAbility.create(s, affectedCard, stAb.getCardState(), false));
addedStaticAbility.add(affectedCard.getStaticAbilityForStaticAbility(s, stAb));
}
}
@@ -937,7 +933,7 @@ public final class StaticAbilityContinuous {
if (controllerMayPlay && (mayPlayLimit == null || stAb.getMayPlayTurn() < mayPlayLimit)) {
String mayPlayAltCost = mayPlayAltManaCost;
boolean additional = mayPlayAltCost.contains("RegularCost");
boolean additional = mayPlayAltCost != null && mayPlayAltCost.contains("RegularCost");
if (mayPlayAltCost != null) {
if (mayPlayAltCost.contains("ConvertedManaCost")) {

View File

@@ -2,8 +2,11 @@ package forge;
import java.awt.Desktop;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
@@ -334,4 +337,18 @@ public class GuiDesktop implements IGuiBase {
public void preventSystemSleep(boolean preventSleep) {
OperatingSystem.preventSystemSleep(preventSleep);
}
private static float initializeScreenScale() {
GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
AffineTransform at = gc.getDefaultTransform();
double scaleX = at.getScaleX();
double scaleY = at.getScaleY();
return (float) Math.min(scaleX, scaleY);
}
static float screenScale = initializeScreenScale();
@Override
public float getScreenScale() {
return screenScale;
}
}

View File

@@ -45,6 +45,7 @@ import forge.game.card.Card;
import forge.game.card.CardView;
import forge.game.player.PlayerView;
import forge.gui.FThreads;
import forge.gui.GuiBase;
import forge.item.IPaperCard;
import forge.item.InventoryItem;
import forge.item.PaperCard;
@@ -259,7 +260,8 @@ public class ImageCache {
// as otherwise it's problematic to update if the real image gets fetched.
if (original == null || useArtCrop) {
if ((ipc != null || cardView != null) && !originalKey.equals(ImageKeys.getTokenKey(ImageKeys.HIDDEN_CARD))) {
int width = 488, height = 680;
float screenScale = GuiBase.getInterface().getScreenScale();
int width = Math.round(488 * screenScale), height = Math.round(680 * screenScale);
BufferedImage art = original;
CardView card = ipc != null ? Card.getCardForUi(ipc).getView() : cardView;
String legalString = null;

View File

@@ -8,6 +8,7 @@ import javax.swing.JPanel;
import com.google.common.base.Predicates;
import forge.gui.GuiBase;
import forge.gui.UiCommand;
import forge.item.InventoryItem;
import forge.item.ItemPredicate;
@@ -43,7 +44,8 @@ public abstract class StatTypeFilter<T extends InventoryItem> extends ToggleButt
}
tooltip.append(")");
final FLabel button = addToggleButton(widget, FSkin.getImage(st.skinProp, 18, 18), tooltip.toString());
int imageSize = Math.round(18 * GuiBase.getInterface().getScreenScale());
final FLabel button = addToggleButton(widget, FSkin.getImage(st.skinProp, imageSize, imageSize), tooltip.toString());
buttonMap.put(st, button);
//hook so right-clicking a button toggles itself on and toggles off all other buttons

View File

@@ -7,6 +7,7 @@ import forge.deck.io.DeckPreferences;
import forge.game.card.Card;
import forge.game.card.CardView;
import forge.gamemodes.limited.CardRanker;
import forge.gui.GuiBase;
import forge.gui.framework.ILocalRepaint;
import forge.item.IPaperCard;
import forge.item.InventoryItem;
@@ -1164,10 +1165,17 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
g.setColor(Color.black);
g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, cornerSize, cornerSize);
BufferedImage img = ImageCache.getImage(item, bounds.width - 2 * borderSize, bounds.height - 2 * borderSize, itemInfo.alt);
final float screenScale = GuiBase.getInterface().getScreenScale();
final int drawX = bounds.x + borderSize;
final int drawY = bounds.y + borderSize;
final int drawWidth = bounds.width - 2 * borderSize;
final int drawHeight = bounds.height - 2 * borderSize;
final int imageWidth = Math.round(drawWidth * screenScale);
final int imageHeight = Math.round(drawHeight * screenScale);
BufferedImage img = ImageCache.getImage(item, imageWidth, imageHeight, itemInfo.alt);
if (img != null) {
g.drawImage(img, null, bounds.x + borderSize, bounds.y + borderSize);
g.drawImage(img, drawX, drawY, drawX + drawWidth, drawY + drawHeight, 0, 0, imageWidth, imageHeight, null);
}
else {
if (deckSelectMode) {

View File

@@ -43,6 +43,7 @@ import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deck.DeckProxy;
import forge.deck.DeckgenUtil;
import forge.gui.GuiBase;
import forge.gui.UiCommand;
import forge.item.PaperCard;
import forge.localinstance.skin.FSkinProp;
@@ -436,12 +437,15 @@ public class AddBasicLandsDialog {
final Graphics2D g2d = (Graphics2D) g;
int width = getWidth();
int height = getHeight();
final float screenScale = GuiBase.getInterface().getScreenScale();
final int drawWidth = getWidth();
final int drawHeight = getHeight();
final int imageWidth = Math.round(drawWidth * screenScale);
final int imageHeight = Math.round(drawHeight * screenScale);
final BufferedImage img = ImageCache.getImage(card, width, height);
final BufferedImage img = ImageCache.getImage(card, imageWidth, imageHeight);
if (img != null) {
g2d.drawImage(img, null, (width - img.getWidth()) / 2, (height - img.getHeight()) / 2);
g2d.drawImage(img, 0, 0, drawWidth, drawHeight, 0, 0, imageWidth, imageHeight, null);
}
}
}

View File

@@ -37,6 +37,7 @@ import forge.deck.DeckBase;
import forge.deck.DeckFormat;
import forge.deck.DeckSection;
import forge.game.GameType;
import forge.gui.GuiBase;
import forge.gui.GuiChoose;
import forge.gui.GuiUtils;
import forge.gui.UiCommand;
@@ -126,7 +127,9 @@ public abstract class ACEditorBase<TItem extends InventoryItem, TModel extends D
.fontSize(14)
.text(localizer.getMessage("lblAddBasicLands"))
.tooltip(localizer.getMessage("ttAddBasicLands"))
.icon(FSkin.getImage(FSkinProp.IMG_LAND, 18, 18))
.icon(FSkin.getImage(FSkinProp.IMG_LAND,
Math.round(18 * GuiBase.getInterface().getScreenScale()),
Math.round(18 * GuiBase.getInterface().getScreenScale())))
.iconScaleAuto(false).hoverable().build();
protected ACEditorBase(final FScreen screen0, final CDetailPicture cDetailPicture0, final GameType gameType0) {

View File

@@ -8,6 +8,7 @@ import forge.gui.framework.DragCell;
import forge.gui.framework.DragTab;
import forge.gui.framework.EDocID;
import forge.gui.framework.IVDoc;
import forge.gui.GuiBase;
import forge.itemmanager.SItemManagerUtil.StatTypes;
import forge.screens.deckeditor.controllers.CStatistics;
import forge.toolbox.FLabel;
@@ -309,6 +310,7 @@ public enum VStatistics implements IVDoc<CStatistics> {
}
private static FLabel buildLabel(final StatTypes statType, final boolean zebra) {
return buildLabel(FSkin.getImage(statType.skinProp, 18, 18), zebra);
int imageSize = Math.round(18 * GuiBase.getInterface().getScreenScale());
return buildLabel(FSkin.getImage(statType.skinProp, imageSize, imageSize), zebra);
}
}

View File

@@ -10,6 +10,7 @@ import com.esotericsoftware.minlog.Log;
import forge.card.ColorSet;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
import forge.gui.GuiBase;
import forge.localinstance.skin.FSkinProp;
import forge.toolbox.FSkin.SkinImage;
@@ -295,13 +296,23 @@ public class CardFaceSymbols {
FSkin.drawImage(g, MANA_IMAGES.get(imageName), x, y);
}
public static void drawManaSymbol(final String imageName, final Graphics g, final int x, final int y) {
FSkin.drawImage(g, MANA_IMAGES.get(imageName).resize(manaImageSize, manaImageSize), x, y);
drawSymbol(imageName, g, x, y, manaImageSize);
}
public static void drawSymbol(final String imageName, final Graphics g, final int x, final int y, final int size) {
FSkin.drawImage(g, MANA_IMAGES.get(imageName).resize(size, size), x, y);
// Obtain screen DPI scale
float screenScale = GuiBase.getInterface().getScreenScale();
int imageSize = Math.round(size * screenScale);
FSkin.drawImage(g, MANA_IMAGES.get(imageName).resize(imageSize, imageSize),
x, y, x + size, y + size, 0, 0, imageSize, imageSize);
}
public static void drawWatermark(final String imageName, final Graphics g, final int x, final int y, final int size) {
FSkin.drawImage(g, WATERMARKS.get(imageName).resize(size, size), x, y);
// Obtain screen DPI scale
float screenScale = GuiBase.getInterface().getScreenScale();
int imageSize = Math.round(size * screenScale);
FSkin.drawImage(g, WATERMARKS.get(imageName).resize(imageSize, imageSize),
x, y, x + size, y + size, 0, 0, imageSize, imageSize);
}
public static void drawAbilitySymbol(final String imageName, final Graphics g, final int x, final int y, final int w, final int h) {
FSkin.drawImage(g, MANA_IMAGES.get(imageName), x, y, w, h);

View File

@@ -39,6 +39,7 @@ import java.awt.Window;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BaseMultiResolutionImage;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
@@ -103,6 +104,7 @@ import javax.swing.text.JTextComponent;
import forge.Singletons;
import forge.gui.FThreads;
import forge.gui.GuiBase;
import forge.gui.GuiUtils;
import forge.gui.framework.ILocalRepaint;
import forge.localinstance.properties.ForgeConstants;
@@ -688,7 +690,14 @@ public class FSkin {
protected ImageIcon getIcon() {
if (this.imageIcon == null) {
this.imageIcon = new ImageIcon(this.image);
float screenScale = GuiBase.getInterface().getScreenScale();
int iconWidth = Math.round(image.getWidth(null) / screenScale);
int iconHeight = Math.round(image.getHeight(null) / screenScale);
Image [] iconImages = new Image[2];
iconImages[0] = image.getScaledInstance(iconWidth, iconHeight, Image.SCALE_SMOOTH);
iconImages[1] = image;
BaseMultiResolutionImage multiImage = new BaseMultiResolutionImage(iconImages);
this.imageIcon = new ImageIcon(multiImage);
}
return this.imageIcon;
}

View File

@@ -26,6 +26,7 @@ import forge.card.CardRarity;
import forge.card.mana.ManaCost;
import forge.game.card.CardView;
import forge.game.card.CardView.CardStateView;
import forge.gui.GuiBase;
import forge.gui.card.CardDetailUtil;
import forge.gui.card.CardDetailUtil.DetailColors;
import forge.localinstance.properties.ForgePreferences.FPref;
@@ -82,12 +83,15 @@ public class FCardImageRenderer {
PT_FONT = NAME_FONT;
ARTIST_FONT = new Font(Font.SERIF, Font.BOLD, 20);
float screenScale = GuiBase.getInterface().getScreenScale();
int arrayMultiplier = Math.round(2 * screenScale);
cachedFonts = new HashMap<>();
cachedFonts.put(NAME_FONT, new Font[NAME_FONT.getSize() * 2]);
cachedFonts.put(TYPE_FONT, new Font[TYPE_FONT.getSize() * 2]);
cachedFonts.put(TEXT_FONT, new Font[TEXT_FONT.getSize() * 2]);
cachedFonts.put(REMINDER_FONT, new Font[REMINDER_FONT.getSize() * 2]);
cachedFonts.put(ARTIST_FONT, new Font[ARTIST_FONT.getSize() * 2]);
cachedFonts.put(NAME_FONT, new Font[NAME_FONT.getSize() * arrayMultiplier]);
cachedFonts.put(TYPE_FONT, new Font[TYPE_FONT.getSize() * arrayMultiplier]);
cachedFonts.put(TEXT_FONT, new Font[TEXT_FONT.getSize() * arrayMultiplier]);
cachedFonts.put(REMINDER_FONT, new Font[REMINDER_FONT.getSize() * arrayMultiplier]);
cachedFonts.put(ARTIST_FONT, new Font[ARTIST_FONT.getSize() * arrayMultiplier]);
isInitialed = true;
}
@@ -620,10 +624,12 @@ public class FCardImageRenderer {
float halfWidth = w / 2;
GradientPaint gradient1 = new GradientPaint(x, y, colors[0], x + halfWidth, y, colors[1]);
g.setPaint(gradient1);
g.fillRoundRect(Math.round(x), Math.round(y), Math.round(halfWidth + arcWidth), Math.round(h), Math.round(arcWidth), Math.round(arcHeight));
g.fillRoundRect(Math.round(x), Math.round(y), Math.round(halfWidth), Math.round(h), Math.round(arcWidth), Math.round(arcHeight));
g.fillRect(Math.round(x + halfWidth - arcWidth), Math.round(y), Math.round(arcWidth), Math.round(h));
GradientPaint gradient2 = new GradientPaint(x + halfWidth, y, colors[1], x + w, y, colors[2]);
g.setPaint(gradient2);
g.fillRoundRect(Math.round(x + halfWidth - arcWidth), Math.round(y), Math.round(halfWidth + arcWidth), Math.round(h), Math.round(arcWidth), Math.round(arcHeight));
g.fillRoundRect(Math.round(x + halfWidth), Math.round(y), Math.round(halfWidth), Math.round(h), Math.round(arcWidth), Math.round(arcHeight));
g.fillRect(Math.round(x + halfWidth), Math.round(y), Math.round(arcWidth), Math.round(h));
break;
}
g.setPaint(oldPaint);

View File

@@ -33,6 +33,8 @@ import javax.swing.Timer;
import com.mortennobel.imagescaling.DimensionConstrain;
import com.mortennobel.imagescaling.ResampleOp;
import forge.gui.GuiBase;
/**
* Displays a {@code BufferedImage} at its center.
* <p>
@@ -295,8 +297,11 @@ public class FImagePanel extends JPanel {
at.rotate(Math.toRadians(degreesOfRotation));
// 2. scale image.
float screenScale = GuiBase.getInterface().getScreenScale();
if (createScaleTransform) {
at.scale(this.imageScale, this.imageScale);
at.scale(this.imageScale / screenScale, this.imageScale / screenScale);
} else {
at.scale(1 / screenScale, 1 / screenScale);
}
// 1. move the image so that its center is at (0,0).
@@ -334,6 +339,8 @@ public class FImagePanel extends JPanel {
if (this.sourceImage != null) {
if (this.autoSizeMode != AutoSizeImageMode.OFF) {
Double newScale = FImageUtil.getBestFitScale(getSourceImageSize(), this.getSize());
// apply DPI based scale
newScale *= GuiBase.getInterface().getScreenScale();
if (newScale != this.imageScale) {
isResampleEnabled = true;
this.imageScale = newScale;

View File

@@ -56,6 +56,7 @@ import forge.game.keyword.Keyword;
import forge.game.zone.ZoneType;
import forge.gui.CardContainer;
import forge.gui.FThreads;
import forge.gui.GuiBase;
import forge.item.PaperCard;
import forge.localinstance.properties.ForgeConstants;
import forge.localinstance.properties.ForgeConstants.CounterDisplayType;
@@ -215,7 +216,11 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
return;
}
cachedImage = new CachedCardImage(card, matchUI.getLocalPlayers(), imagePanel.getWidth(), imagePanel.getHeight()) {
// Obtain screen DPI scale and apply them
final float screenScale = GuiBase.getInterface().getScreenScale();
int imageWidth = Math.round(imagePanel.getWidth() * screenScale);
int imageHeight = Math.round(imagePanel.getHeight() * screenScale);
cachedImage = new CachedCardImage(card, matchUI.getLocalPlayers(), imageWidth, imageHeight) {
@Override
public void onImageFetched() {
if (cachedImage != null) {

View File

@@ -23,6 +23,8 @@ import java.awt.image.BufferedImage;
import javax.swing.JPanel;
import forge.gui.GuiBase;
/**
* <p>
* ScaledImagePanel class.
@@ -107,24 +109,25 @@ public class ScaledImagePanel extends JPanel {
//ResampleOp resizer = new ResampleOp(DimensionConstrain.createMaxDimension(this.getWidth(), this.getHeight(), !scaleLarger));
//resizer.setUnsharpenMask(UnsharpenMask.Soft);
BufferedImage img = getSrcImage(); //resizer.filter(getSrcImage(), null);
float screenScale = GuiBase.getInterface().getScreenScale();
boolean needsScale = img.getWidth() < sz.width;
float scaleFactor = ((float)img.getWidth()) / sz.width;
if (needsScale && ( scaleFactor < 0.95 || scaleFactor > 1.05 )) { // This should very low-quality scaling to draw during animation
float maxZoomX = ((float)sz.width) / img.getWidth();
float maxZoomY = ((float)sz.height) / img.getHeight();
boolean needsScale = Math.round(img.getWidth() / screenScale) < sz.width;
float scaleFactor = ((float)img.getWidth() / screenScale) / sz.width;
if (needsScale && ( scaleFactor < 0.95 || scaleFactor > 1.05 )) { // This should very low-quality scaling to draw during animation
float maxZoomX = ((float)sz.width) / (img.getWidth() / screenScale);
float maxZoomY = ((float)sz.height) / (img.getHeight() / screenScale);
float zoom = Math.min(maxZoomX, maxZoomY);
int zoomedWidth = (int) (img.getWidth() * zoom);
int zoomedHeight = (int) (img.getHeight() * zoom);
int zoomedWidth = (int) (img.getWidth() / screenScale * zoom);
int zoomedHeight = (int) (img.getHeight() / screenScale * zoom);
int x = (sz.width - zoomedWidth) / 2;
int y = (sz.height - zoomedHeight) / 2;
g.drawImage(img, x, y, zoomedWidth, zoomedHeight, null);
} else {
int x = (sz.width / 2) - (img.getWidth() / 2);
int y = (sz.height / 2) - (img.getHeight() / 2);
g.drawImage(img, x, y, null);
g.drawImage(img, x, y, x + zoomedWidth, y + zoomedHeight, 0, 0, img.getWidth(), img.getHeight(), null);
} else {
int x = Math.round((sz.width / 2) - (img.getWidth() / screenScale / 2));
int y = Math.round((sz.height / 2) - (img.getHeight() / screenScale / 2));
g.drawImage(img, x, y, x + sz.width, y + sz.height, 0, 0, img.getWidth(), img.getHeight(), null);
}
}

View File

@@ -322,4 +322,9 @@ public class GuiMobile implements IGuiBase {
public void preventSystemSleep(boolean preventSleep) {
Forge.getDeviceAdapter().preventSystemSleep(preventSleep);
}
@Override
public float getScreenScale() {
return 1f;
}
}

View File

@@ -8,8 +8,9 @@ public class DialogData {
public EffectData[] effect; //List of effects to cause when the dialog shows.
public ConditionData[] condition; //List of conditions for the action to show.
public String name; //Text to display when action is listed as a button.
public String locname; //References a localized string for the button labels.
public String text; //The text body.
public String loctext; //References a localized string.
public String loctext; //References a localized string for the text body.
public DialogData[] options; //
static public class EffectData {

View File

@@ -144,8 +144,12 @@ public class GameHUD extends Stage {
float x=(c.x-miniMap.getX())/miniMap.getWidth();
float y=(c.y-miniMap.getY())/miniMap.getHeight();
float mMapX = ui.findActor("map").getX();
float mMapY = ui.findActor("map").getY();
float mMapT = ui.findActor("map").getTop();
float mMapR = ui.findActor("map").getRight();
//map bounds
if (Controls.actorContainsVector(miniMap,c)) {
if (c.x>=mMapX&&c.x<=mMapR&&c.y>=mMapY&&c.y<=mMapT) {
touchpad.setVisible(false);
if (MapStage.getInstance().isInMap())
return true;
@@ -158,7 +162,12 @@ public class GameHUD extends Stage {
@Override
public boolean touchDown(int screenX, int screenY, int pointer, int button)
{Vector2 c=new Vector2();
{
return setPosition(screenX, screenY, pointer, button);
}
boolean setPosition(int screenX, int screenY, int pointer, int button) {
Vector2 c=new Vector2();
Vector2 touch =new Vector2();
screenToStageCoordinates(touch.set(screenX, screenY));
screenToStageCoordinates(c.set(screenX, screenY));
@@ -167,32 +176,46 @@ public class GameHUD extends Stage {
float y=(c.y-miniMap.getY())/miniMap.getHeight();
float uiX = gamehud.getX();
float uiY = gamehud.getY();
float uiTop = gamehud.getTop();
float uiRight = gamehud.getRight();
//gamehud bounds
if (c.x>=uiX&&c.x<=uiRight&&c.y>=uiY&&c.y<=uiTop) {
super.touchDown(screenX, screenY, pointer, button);
return true;
}
float mMapX = miniMap.getX();
float mMapY = miniMap.getY();
float mMapT = miniMap.getTop();
float mMapR = miniMap.getRight();
//map bounds
if (Controls.actorContainsVector(miniMap,c)) {
if (c.x>=mMapX&&c.x<=mMapR&&c.y>=mMapY&&c.y<=mMapT) {
if (MapStage.getInstance().isInMap())
return true;
if(Current.isInDebug())
WorldStage.getInstance().GetPlayer().setPosition(x*WorldSave.getCurrentSave().getWorld().getWidthInPixels(),y*WorldSave.getCurrentSave().getWorld().getHeightInPixels());
return true;
}
//gamehud bounds
for(Actor child:ui.getChildren())
{
if(child==touchpad)
continue;
if (Controls.actorContainsVector(child,c)) {
super.touchDown(screenX, screenY, pointer, button);
return true;
}
}
//display bounds
float displayX = ui.getX();
float displayY = ui.getY();
float displayT = ui.getTop();
float displayR = ui.getRight();
//menu Y bounds
float menuY = menuActor.getY();
//auto follow touchpad
if (GuiBase.isAndroid()) {
if ( (Controls.actorContainsVector(ui,touch)) //inside display bounds
if (!(touch.x>=mMapX&&touch.x<=mMapR&&touch.y>=mMapY&&touch.y<=mMapT) // not inside map bounds
&& !(touch.x>=uiX&&touch.x<=uiRight&&touch.y>=menuY&&touch.y<=uiTop) //not inside gamehud bounds and menu Y bounds
&& (touch.x>=displayX&&touch.x<=displayR&&touch.y>=displayY&&touch.y<=displayT) //inside display bounds
&& pointer < 1) { //not more than 1 pointer
touchpad.setBounds(touch.x-TOUCHPAD_SCALE/2, touch.y-TOUCHPAD_SCALE/2, TOUCHPAD_SCALE, TOUCHPAD_SCALE);
touchpad.setVisible(true);
touchpad.setResetOnTouchUp(true);
if (!Forge.isLandscapeMode())
hideButtons();
return super.touchDown(screenX, screenY, pointer, button);
}
}
@@ -268,6 +291,20 @@ public class GameHUD extends Stage {
return true;
}
if (keycode == Input.Keys.BACK) {
if (!Forge.isLandscapeMode()) {
menuActor.setVisible(!menuActor.isVisible());
statsActor.setVisible(!statsActor.isVisible());
inventoryActor.setVisible(!inventoryActor.isVisible());
deckActor.setVisible(!deckActor.isVisible());
}
}
return super.keyDown(keycode);
}
public void hideButtons() {
menuActor.setVisible(false);
deckActor.setVisible(false);
inventoryActor.setVisible(false);
statsActor.setVisible(false);
}
}

View File

@@ -1,11 +1,8 @@
package forge.adventure.util;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.Actor;
@@ -126,26 +123,14 @@ public class Controls {
FileHandle atlasFile = skinFile.sibling(skinFile.nameWithoutExtension() + ".atlas");
TextureAtlas atlas = new TextureAtlas(atlasFile);
//font
FreeTypeFontGenerator generateFonts=new FreeTypeFontGenerator(Config.instance().getFile(Paths.SKIN_FONT));
FreeTypeFontGenerator.FreeTypeFontParameter parameter = new FreeTypeFontGenerator.FreeTypeFontParameter();
parameter.borderWidth=0;
parameter.incremental = true;
parameter.mono=true;
parameter.size=11;
parameter.minFilter = Texture.TextureFilter.Nearest;
parameter.magFilter = Texture.TextureFilter.Nearest;
parameter.color= Color.WHITE;
defaultfont = generateFonts.generateFont(parameter);
parameter.size=22;
parameter.color= Color.WHITE;
bigfont = generateFonts.generateFont(parameter);
SelectedSkin.add("default",defaultfont);
SelectedSkin.add("big",bigfont);
defaultfont = new BitmapFont(Config.instance().getFile(Paths.SKIN).sibling("LanaPixel.fnt"));
bigfont = new BitmapFont(Config.instance().getFile(Paths.SKIN).sibling("LanaPixel.fnt"));
bigfont.getData().setScale(2, 2);
SelectedSkin.add("default", defaultfont);
SelectedSkin.add("big", bigfont);
SelectedSkin.addRegions(atlas);
SelectedSkin.load(skinFile);
}
return SelectedSkin;
}

View File

@@ -1,11 +1,14 @@
package forge.adventure.util;
import com.badlogic.gdx.scenes.scene2d.ui.*;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.SerializationException;
import forge.Forge;
import forge.adventure.data.DialogData;
import forge.adventure.stage.MapStage;
import forge.util.Localizer;
/**
* MapDialog
* Implements a dialogue/event tree for dialogs.
@@ -15,7 +18,7 @@ public class MapDialog {
private final MapStage stage;
private Array<DialogData> data;
private final int parentID;
private final float WIDTH = 260f;
static private final String defaultJSON = "[\n" +
" {\n" +
" \"effect\":[],\n" +
@@ -46,30 +49,27 @@ public class MapDialog {
}
}
private void loadDialog(DialogData dialog) {
private void loadDialog(DialogData dialog) { //Displays a dialog with dialogue and possible choices.
setEffects(dialog.effect);
stage.getDialog().getContentTable().clear();
stage.getDialog().getButtonTable().clear();
String text;
if(dialog.loctext != null && !dialog.loctext.isEmpty()){ //Check for localized string, otherwise print text.
text = Forge.getLocalizer().getMessage(dialog.loctext);
} else {
text = dialog.text;
}
int charCount = 0;
stage.getDialog().text(text);
Dialog D = stage.getDialog();
Localizer L = Forge.getLocalizer();
D.getContentTable().clear(); D.getButtonTable().clear(); //Clear tables to start fresh.
String text; //Check for localized string (locname), otherwise print text.
if(dialog.loctext != null && !dialog.loctext.isEmpty()) text = L.getMessage(dialog.loctext);
else text = dialog.text;
Label A = Controls.newLabel(text);
A.setWrap(true);
D.getContentTable().add(A).width(WIDTH); //Add() returns a Cell, which is what the width is being applied to.
if(dialog.options != null) {
for(DialogData option:dialog.options) {
if( isConditionOk(option.condition) ) {
charCount += option.name.length();
if(charCount > 35){ //Gross hack.
stage.getDialog().getButtonTable().row();
charCount = 0;
}
stage.getDialog().getButtonTable().add(Controls.newTextButton(option.name,() -> loadDialog(option)));
String name; //Get localized label if present.
if(option.locname != null && !option.locname.isEmpty()) name = L.getMessage(option.locname);
else name = option.name;
TextButton B = Controls.newTextButton(name,() -> loadDialog(option));
B.getLabel().setWrap(true); //We want this to wrap in case it's a wordy choice.
D.getButtonTable().add(B).width(WIDTH - 10); //The button table also returns a Cell when adding.
D.getButtonTable().row(); //Add a row. Tried to allow a few per row but it was a bit erratic.
}
}
stage.showDialog();
@@ -90,23 +90,23 @@ public class MapDialog {
void setEffects(DialogData.EffectData[] data) {
if(data==null) return;
for(DialogData.EffectData E:data) {
if (E.removeItem != null){
if (E.removeItem != null){ //Removes an item from the player's inventory.
Current.player().removeItem(E.removeItem);
}
if (E.addItem != null){
if (E.addItem != null){ //Gives an item to the player.
Current.player().addItem(E.addItem);
}
if (E.deleteMapObject != 0){
if (E.deleteMapObject != 0){ //Removes a dummy object from the map.
if(E.deleteMapObject < 0) stage.deleteObject(parentID);
else stage.deleteObject(E.deleteMapObject);
}
if (E.battleWithActorID != 0){
if (E.battleWithActorID != 0){ //Starts a battle with the given enemy ID.
if(E.battleWithActorID < 0) stage.beginDuel(stage.getEnemyByID(parentID));
else stage.beginDuel(stage.getEnemyByID(E.battleWithActorID));
}
//Create map object.
//Check for quest flags, local.
//Check for quest flags, global.
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

View File

@@ -1,4 +1,18 @@
{
"com.badlogic.gdx.graphics.g2d.BitmapFont": {
"big": {
"file": "LanaPixel.fnt"
},
"black": {
"file": "LanaPixel.fnt"
},
"blackbig": {
"file": "LanaPixel.fnt"
},
"default": {
"file": "LanaPixel.fnt"
}
},
"com.badlogic.gdx.graphics.Color": {
"RGBA_0_0_0_255": {
"r": 0,

View File

@@ -102,3 +102,4 @@ Innistrad: Midnight Hunt, 3/6/MID, MID
Innistrad: Crimson Vow, 3/6/VOW, VOW
Innistrad: Double Feature, 3/6/MID, DBL
Kamigawa: Neon Dynasty, 3/6/NEO, NEO
Streets of New Capenna, 3/6/SNC, SNC

View File

@@ -3,10 +3,10 @@ ManaCost:W U B R
Types:Legendary Artifact Creature Human
PT:4/4
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create two 1/1 blue Thopter artifact creature tokens with flying.
SVar:TrigToken:DB$ Token | TokenAmount$ 2 | TokenScript$ u_1_1_a_thopter_flying | TokenOwner$ You
A:AB$ Charm | Cost$ 2 Sac<2/Artifact> | Choices$ DBDealDamage,DBPump,DBGainLife | Defined$ You
SVar:DBDealDamage:DB$ DealDamage | ValidTgts$ Player,Planeswalker | TgtPrompt$ Select target player or planeswalker | NumDmg$ 3 | SpellDescription$ CARDNAME deals 3 damage to target player or planeswalker.
SVar:DBPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ -4 | NumDef$ -4 | IsCurse$ True | SpellDescription$ Target creature gets -4/-4 until end of turn
SVar:TrigToken:DB$ Token | TokenAmount$ 2 | TokenScript$ u_1_1_a_thopter_flying
A:AB$ Charm | Cost$ 2 Sac<2/Artifact> | Choices$ DBDealDamage,DBPump,DBGainLife
SVar:DBDealDamage:DB$ DealDamage | ValidTgts$ Player,Planeswalker | TgtPrompt$ Select target player or planeswalker | NumDmg$ 3 | SpellDescription$ NICKNAME deals 3 damage to target player or planeswalker.
SVar:DBPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ -4 | NumDef$ -4 | IsCurse$ True | SpellDescription$ Target creature gets -4/-4 until end of turn.
SVar:DBGainLife:DB$ GainLife | LifeAmount$ 5 | Defined$ You | SpellDescription$ You gain 5 life.
DeckHas:Ability$Token|LifeGain
DeckHas:Ability$Token|LifeGain|Sacrifice & Type$Thopter
Oracle:When Breya, Etherium Shaper enters the battlefield, create two 1/1 blue Thopter artifact creature tokens with flying.\n{2}, Sacrifice two artifacts: Choose one —\n• Breya deals 3 damage to target player or planeswalker.\n• Target creature gets -4/-4 until end of turn.\n• You gain 5 life.

View File

@@ -2,8 +2,9 @@ Name:Grand Master of Flowers
ManaCost:2 W W
Types:Legendary Planeswalker Bahamut
Loyalty:3
S:Mode$ Continuous | Affected$ Card.Self | CheckSVar$ X | SVarCompare$ GE7 | AddKeyword$ Flying & Indestructible | AddType$ Creature & Dragon & God | SetPower$ 7 | SetToughness$ 7 | RemoveCardTypes$ True | CharacteristicDefining$ True | Description$ As long as CARDNAME has seven or more loyalty counters on him, he's a 7/7 Dragon God creature with flying and indestructible.
SVar:X:Count$CardCounters.LOYALTY
A:AB$ Pump | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | ValidTgts$ Creature.withoutFirst Strike+withoutDouble Strike+withoutVigilance | KW$ HIDDEN CARDNAME can't attack or block. | IsCurse$ True | Duration$ UntilYourNextTurn | AILogic$ DetainNonLand | SpellDescription$ Target creature without first strike, double strike, or vigilance can't attack or block until your next turn.
S:Mode$ Continuous | Affected$ Card.Self | IsPresent$ Card.Self+counters_GE7_LOYALTY | AddKeyword$ Flying & Indestructible | AddType$ Creature & Dragon & God | SetPower$ 7 | SetToughness$ 7 | RemoveCardTypes$ True | CharacteristicDefining$ True | Description$ As long as CARDNAME has seven or more loyalty counters on him, he's a 7/7 Dragon God creature with flying and indestructible.
A:AB$ Pump | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | ValidTgts$ Creature.withoutFirst Strike+withoutDouble Strike+withoutVigilance | TgtPrompt$ Target creature without first strike, double strike, or vigilance | Select KW$ HIDDEN CARDNAME can't attack or block. | IsCurse$ True | Duration$ UntilYourNextTurn | AILogic$ DetainNonLand | SpellDescription$ Target creature without first strike, double strike, or vigilance can't attack or block until your next turn.
A:AB$ ChangeZone | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | Origin$ Library | OriginChoice$ True | OriginAlternative$ Graveyard | AlternativeMessage$ Would you like to search your library with this ability? If you do, your library will be shuffled. | Destination$ Hand | ChangeType$ Card.namedMonk of the Open Hand | ChangeNum$ 1 | Optional$ True | SpellDescription$ Search your library and/or graveyard for a card named Monk of the Open Hand, reveal it, and put it into your hand. If you search your library this way, shuffle.
DeckHas:Type$Dragon|God
DeckHints:Name$Monk of the Open Hand
Oracle:As long as Grand Master of Flowers has seven or more loyalty counters on him, he's a 7/7 Dragon God creature with flying and indestructible.\n[+1]: Target creature without first strike, double strike, or vigilance can't attack or block until your next turn.\n[+1]: Search your library and/or graveyard for a card named Monk of the Open Hand, reveal it, and put it into your hand. If you search your library this way, shuffle.

View File

@@ -1,6 +1,7 @@
Name:Shoulder to Shoulder
ManaCost:2 W
Types:Sorcery
A:SP$ PutCounter | Cost$ 2 W | ValidTgts$ Creature | TargetMin$ 0 | TargetMax$ 2 | TgtPrompt$ Select target creature other than CARDNAME | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBDraw | SpellDescription$ Support 2. (Put a +1/+1 counter on each of up to two target creatures.)
A:SP$ PutCounter | ValidTgts$ Creature | TargetMin$ 0 | TargetMax$ 2 | TgtPrompt$ Select up to two target creatures | TargetUnique$ True | CounterType$ P1P1 | SubAbility$ DBDraw | SpellDescription$ Support 2. (Put a +1/+1 counter on each of up to two target creatures.)
SVar:DBDraw:DB$ Draw | NumCards$ 1 | SpellDescription$ Draw a card.
DeckHas:Ability$Counters
Oracle:Support 2. (Put a +1/+1 counter on each of up to two target creatures.)\nDraw a card.

View File

@@ -0,0 +1,10 @@
Name:Angel of Suffering
ManaCost:3 B B
Types:Creature Nightmare Angel
PT:5/3
K:Flying
R:Event$ DamageDone | ActiveZones$ Battlefield | ValidTarget$ You | ReplaceWith$ DoubleMill | PreventionEffect$ True | Description$ If damage would be dealt to you, prevent that damage and mill twice that many cards.
SVar:DoubleMill:DB$ Mill | Defined$ ReplacedTarget | NumCards$ X
SVar:X:ReplaceCount$DamageAmount/Twice
DeckHas:Ability$Mill
Oracle:Flying\nIf damage would be dealt to you, prevent that damage and mill twice that many cards.

View File

@@ -0,0 +1,9 @@
Name:Angelic Observer
ManaCost:5 W
Types:Creature Angel Advisor
PT:3/3
K:Flying
S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Description$ This spell costs {1} less to cast for each Citizen you control.
SVar:X:Count$TypeYouCtrl.Citizen
DeckHints:Type$Citizen
Oracle:This spell costs {1} less to cast for each Citizen you control.\nFlying

View File

@@ -0,0 +1,8 @@
Name:Attended Socialite
ManaCost:1 G
Types:Creature Elf Druid
PT:2/1
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.Other+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Alliance — Whenever another creature enters the battlefield under your control, CARDNAME gets +1/+1 until end of turn.
SVar:TrigPump:DB$ Pump | Defined$ Self | NumAtt$ 1 | NumDef$ 1
SVar:BuffedBy:Creature
Oracle:Alliance — Whenever another creature enters the battlefield under your control, Attended Socialite gets +1/+1 until end of turn.

View File

@@ -0,0 +1,12 @@
Name:Aven Heartstabber
ManaCost:U B
Types:Creature Bird Assassin
PT:1/1
K:Flying
S:Mode$ Continuous | Affected$ Card.Self | AddPower$ 2 | AddToughness$ 2 | AddKeyword$ Deathtouch | CheckSVar$ X | SVarCompare$ GE5 | Description$ As long as there are five or more mana values among cards in your graveyard, CARDNAME gets +2/+2 and has deathtouch.
SVar:X:Count$ValidGraveyard Card.YouOwn$DifferentCMC
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigMill | TriggerDescription$ When CARDNAME dies, mill two cards, then draw a card.
SVar:TrigMill:DB$ Mill | NumCards$ 2 | Defined$ You | SubAbility$ TrigDraw
SVar:TrigDraw:DB$ Draw | NumCards$ 1 | Defined$ You
DeckHas:Ability$Graveyard|Mill
Oracle:Flying\nAs long as there are five or more mana values among cards in your graveyard, Aven Heartstabber gets +2/+2 and has deathtouch.\nWhen Aven Heartstabber dies, mill two cards, then draw a card.

View File

@@ -3,7 +3,7 @@ ManaCost:3 W W
Types:Creature Human Warrior
PT:3/5
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigSelectTargetCreature | TriggerDescription$ Whenever CARDNAME attacks, CARDNAME and up to one other target creature you control both gain your choice of first strike or lifelink until end of turn.
SVar:TrigSelectTargetCreature:DB$ Pump | ValidTgts$ Creature.YouCtrl+Other | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select up to one other target creature | SubAbility$ DBKeywordChoice | SpellDescription$ CARDNAME and up to one other target creature you control both gain your choice of first strike or lifelink until end of turn.
SVar:TrigSelectTargetCreature:DB$ Pump | ValidTgts$ Creature.Other+YouCtrl | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select up to one other target creature | SubAbility$ DBKeywordChoice | SpellDescription$ CARDNAME and up to one other target creature you control both gain your choice of first strike or lifelink until end of turn.
SVar:DBKeywordChoice:DB$ GenericChoice | Defined$ You | Choices$ DBFirstStrike,DBLifelink
SVar:DBFirstStrike:DB$ Pump | Defined$ Self | KW$ First Strike | SubAbility$ DBFirstStrike2 | SpellDescription$ First strike
SVar:DBFirstStrike2:DB$ Pump | Defined$ Targeted | KW$ First Strike

View File

@@ -0,0 +1,10 @@
Name:Bouncer's Beatdown
ManaCost:2 G
Types:Instant
S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ CostReduction | Relative$ True | EffectZone$ All | Description$ This spell costs {2} less to cast if it targets a a black permanent.
SVar:CostReduction:Count$Compare CheckTgt GE1.2.0
SVar:CheckTgt:Targeted$Valid Permanent.Black
A:SP$ DealDamage | ValidTgts$ Creature,Planeswalker | TgtPrompt$ Select target creature or planeswalker | NumDmg$ X | ReplaceDyingDefined$ Targeted | SpellDescription$ CARDNAME deals X damage to target creature or planeswalker, where X is the greatest power among creatures you control. If that creature or planeswalker would die this turn, exile it instead.
SVar:X:Count$Valid Creature.YouCtrl$GreatestPower
SVar:NeedsToPlayVar:X GE3
Oracle:This spell costs {2} less to cast if it targets a black permanent.\nBouncer's Beatdown deals X damage to target creature or planeswalker, where X is the greatest power among creatures you control. If that creature or planeswalker would die this turn, exile it instead.

View File

@@ -0,0 +1,7 @@
Name:Buy Your Silence
ManaCost:4 W
Types:Sorcery
A:SP$ ChangeZone | Cost$ 4 W | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Permanent.nonLand | TgtPromt$ Select target nonland permanant | SubAbility$ DBTreasure | StackDescription$ SpellDescription | SpellDescription$ Exile target nonland permanent. Its controller creates a Treasure token. (It's an artifact with "{T}, Sacrifice this artifact: Add one mana of any color.")
SVar:DBTreasure:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_treasure_sac | TokenOwner$ TargetedController
DeckHas:Ability$Sacrifice|Token & Type$Treasure|Artifact
Oracle:Exile target nonland permanent. Its controller creates a Treasure token. (It's an artifact with "{T}, Sacrifice this artifact: Add one mana of any color.")

View File

@@ -0,0 +1,10 @@
Name:Caldaia Strongarm
ManaCost:4 G
Types:Creature Human Warrior
PT:2/3
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPutCounter | TriggerDescription$ When CARDNAME enters the battlefield, put two +1/+1 counters on target creature.
SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 2
K:Blitz:3 G
SVar:PlayMain1:TRUE
DeckHas:Ability$Counters|Sacrifice
Oracle:When Caldaia Strongarm enters the battlefield, put two +1/+1 counters on target creature.\nBlitz {3}{G} (If you cast this spell for its blitz cost, it gains haste and "When this creature dies, draw a card." Sacrifice it at the beginning of the next end step.)

View File

@@ -0,0 +1,9 @@
Name:Celebrity Fencer
ManaCost:3 W
Types:Creature Elf Druid
PT:3/2
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.Other+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Alliance — Whenever another creature enters the battlefield under your control, put a +1/+1 counter on CARDNAME.
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1
SVar:BuffedBy:Creature
DeckHas:Ability$Counters
Oracle:Alliance — Whenever another creature enters the battlefield under your control, put a +1/+1 counter on Celebrity Fencer.

View File

@@ -4,7 +4,7 @@ Types:Enchantment
K:Hideaway:5
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigMill | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, you may mill three cards. Then if there are twenty or more cards in your graveyard, you may play the exiled card without paying its mana cost.
SVar:TrigMill:DB$ Mill | NumCards$ 3 | Optional$ True | SubAbility$ DBPlay
SVar:DBPlay:DB$ Play | ConditionPresent$ Card.IsRemembered | ConditionZone$ Exile | ConditionCheckSVar$ X | ConditionSVarCompare$ GE20 | ConditionZone$ Graveyard | Defined$ Remembered | Amount$ All | Controller$ You | WithoutManaCost$ True | Optional$ True
SVar:DBPlay:DB$ Play | ConditionPresent$ Card.IsRemembered | ConditionZone$ Exile | ConditionCheckSVar$ X | ConditionSVarCompare$ GE20 | Defined$ Remembered | Amount$ All | Controller$ You | WithoutManaCost$ True | Optional$ True
SVar:X:Count$ValidGraveyard Card.YouOwn
DeckHas:Ability$Mill
Oracle:Hideaway 5 (When this enchantment enters the battlefield, look at the top five cards of your library, exile one face down, then put the rest on the bottom in a random order.)\nAt the beginning of your upkeep, you may mill three cards. Then if there are twenty or more cards in your graveyard, you may play the exiled card without paying its mana cost.

View File

@@ -4,4 +4,5 @@ Types:Creature Human Citizen
PT:2/2
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigUntap | TriggerDescription$ Whenever CARDNAME attacks, untap target creature or land.
SVar:TrigUntap:DB$ Untap | ValidTgts$ Creature,Land | TgtPrompt$ Select target creature or land
SVar:HasAttackEffect:True
Oracle:Whenever Civic Gardener attacks, untap target creature or land.

View File

@@ -0,0 +1,10 @@
Name:Corpse Appraiser
ManaCost:U B R
Types:Creature Vampire Rogue
PT:3/3
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When CARDNAME enters the battlefield, you may exile up to one target creature from a graveyard. If a card is put into exile this way, look at the top three cards of your library, then put one of those cards into your hand and the rest into your graveyard.
SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | ValidTgts$ Creature | TargetMin$ 0 | TargetMax$ 1 | SubAbility$ DBDig | TgtPrompt$ Select up to one target creature card from a graveyard | RememberChanged$ True
SVar:DBDig:DB$ Dig | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionZone$ Exile | DigNum$ 3 | DestinationZone2$ Graveyard | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
DeckHas:Ability$Graveyard
Oracle: When Corpse Appraiser enters the battlefield, exile up to one target creature card from a graveyard. If a card is put into exile this way, look at the top three cards of your library, then put one of those cards into your hand and the rest into your graveyard.

View File

@@ -7,4 +7,5 @@ K:Haste
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.Other+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Alliance — Whenever another creature enters the battlefield under your control, double CARDNAME's power until end of turn.
SVar:TrigPump:DB$ Pump | Defined$ Self | NumAtt$ +X | Double$ True
SVar:X:Count$CardPower
SVar:BuffedBy:Creature
Oracle:Trample, haste\nAlliance — Whenever another creature enters the battlefield under your control, double Devilish Valet's power until end of turn.

View File

@@ -0,0 +1,9 @@
Name:Elegant Entourage
ManaCost:3 G
Types:Creature Elf Druid
PT:4/4
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.Other+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Alliance — Whenever another creature enters the battlefield under your control, target creature other than CARDNAME gets +1/+1 and gains trample until end of turn.
SVar:TrigPump:DB$ Pump | ValidTgts$ Creature.Other | TgtPrompt$ Select target creature other than CARDNAME | NumAtt$ +1 | NumDef$ +1 | KW$ Trample
SVar:PlayMain1:TRUE
SVar:BuffedBy:Creature
Oracle:Alliance — Whenever another creature enters the battlefield under your control, target creature other than Elegant Entourage gets +1/+1 and gains trample until end of turn.

View File

@@ -0,0 +1,10 @@
Name:Evolving Door
ManaCost:2 G
Types:Artifact
A:AB$ ChangeZone | Cost$ 1 T Sac<1/Creature> | Origin$ Library | Destination$ Exile | ChangeType$ Creature.numColorsEQX | ChangeTypeDesc$ creature card that's {n:X color} | SorcerySpeed$ True | RememberChanged$ True | SubAbility$ DBPlay | SpellDescription$ Count the colors of the sacrificed creature, then search your library for a creature card that's exactly that many colors plus one. Exile that card, then shuffle.
SVar:DBPlay:DB$ Play | Defined$ Remembered | ValidSA$ Spell | Amount$ All | Controller$ You | Optional$ True | SubAbility$ DBCleanup | DefinedDesc$ the exiled card | SpellDescription$ You may cast the exiled card. Activate only as a sorcery.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Sacrificed$CardNumColors/Plus.1
DeckHas:Ability$Sacrifice
AI:RemoveDeck:Random
Oracle:{1}, {T}, Sacrifice a creature: Count the colors of the sacrificed creature, then search your library for a creature card that's exactly that many colors plus one. Exile that card, then shuffle. You may cast the exiled card. Activate only as a sorcery.

View File

@@ -0,0 +1,11 @@
Name:Extraction Specialist
ManaCost:2 W
Types:Creature Human Rogue
PT:3/2
K:Lifelink
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When CARDNAME enters the battlefield, return target creature card with mana value 2 or less from your graveyard to the battlefield. That creature can't attack or block for as long as you control CARDNAME.
SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.YouCtrl+cmcLE2 | TgtPrompt$ Select target creature with mana value 2 or less from your graveyard | SubAbility$ DBAnimate
SVar:DBAnimate:DB$ Animate | Defined$ Targeted | HiddenKeywords$ CARDNAME can't attack or block. | Duration$ UntilHostLeavesPlay
SVar:PlayMain1:TRUE
DeckHas:Ability$LifeGain|Graveyard
Oracle:Lifelink\nWhen Extraction Specialist enters the battlefield, return target creature card with mana value 2 or less from your graveyard to the battlefield. That creature can't attack or block for as long as you control Extraction Specialist.

View File

@@ -2,7 +2,7 @@ Name:Forge Boss
ManaCost:2 B R
Types:Creature Human Warrior
PT:3/4
T:Mode$ Sacrificed | ValidCard$ Creature.YouCtrl+Other | TriggerZones$ Battlefield | Execute$ TrigDamage | ActivationLimit$ 1 | TriggerDescription$ Whenever you sacrifice one or more other creatures, CARDNAME deals 2 damage to each opponent. This ability triggers only once each turn.
T:Mode$ Sacrificed | ValidCard$ Creature.Other+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigDamage | ActivationLimit$ 1 | TriggerDescription$ Whenever you sacrifice one or more other creatures, CARDNAME deals 2 damage to each opponent. This ability triggers only once each turn.
SVar:TrigDamage:DB$ DealDamage | Defined$ Opponent | NumDmg$ 2
DeckNeeds:Ability$Sacrifice
Oracle:Whenever you sacrifice one or more other creatures, Forge Boss deals 2 damage to each opponent. This ability triggers only once each turn.

View File

@@ -7,5 +7,6 @@ SVar:TrigCharm:DB$ Charm | Choices$ DBPutCounter,DBToken,DBGainLife | ChoiceRest
SVar:DBPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a +1/+1 counter on CARDNAME.
SVar:DBToken:DB$ Token | TokenScript$ c_a_treasure_sac | TokenTapped$ True | SpellDescription$ Create a tapped Treasure token.
SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 2 | SpellDescription$ You gain 2 life.
SVar:BuffedBy:Creature
DeckHas:Ability$Counters|Token|LifeGain|Sacrifice & Type$Treasure
Oracle:Alliance — Whenever another creature enters the battlefield under your control, choose one that hasn't been chosen this turn —\n• Put a +1/+1 counter on Gala Greeters.\n• Create a tapped Treasure token.\n• You gain 2 life.

View File

@@ -0,0 +1,13 @@
Name:Giada, Font of Hope
ManaCost:1 W
Types:Legendary Creature Angel
PT:2/2
K:Flying
K:Vigilance
K:ETBReplacement:Other:AddExtraCounter:Mandatory:Battlefield:Creature.Angel+YouCtrl+Other
SVar:AddExtraCounter:DB$ PutCounter | ETB$ True | Defined$ ReplacedCard | CounterType$ P1P1 | CounterNum$ X | SpellDescription$ Each other Angel you control enters the battlefield with an additional +1/+1 counter on it for each Angel you already control.
SVar:X:Count$Valid Angel.YouCtrl
A:AB$ Mana | Cost$ T | Produced$ W | RestrictValid$ Spell.Angel | SpellDescription$ Add {W}. Spend this mana only to cast an Angel spell.
DeckHas:Ability$Counters
DeckNeeds:Type$Angel
Oracle:Flying, vigilance\nEach other Angel you control enters the battlefield with an additional +1/+1 counter on it for each Angel you already control.\n{T}: Add {W}. Spend this mana only to cast an Angel spell.

View File

@@ -0,0 +1,9 @@
Name:Girder Goons
ManaCost:4 B
Types:Creature Ogre Warrior
PT:4/4
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ When CARDNAME dies, create a tapped 2/2 black Rogue creature token.
SVar:TrigToken:DB$ Token | TokenScript$ b_2_2_rogue | TokenTapped$ True
K:Blitz:3 B
DeckHas:Ability$Token|Sacrifice & Type$Rogue
Oracle:When Girder Goons dies, create a tapped 2/2 black Rogue creature token.\nBlitz {3}{B} (If you cast this spell for its blitz cost, it gains haste and "When this creature dies, draw a card." Sacrifice it at the beginning of the next end step.)

View File

@@ -0,0 +1,14 @@
Name:Glamorous Outlaw
ManaCost:3 U B R
Types:Creature Vampire Rogue
PT:4/5
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDealDamage | TriggerDescription$ When CARDNAME enters the battlefield, it deals 2 damage to each opponent and you scry 2.
SVar:TrigDealDamage:DB$ DealDamage | NumDmg$ 2 | Defined$ Opponent | SubAbility$ DBScry
SVar:DBScry:DB$ Scry | ScryNum$ 2
A:AB$ Effect | Cost$ 2 ExileFromHand<1/CARDNAME> | ActivationZone$ Hand | ValidTgts$ Land | TgtPrompt$ Select target land | RememberObjects$ Targeted,Self | StaticAbilities$ Land,MayPlay | Triggers$ Cast | ImprintCards$ Self | Duration$ Permanent | ForgetOnMoved$ Exile | SpellDescription$ Target land gains "{T}: Add {U}, {B}, or {R}" until CARDNAME is cast from exile. You may cast CARDNAME for as long as it remains exiled.
SVar:Land:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Card.IsRemembered+IsNotImprinted | AddAbility$ Mana | Description$ Target land gains "{T}: Add {U}, {B}, or {R}" until EFFECTSOURCE is cast from exile. You may cast EFFECTSOURCE for as long as it remains exiled.
SVar:Mana:AB$ Mana | Cost$ T | Produced$ Combo U B R | Amount$ 1 | SpellDescription$ Add {U}, {B}, or {R}
SVar:MayPlay:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsImprinted+IsRemembered | AffectedZone$ Exile | Secondary$ True | Description$ You may cast EFFECTSOURCE for as long as it remains exiled.
SVar:Cast:Mode$ SpellCast | ValidCard$ Card.IsImprinted+IsRemembered+wasCastFromExile | Execute$ ExileSelf | Static$ True
SVar:ExileSelf:DB$ ChangeZone | Origin$ Command | Destination$ Exile | Defined$ Self
Oracle:When Glamorous Outlaw enters the battlefield, it deals 2 damage to each opponent and you scry 2.\n{2}, Exile Glamorous Outlaw from your hand: Target land gains "{T}: Add {U}, {B}, or {R}" until Glamorous Outlaw is cast from exile. You may cast Glamorous Outlaw for as long as it remains exiled.

View File

@@ -0,0 +1,9 @@
Name:Graveyard Shift
ManaCost:4 B
Types:Sorcery
S:Mode$ Continuous | CharacteristicDefining$ True | Affected$ Card.Self | AddKeyword$ Flash | CheckSVar$ X | SVarCompare$ GE5 | Description$ This spell has flash as long as there are five or more mana values among cards in your graveyard.
SVar:X:Count$ValidGraveyard Card.YouOwn$DifferentCMC
A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.YouOwn | TgtPrompt$ Select target creature card in your graveyard | SpellDescription$ Return target creature card from your graveyard to the battlefield.
DeckHas:Ability$Graveyard
DeckHints:Ability$Discard|Mill
Oracle:This spell has flash as long as there are five or more mana values among cards in your graveyard.\nReturn target creature card from your graveyard to the battlefield.

View File

@@ -0,0 +1,5 @@
Name:Incandescent Aria
ManaCost:R G W
Types:Sorcery
A:SP$ DamageAll | ValidCards$ Creature.nonToken | NumDmg$ 3 | SpellDescription$ CARDNAME deals 3 damage to each nontoken creature.
Oracle:Incandescent Aria deals 3 damage to each nontoken creature.

View File

@@ -6,4 +6,5 @@ A:AB$ CopyPermanent | Cost$ R T Discard<1/Card> | ValidTgts$ Creature.Other+YouC
SVar:Dies:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ When this creature dies, draw a card.
SVar:TrigDraw:DB$ Draw | NumCards$ 1
K:Blitz:1 R
DeckHas:Ability$Discard|Token|Sacrifice
Oracle:{R}, {T}, Discard a card: Create a token that's a copy of another target creature you control. It gains haste and "When this creature dies, draw a card." Sacrifice it at the beginning of the next end step. Activate only as a sorcery.\nBlitz {1}{R} (If you cast this spell for its blitz cost, it gains haste and "When this creature dies, draw a card." Sacrifice it at the beginning of the next end step.)

View File

@@ -0,0 +1,10 @@
Name:Knockout Blow
ManaCost:2 W
Types:Instant
S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ CostReduction | Relative$ True | EffectZone$ All | Description$ This spell costs {2} less to cast if it targets a red creature.
SVar:CostReduction:Count$Compare CheckTgt GE1.2.0
SVar:CheckTgt:Targeted$Valid Creature.Red
A:SP$ DealDamage | ValidTgts$ Creature.attacking,Creature.blocking | TgtPrompt$ Select target attacking or blocking creature | NumDmg$ 4 | SubAbility$ GainLife | SpellDescription$ CARDNAME deals 4 damage to target attacking or blocking creature and you gain 2 life.
SVar:GainLife:DB$ GainLife | LifeAmount$ 2 | Defined$ You
DeckHas:Ability$LifeGain
Oracle:This spell costs {2} less to cast if it targets a red creature.\nKnockout Blow deals 4 damage to target attacking or blocking creature and you gain 2 life.

View File

@@ -5,4 +5,5 @@ PT:3/1
A:AB$ Draw | Cost$ 4 UR ExileFromGrave<1/CARDNAME> | ActivationZone$ Graveyard | NumCards$ 2 | SubAbility$ DBDiscard | SpellDescription$ Draw two cards, then discard a card.
SVar:DBDiscard:DB$ Discard | Mode$ TgtChoose
DeckHas:Ability$Discard
DeckHints:Color$Blue|Red
Oracle:{4}{U/R}, Exile Maestros Initiate from your graveyard: Draw two cards, then discard a card.

View File

@@ -0,0 +1,13 @@
Name:Masked Bandits
ManaCost:3 B R G
Types:Creature Raccoon Rogue
PT:5/5
K:Vigilance
K:Menace
A:AB$ Effect | Cost$ 2 ExileFromHand<1/CARDNAME> | ActivationZone$ Hand | ValidTgts$ Land | TgtPrompt$ Select target land | RememberObjects$ Targeted,Self | StaticAbilities$ Land,MayPlay | Triggers$ Cast | ImprintCards$ Self | Duration$ Permanent | ForgetOnMoved$ Exile | SpellDescription$ Target land gains "{T}: Add {B}, {R}, or {G}" until CARDNAME is cast from exile. You may cast CARDNAME for as long as it remains exiled.
SVar:Land:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Card.IsRemembered+IsNotImprinted | AddAbility$ Mana | Description$ Target land gains "{T}: Add {B}, {R}, or {G}" until EFFECTSOURCE is cast from exile. You may cast EFFECTSOURCE for as long as it remains exiled.
SVar:Mana:AB$ Mana | Cost$ T | Produced$ Combo B R G | Amount$ 1 | SpellDescription$ Add {B}, {R}, or {G}
SVar:MayPlay:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsImprinted+IsRemembered | AffectedZone$ Exile | Secondary$ True | Description$ You may cast EFFECTSOURCE for as long as it remains exiled.
SVar:Cast:Mode$ SpellCast | ValidCard$ Card.IsImprinted+IsRemembered+wasCastFromExile | Execute$ ExileSelf | Static$ True
SVar:ExileSelf:DB$ ChangeZone | Origin$ Command | Destination$ Exile | Defined$ Self
Oracle:Vigilance\nMenace (This creature can't be blocked except by two or more creatures.)\n{2}, Exile Masked Bandits from your hand: Target land gains "{T}: Add {B}, {R}, or {G}" until Masked Bandits is cast from exile. You may cast Masked Bandits for as long as it remains exiled.

View File

@@ -0,0 +1,11 @@
Name:Mayhem Patrol
ManaCost:1 R
Types:Creature Devil Warrior
PT:1/2
K:Menace
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPump | TriggerDescription$ Whenever CARDNAME attacks, target creature gets +1/+0 until end of turn.
SVar:TrigPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ 1
K:Blitz:1 R
SVar:HasAttackEffect:True
DeckHas:Ability$Sacrifice
Oracle:Menace (This creature can't be blocked except by two or more creatures.)\nWhenever Mayhem Patrol attacks, target creature gets +1/+0 until end of turn.\nBlitz {1}{R} (If you cast this spell for its blitz cost, it gains haste and "When this creature dies, draw a card." Sacrifice it at the beginning of the next end step.)

View File

@@ -0,0 +1,10 @@
Name:Meeting of the Five
ManaCost:3 W U B R G
Types:Sorcery
A:SP$ Dig | DigNum$ 10 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBEffect | SpellDescription$ Exile the top ten cards of your library.
SVar:DBEffect:DB$ Effect | StaticAbilities$ EffSModeContinuous | ExileOnMoved$ Exile | RememberObjects$ Remembered | SubAbility$ DBCleanup | SpellDescription$ You may cast spells with exactly three colors from among them this turn.
SVar:EffSModeContinuous:Mode$ Continuous | EffectZone$ Command | Affected$ Card.IsRemembered+numColorsEQ3 | MayPlay$ True | AffectedZone$ Exile | Description$ You may cast spells with exactly three colors from among them this turn.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | SubAbility$ DBMana
SVar:DBMana:DB$ Mana | Produced$ W W U U B B R R G G | RestrictValid$ Spell.numColorsEQ3 | SpellDescription$ Add {W}{W}{U}{U}{B}{B}{R}{R}{G}{G}. Spend this mana only to cast spells with exactly three colors.
AI:RemoveDeck:Random
Oracle:Exile the top ten cards of your library. You may cast spells with exactly three colors from among them this turn. Add {W}{W}{U}{U}{B}{B}{R}{R}{G}{G}. Spend this mana only to cast spells with exactly three colors.

View File

@@ -0,0 +1,9 @@
Name:Metropolis Angel
ManaCost:2 W U
Types:Creature Angel Soldier
PT:3/1
K:Flying
T:Mode$ AttackersDeclared | ValidAttackers$ Creature.YouCtrl+HasCounters | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever you attack with one or more creatures with counters on them, draw a card.
SVar:TrigDraw:DB$ Draw
DeckHints:Ability$Counters
Oracle:Flying\nWhenever you attack with one or more creatures with counters on them, draw a card.

View File

@@ -0,0 +1,10 @@
Name:Night Clubber
ManaCost:1 B B
Types:Creature Human Warrior
PT:2/2
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPumpAll | TriggerDescription$ When CARDNAME enters the battlefield, creatures your opponents control get -1/-1 until end of turn.
SVar:TrigPumpAll:DB$ PumpAll | NumAtt$ -1 | NumDef$ -1 | ValidCards$ Creature.OppCtrl | IsCurse$ True
K:Blitz:2 B
SVar:PlayMain1:TRUE
DeckHas:Ability$Sacrifice
Oracle:When Night Clubber enters the battlefield, creatures your opponents control get -1/-1 until end of turn.\nBlitz {2}{B} (If you cast this spell for its blitz cost, it gains haste and "When this creature dies, draw a card." Sacrifice it at the beginning of the next end step.)

View File

@@ -0,0 +1,9 @@
Name:Out of the Way
ManaCost:3 U
Types:Instant
S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ CostReduction | Relative$ True | EffectZone$ All | Description$ This spell costs {2} less to cast if it targets a a green permanent.
SVar:CostReduction:Count$Compare CheckTgt GE1.2.0
SVar:CheckTgt:Targeted$Valid Permanent.Green
A:SP$ ChangeZone | ValidTgts$ Permanent.nonLand+OppCtrl | TgtPrompt$ Select target nonland permanent an opponent controls | Origin$ Battlefield | Destination$ Hand | SubAbility$ DBDraw | SpellDescription$ Return target nonland permanent an opponent controls to its owner's hand.
SVar:DBDraw:DB$ Draw
Oracle:This spell costs {2} less to cast if it targets a green permanent.\nReturn target nonland permanent an opponent controls to its owner's hand.\nDraw a card.

View File

@@ -0,0 +1,11 @@
Name:Paragon of Modernity
ManaCost:4
Types:Artifact Creature Angel Warrior
PT:2/2
K:Flying
A:AB$ Branch | Cost$ 3 | BranchConditionSVar$ X | BranchConditionSVarCompare$ EQ3 | FalseSubAbility$ DBPump | TrueSubAbility$ DBPutCounter | SpellDescription$ CARDNAME gets +1/+1 until end of turn. If exactly three colors of mana were spent to activate this ability, put a +1/+1 counter on it instead.
SVar:DBPump:DB$ Pump | Defined$ Self | NumAtt$ 1 | NumDef$ 1
SVar:DBPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1
SVar:X:Count$ManaColorsPaid
DeckHas:Ability$Counters
Oracle:Flying\n{3}: Paragon of Modernity gets +1/+1 until end of turn. If exactly three colors of mana were spent to activate this ability, put a +1/+1 counter on it instead.

View File

@@ -0,0 +1,10 @@
Name:Plasma Jockey
ManaCost:3 R
Types:Creature Viashino Warrior
PT:3/1
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPump | TriggerDescription$ Whenever CARDNAME attacks, target creature an opponent controls can't block this turn.
SVar:TrigPump:DB$ Pump | ValidTgts$ Creature.OppCtrl | KW$ HIDDEN CARDNAME can't block. | TgtPrompt$ Select target creature an opponent controls | IsCurse$ True
K:Blitz:2 R
SVar:HasAttackEffect:True
DeckHas:Ability$Sacrifice
Oracle:Whenever Plasma Jockey attacks, target creature an opponent controls can't block this turn.\nBlitz {2}{R} (If you cast this spell for its blitz cost, it gains haste and "When this creature dies, draw a card." Sacrifice it at the beginning of the next end step.)

View File

@@ -0,0 +1,10 @@
Name:Pugnacious Pugilist
ManaCost:3 R R
Types:Creature Ogre Warrior
PT:4/4
T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever CARDNAME attacks, create a tapped and attacking 1/1 red Devil creature token with "When this creature dies, it deals 1 damage to any target."
SVar:TrigToken:DB$ Token | TokenScript$ r_1_1_devil_burn | TokenTapped$ True | TokenAttacking$ True
K:Blitz:3 R
DeckHas:Ability$Token|Sacrifice & Type$Devil
SVar:HasAttackEffect:True
Oracle:Whenever Pugnacious Pugilist attacks, create a tapped and attacking 1/1 red Devil creature token with "When this creature dies, it deals 1 damage to any target."\nBlitz {3}{R} (If you cast this spell for its blitz cost, it gains haste and "When this creature dies, draw a card." Sacrifice it at the beginning of the next end step.)

View File

@@ -0,0 +1,9 @@
Name:Raffine's Guidance
ManaCost:W
Types:Enchantment Aura
K:Enchant creature
A:SP$ Attach | ValidTgts$ Creature | AILogic$ Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 1 | AddToughness$ 1 | Description$ Enchanted creature gets +1/+1.
SVar:AltCost:Cost$ 2 W | ActivationZone$ Graveyard | Description$ You may cast CARDNAME from your graveyard by paying {2}{W} rather than paying its mana cost.
DeckHas:Ability$Graveyard
Oracle:Enchant creature\nEnchanted creature gets +1/+1.\nYou may cast Raffine's Guidance from your graveyard by paying {2}{W} instead of its mana cost.

View File

@@ -0,0 +1,14 @@
Name:Rakish Revelers
ManaCost:2 R G W
Types:Creature Elf Druid Rogue
PT:5/3
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create a 1/1 green and white Citizen creature token.
SVar:TrigToken:DB$ Token | TokenScript$ gw_1_1_citizen
A:AB$ Effect | Cost$ 2 ExileFromHand<1/CARDNAME> | ActivationZone$ Hand | ValidTgts$ Land | TgtPrompt$ Select target land | RememberObjects$ Targeted,Self | StaticAbilities$ Land,MayPlay | Triggers$ Cast | ImprintCards$ Self | Duration$ Permanent | ForgetOnMoved$ Exile | SpellDescription$ Target land gains "{T}: Add {R}, {G}, or {W}" until CARDNAME is cast from exile. You may cast CARDNAME for as long as it remains exiled.
SVar:Land:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Card.IsRemembered+IsNotImprinted | AddAbility$ Mana | Description$ Target land gains "{T}: Add {R}, {G}, or {W}" until EFFECTSOURCE is cast from exile. You may cast EFFECTSOURCE for as long as it remains exiled.
SVar:Mana:AB$ Mana | Cost$ T | Produced$ Combo R G W | Amount$ 1 | SpellDescription$ Add {R}, {G}, or {W}
SVar:MayPlay:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsImprinted+IsRemembered | AffectedZone$ Exile | Secondary$ True | Description$ You may cast EFFECTSOURCE for as long as it remains exiled.
SVar:Cast:Mode$ SpellCast | ValidCard$ Card.IsImprinted+IsRemembered+wasCastFromExile | Execute$ ExileSelf | Static$ True
SVar:ExileSelf:DB$ ChangeZone | Origin$ Command | Destination$ Exile | Defined$ Self
DeckHas:Ability$Token & Type$Citizen
Oracle:When Rakish Revelers enters the battlefield, create a 1/1 green and white Citizen creature token.\n{2}, Exile Rakish Revelers from your hand: Target land gains "{T}: Add {R}, {G}, or {W}" until Rakish Revelers is cast from exile. You may cast Rakish Revelers for as long as it remains exiled.

View File

@@ -0,0 +1,6 @@
Name:Refuse to Yield
ManaCost:1 W
Types:Instant
A:SP$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +2 | NumDef$ +7 | SubAbility$ DBUntap | SpellDescription$ Target creature gets +2/+7 until end of turn. Untap it.
SVar:DBUntap:DB$ Untap | Defined$ Targeted
Oracle:Target creature gets +2/+7 until end of turn. Untap it.

View File

@@ -0,0 +1,8 @@
Name:Riveteers Decoy
ManaCost:1 G
Types:Creature Human Warrior
PT:3/1
K:CARDNAME must be blocked if able.
K:Blitz:3 G
DeckHas:Ability$Sacrifice
Oracle:Riveteers Decoy must be blocked if able.\nBlitz {3}{G} (If you cast this spell for its blitz cost, it gains haste and "When this creature dies, draw a card." Sacrifice it at the beginning of the next end step.)

View File

@@ -0,0 +1,9 @@
Name:Riveteers Requisitioner
ManaCost:1 R
Types:Creature Viashino Rogue
PT:3/1
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME dies, create a Treasure token. (It's an artifact with "{T}, Sacrifice this artifact: Add one mana of any color.")
SVar:TrigToken:DB$ Token | TokenScript$ c_a_treasure_sac
DeckHas:Ability$Token|Sacrifice & Type$Treasure|Artifact
K:Blitz:2 R
Oracle:When Riveteers Requisitioner dies, create a Treasure token. (It's an artifact with "{T}, Sacrifice this artifact: Add one mana of any color.")\nBlitz {2}{R} (If you cast this spell for its blitz cost, it gains haste and "When this creature dies, draw a card." Sacrifice it at the beginning of the next end step.)

View File

@@ -7,4 +7,5 @@ T:Mode$ Attacks | ValidCard$ Card.Self | OptionalDecider$ DefendingPlayer | Exec
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 | SubAbility$ TrigUntap
SVar:TrigUntap:DB$ Untap | Defined$ Self | SubAbility$ RemCombat
SVar:RemCombat:DB$ RemoveFromCombat | Defined$ Self
SVar:HasAttackEffect:True
Oracle:Menace\nWhenever Shakedown Heavy attacks, defending player may have you draw a card. If they do, untap Shakedown Heavy and remove it from combat.

View File

@@ -0,0 +1,15 @@
Name:Shattered Seraph
ManaCost:4 W U B
Types:Creature Angel Rogue
PT:4/4
K:Flying
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigGainLife | TriggerDescription$ When CARDNAME enters the battlefield, you gain 3 life.
SVar:TrigGainLife:DB$ GainLife | LifeAmount$ 3
A:AB$ Effect | Cost$ 2 ExileFromHand<1/CARDNAME> | ActivationZone$ Hand | ValidTgts$ Land | TgtPrompt$ Select target land | RememberObjects$ Targeted,Self | StaticAbilities$ Land,MayPlay | Triggers$ Cast | ImprintCards$ Self | Duration$ Permanent | ForgetOnMoved$ Exile | SpellDescription$ Target land gains "{T}: Add {W}, {U}, or {B}" until CARDNAME is cast from exile. You may cast CARDNAME for as long as it remains exiled.
SVar:Land:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Card.IsRemembered+IsNotImprinted | AddAbility$ Mana | Description$ Target land gains "{T}: Add {W}, {U}, or {B}" until EFFECTSOURCE is cast from exile. You may cast EFFECTSOURCE for as long as it remains exiled.
SVar:Mana:AB$ Mana | Cost$ T | Produced$ Combo W U B | Amount$ 1 | SpellDescription$ Add {W}, {U}, or {B}
SVar:MayPlay:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsImprinted+IsRemembered | AffectedZone$ Exile | Secondary$ True | Description$ You may cast EFFECTSOURCE for as long as it remains exiled.
SVar:Cast:Mode$ SpellCast | ValidCard$ Card.IsImprinted+IsRemembered+wasCastFromExile | Execute$ ExileSelf | Static$ True
SVar:ExileSelf:DB$ ChangeZone | Origin$ Command | Destination$ Exile | Defined$ Self
DeckHas:Ability$LifeGain
Oracle:Flying\nWhen Shattered Seraph enters the battlefield, you gain 3 life.\n{2}, Exile Shattered Seraph from your hand: Target land gains "{T}: Add {W}, {U}, or {B}" until Shattered Seraph is cast from exile. You may cast Shattered Seraph for as long as it remains exiled.

View File

@@ -0,0 +1,9 @@
Name:Social Climber
ManaCost:2 G
Types:Creature Human Druid
PT:3/2
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.Other+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigGainLife | TriggerDescription$ Alliance — Whenever another creature enters the battlefield under your control, you gain 1 life.
SVar:TrigGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 1
SVar:BuffedBy:Creature
DeckHas:Ability$LifeGain
Oracle:Alliance — Whenever another creature enters the battlefield under your control, you gain 1 life.

View File

@@ -0,0 +1,13 @@
Name:Spara's Adjudicators
ManaCost:2 G W U
Types:Creature Cat Citizen
PT:4/4
T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigPump | TriggerDescription$ When CARDNAME enters the battlefield, target creature an opponent controls can't attack or block until your next turn.
SVar:TrigPump:DB$ Pump | ValidTgts$ Creature.OpponentCtrl | TgtPrompt$ Select target creature an opponent controls | KW$ HIDDEN CARDNAME can't attack or block. | IsCurse$ True | Duration$ UntilYourNextTurn
A:AB$ Effect | Cost$ 2 ExileFromHand<1/CARDNAME> | ActivationZone$ Hand | ValidTgts$ Land | TgtPrompt$ Select target land | RememberObjects$ Targeted,Self | StaticAbilities$ Land,MayPlay | Triggers$ Cast | ImprintCards$ Self | Duration$ Permanent | ForgetOnMoved$ Exile | SpellDescription$ Target land gains "{T}: Add {G}, {W}, or {U}" until CARDNAME is cast from exile. You may cast CARDNAME for as long as it remains exiled.
SVar:Land:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Card.IsRemembered+IsNotImprinted | AddAbility$ Mana | Description$ Target land gains "{T}: Add {G}, {W}, or {U}" until EFFECTSOURCE is cast from exile. You may cast EFFECTSOURCE for as long as it remains exiled.
SVar:Mana:AB$ Mana | Cost$ T | Produced$ Combo G W U | Amount$ 1 | SpellDescription$ Add {G}, {W}, or {U}
SVar:MayPlay:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsImprinted+IsRemembered | AffectedZone$ Exile | Secondary$ True | Description$ You may cast EFFECTSOURCE for as long as it remains exiled.
SVar:Cast:Mode$ SpellCast | ValidCard$ Card.IsImprinted+IsRemembered+wasCastFromExile | Execute$ ExileSelf | Static$ True
SVar:ExileSelf:DB$ ChangeZone | Origin$ Command | Destination$ Exile | Defined$ Self
Oracle:When Spara's Adjudicators enters the battlefield, target creature an opponent controls can't attack or block until your next turn.\n{2}, Exile Spara's Adjudicators from your hand: Target land gains "{T}: Add {G}, {W}, or {U}" until Spara's Adjudicators is cast from exile. You may cast Spara's Adjudicators for as long as it remains exiled.

View File

@@ -0,0 +1,8 @@
Name:Tainted Indulgence
ManaCost:U B
Types:Instant
A:SP$ Draw | NumCards$ 2 | SubAbility$ DBDiscard | SpellDescription$ Draw two cards. Then discard a card unless there are five or more mana values among cards in your graveyard.
SVar:DBDiscard:DB$ Discard | NumCards$ 1 | Mode$ TgtChoose | ConditionCheckSVar$ X | ConditionSVarCompare$ LE4
SVar:X:Count$ValidGraveyard Card.YouOwn$DifferentCMC
DeckHas:Ability$Discard|Graveyard
Oracle:Draw two cards. Then discard a card unless there are five or more mana values among cards in your graveyard.

View File

@@ -0,0 +1,8 @@
Name:Tenacious Underdog
ManaCost:1 B
Types:Creature Human Warrior
PT:3/2
K:Blitz:2 B B PayLife<2>
S:Mode$ Continuous | Affected$ Card.Self | MayPlay$ True | ValidSA$ Spell.Blitz | AffectedZone$ Graveyard | EffectZone$ Graveyard | Description$ You may cast CARDNAME from your graveyard using its blitz ability.
DeckHas:Ability$Sacrifice|Graveyard
Oracle:Blitz—{2}{B}{B}, Pay 2 life. (If you cast this spell for its blitz cost, it gains haste and "When this creature dies, draw a card." Sacrifice it at the beginning of the next end step.)\nYou may cast Tenacious Underdog from your graveyard using its blitz ability.

View File

@@ -0,0 +1,10 @@
Name:Torch Breath
ManaCost:X R
Types:Instant
S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ CostReduction | Relative$ True | EffectZone$ All | Description$ This spell costs {2} less to cast if it targets a a blue permanent.
SVar:CostReduction:Count$Compare CheckTgt GE1.2.0
SVar:CheckTgt:Targeted$Valid Permanent.Blue
K:This spell can't be countered.
A:SP$ DealDamage | ValidTgts$ Creature,Planeswalker | TgtPrompt$ Select target creature or planeswalker | NumDmg$ X | SpellDescription$ CARDNAME deals X damage to target creature or planeswalker.
SVar:X:Count$xPaid
Oracle:This spell costs {2} less to cast if it targets a blue permanent.\nThis spell can't be countered.\nTorch Breath deals X damage to target creature or planeswalker.

View File

@@ -0,0 +1,10 @@
Name:Venom Connoisseur
ManaCost:1 G
Types:Creature Human Druid
PT:2/2
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.Other+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Alliance — Whenever another creature enters the battlefield under your control, Venom Connoisseur gains deathtouch until end of turn. If this is the second time this ability has resolved this turn, all creatures you control gain deathtouch until end of turn.
SVar:TrigPump:DB$ Pump | Defined$ Self | KW$ Deathtouch | SubAbility$ DBPumpAll
SVar:DBPumpAll:DB$ PumpAll | ValidCards$ Creature.YouCtrl | KW$ Deathtouch | ConditionCheckSVar$ CreatureETBAmount | ConditionSVarCompare$ EQ2
SVar:CreatureETBAmount:Count$ResolvedThisTurn
SVar:BuffedBy:Creature
Oracle:Alliance — Whenever another creature enters the battlefield under your control, Venom Connoisseur gains deathtouch until end of turn. If this is the second time this ability has resolved this turn, all creatures you control gain deathtouch until end of turn.

View File

@@ -0,0 +1,6 @@
Name:Void Rend
ManaCost:W U B
Types:Instant
K:This spell can't be countered.
A:SP$ Destroy | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | SpellDescription$ Destroy target nonland permanent.
Oracle:This spell can't be countered.\nDestroy target nonland permanent.

View File

@@ -0,0 +1,8 @@
Name:Whack
ManaCost:3 B
Types:Sorcery
S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ CostReduction | Relative$ True | EffectZone$ All | Description$ This spell costs {3} less to cast if it targets a a white creature.
SVar:CostReduction:Count$Compare CheckTgt GE1.3.0
SVar:CheckTgt:Targeted$Valid Creature.White
A:SP$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ -4 | NumDef$ -4 | IsCurse$ True | SpellDescription$ Target creature gets -4/-4 until end of turn.
Oracle:This spell costs {3} less to cast if it targets a white creature.\nTarget creature gets -4/-4 until end of turn.

View File

@@ -0,0 +1,11 @@
Name:Widespread Thieving
ManaCost:2 R
Types:Enchantment
K:Hideaway:5
T:Mode$ SpellCast | ValidCard$ Card.MultiColor | ValidActivatingPlayer$ You | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a multicolored spell, create a Treasure token. Then, you may pay {W}{U}{B}{R}{G}. If you do, you may play the exiled card without paying its mana cost.
SVar:TrigToken:DB$ Token | TokenScript$ c_a_treasure_sac | SubAbility$ DBPlay
SVar:DBPlay:DB$ Play | UnlessCost$ W U B R G | UnlessSwitched$ True | UnlessPayer$ You | ConditionPresent$ Card.IsRemembered | ConditionZone$ Exile | Defined$ Remembered | Amount$ All | Controller$ You | WithoutManaCost$ True | Optional$ True
AI:RemoveDeck:Random
SVar:BuffedBy:Card.MultiColor
DeckHas:Ability$Token|Sacrifice & Type$Treasure|Artifact
Oracle:Hideaway 5 (When this enchantment enters the battlefield, look at the top five cards of your library, exile one face down, then put the rest on the bottom in a random order.)\nWhenever you cast a multicolored spell, create a Treasure token. Then, you may pay {W}{U}{B}{R}{G}. If you do, you may play the exiled card without paying its mana cost.

View File

@@ -0,0 +1,8 @@
Name:Witness Protection
ManaCost:U
Types:Enchantment Aura
K:Enchant creature
A:SP$ Attach | ValidTgts$ Creature | AILogic$ Curse
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | SetPower$ 1 | SetToughness$ 1 | SetColor$ Green & White | RemoveAllAbilities$ True | AddType$ Creature & Citizen | SetName$ Legitimate Businessperson | RemoveCardTypes$ True | RemoveCreatureTypes$ True | Description$ Enchanted creature loses all abilities and is a green and white Citizen creature with base power and toughness 1/1 named Legitimate Businessperson. (It loses all other colors, card types, creature types, and names.)
SVar:NonStackingAttachEffect:True
Oracle:Enchant creature\nEnchanted creature loses all abilities and is a green and white Citizen creature with base power and toughness 1/1 named Legitimate Businessperson. (It loses all other colors, card types, creature types, and names.)

View File

@@ -0,0 +1,8 @@
Name:Witty Roastmaster
ManaCost:2 R
Types:Creature Devil Citizen
PT:3/2
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.Other+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigDmg | TriggerDescription$ Alliance — Whenever another creature enters the battlefield under your control, CARDNAME deals 1 damage to each opponent.
SVar:TrigDmg:DB$ DealDamage | Defined$ Player.Opponent | NumDmg$ 1
SVar:BuffedBy:Creature
Oracle:Alliance — Whenever another creature enters the battlefield under your control, Witty Roastmaster deals 1 damage to each opponent.

View File

@@ -0,0 +1,12 @@
Name:Workshop Warchief
ManaCost:3 G G
Types:Creature Rhino Warrior
PT:5/3
K:Trample
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigGainLife | TriggerDescription$ When CARDNAME enters the battlefield, you gain 3 life.
SVar:TrigGainLife:DB$ GainLife | LifeAmount$ 3
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME dies, create a 4/4 green Rhino Warrior creature token.
SVar:TrigToken:DB$ Token | TokenScript$ g_4_4_rhino_warrior
K:Blitz:4 G G
DeckHas:Ability$LifeGain|Token|Sacrifice
Oracle:Trample\nWhen Workshop Warchief enters the battlefield, you gain 3 life.\nWhen Workshop Warchief dies, create a 4/4 green Rhino Warrior creature token.\nBlitz {4}{G}{G} (If you cast this spell for its blitz cost, it gains haste and "When this creature dies, draw a card." Sacrifice it at the beginning of the next end step.)

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