mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 11:18:01 +00:00
Merge branch 'master' of https://github.com/Ayora29/forge
This commit is contained in:
10
.github/workflows/publish-android.yml
vendored
10
.github/workflows/publish-android.yml
vendored
@@ -71,11 +71,11 @@ jobs:
|
||||
|
||||
- name: Install Android maven plugin
|
||||
run: |
|
||||
mkdir -p ~/.m2/repository/com/simpligility/maven/plugins/android-maven-plugin/4.6.1
|
||||
cd ~/.m2/repository/com/simpligility/maven/plugins/android-maven-plugin/4.6.1
|
||||
curl -L -o android-maven-plugin-4.6.1.jar https://github.com/Card-Forge/android-maven-plugin/releases/download/4.6.1/android-maven-plugin-4.6.1.jar
|
||||
curl -L -o android-maven-plugin-4.6.1.pom https://github.com/Card-Forge/android-maven-plugin/releases/download/4.6.1/android-maven-plugin-4.6.1.pom
|
||||
#mvn install:install-file -Dfile=android-maven-plugin-4.6.1.jar -DgroupId=com.simpligility.maven.plugins -DartifactId=android-maven-plugin -Dversion=4.6.1 -Dpackaging=jar
|
||||
mkdir -p ~/.m2/repository/com/simpligility/maven/plugins/android-maven-plugin/4.6.2
|
||||
cd ~/.m2/repository/com/simpligility/maven/plugins/android-maven-plugin/4.6.2
|
||||
curl -L -o android-maven-plugin-4.6.2.jar https://github.com/Card-Forge/android-maven-plugin/releases/download/4.6.2/android-maven-plugin-4.6.2.jar
|
||||
curl -L -o android-maven-plugin-4.6.2.pom https://github.com/Card-Forge/android-maven-plugin/releases/download/4.6.2/android-maven-plugin-4.6.2.pom
|
||||
#mvn install:install-file -Dfile=android-maven-plugin-4.6.2.jar -DgroupId=com.simpligility.maven.plugins -DartifactId=android-maven-plugin -Dversion=4.6.2 -Dpackaging=jar
|
||||
cd -
|
||||
mvn install -Dmaven.test.skip=true
|
||||
mvn dependency:tree
|
||||
|
||||
16
.github/workflows/snapshots-android.yml
vendored
16
.github/workflows/snapshots-android.yml
vendored
@@ -8,6 +8,11 @@ on:
|
||||
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
|
||||
required: false
|
||||
default: false
|
||||
upload_package:
|
||||
type: boolean
|
||||
description: 'Upload the completed Android package'
|
||||
required: false
|
||||
default: true
|
||||
schedule:
|
||||
# * is a special character in YAML so you have to quote this string
|
||||
- cron: '00 19 * * *'
|
||||
@@ -74,11 +79,11 @@ jobs:
|
||||
|
||||
- name: Install Android maven plugin
|
||||
run: |
|
||||
mkdir -p ~/.m2/repository/com/simpligility/maven/plugins/android-maven-plugin/4.6.1
|
||||
cd ~/.m2/repository/com/simpligility/maven/plugins/android-maven-plugin/4.6.1
|
||||
curl -L -o android-maven-plugin-4.6.1.jar https://github.com/Card-Forge/android-maven-plugin/releases/download/4.6.1/android-maven-plugin-4.6.1.jar
|
||||
curl -L -o android-maven-plugin-4.6.1.pom https://github.com/Card-Forge/android-maven-plugin/releases/download/4.6.1/android-maven-plugin-4.6.1.pom
|
||||
#mvn install:install-file -Dfile=android-maven-plugin-4.6.1.jar -DgroupId=com.simpligility.maven.plugins -DartifactId=android-maven-plugin -Dversion=4.6.1 -Dpackaging=jar
|
||||
mkdir -p ~/.m2/repository/com/simpligility/maven/plugins/android-maven-plugin/4.6.2
|
||||
cd ~/.m2/repository/com/simpligility/maven/plugins/android-maven-plugin/4.6.2
|
||||
curl -L -o android-maven-plugin-4.6.2.jar https://github.com/Card-Forge/android-maven-plugin/releases/download/4.6.2/android-maven-plugin-4.6.2.jar
|
||||
curl -L -o android-maven-plugin-4.6.2.pom https://github.com/Card-Forge/android-maven-plugin/releases/download/4.6.2/android-maven-plugin-4.6.2.pom
|
||||
#mvn install:install-file -Dfile=android-maven-plugin-4.6.2.jar -DgroupId=com.simpligility.maven.plugins -DartifactId=android-maven-plugin -Dversion=4.6.2 -Dpackaging=jar
|
||||
cd -
|
||||
mvn install -Dmaven.test.skip=true
|
||||
mvn dependency:tree
|
||||
@@ -116,6 +121,7 @@ jobs:
|
||||
|
||||
- name: 📂 Sync files
|
||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.4
|
||||
if: ${{ inputs.upload_package }}
|
||||
with:
|
||||
server: ftp.cardforge.org
|
||||
username: ${{ secrets.FTP_USERNAME }}
|
||||
|
||||
99
README.md
99
README.md
@@ -1,50 +1,93 @@
|
||||
# Forge
|
||||
# ⚔️ Forge: The Magic: The Gathering Rules Engine
|
||||
|
||||
Join the [Discord](https://discord.gg/HcPJNyD66a)
|
||||
Join the **Forge community** on [Discord](https://discord.gg/HcPJNyD66a)!
|
||||
|
||||
[](https://github.com/Card-Forge/forge/actions/workflows/test-build.yaml)
|
||||
|
||||
## Introduction
|
||||
---
|
||||
|
||||
Forge is a "Rules Engine" for the game Magic: the Gathering.
|
||||
Forge is not related in any way with Wizards of the Coast.
|
||||
Forge is open source software released under the GNU Public License.
|
||||
Forge is developed by a community of programmers who love trading card games.
|
||||
## ✨ Introduction
|
||||
|
||||
Forge is a cross-platform application and can be run on Windows, Mac, Linux and Android. It is written in Java. The engine is written in Java. The engine is designed to be extensible, so any interested programmer can join and help add new features and cards to the game. Any tech savvy user could read out card scripting system to create cards to be used inside Forge.
|
||||
The engine allows you to play in a handful of different single player environments or online against other players.
|
||||
**Forge** is a dynamic and open-source **Rules Engine** tailored for **Magic: The Gathering** enthusiasts. Developed by a community of passionate programmers, Forge allows players to explore the rich universe of MTG through a flexible, engaging platform.
|
||||
|
||||
**Note:** Forge operates independently and is not affiliated with Wizards of the Coast.
|
||||
|
||||
## Installation
|
||||
---
|
||||
|
||||
For a more in depth User Guide, please visit the [User Guide](https://github.com/Card-Forge/forge/wiki/User-Guide)
|
||||
## 🌟 Key Features
|
||||
|
||||
For Desktop users, download the [Latest Releases](https://github.com/Card-Forge/forge/releases/latest) which are typically based around Set releases.
|
||||
Or download the [Snapshot Build](https://downloads.cardforge.org/dailysnapshots/) the file that starts with "forge-gui-desktop".
|
||||
This file is tarball, and may need to be extracted twice depending on which program is being used to extract it.
|
||||
We recommend extracting to a new folder rather than on top of an existing installation.
|
||||
**For users who have played Forge before all of your user data is stored separately so you don't have to worry about losing it on upgrade.**
|
||||
- **🌐 Cross-Platform Support:** Play on **Windows, Mac, Linux,** and **Android**.
|
||||
- **🔧 Extensible Architecture:** Built in **Java**, Forge encourages developers to contribute by adding features and cards.
|
||||
- **🎮 Versatile Gameplay:** Dive into single-player modes or challenge opponents online!
|
||||
|
||||
Java 8 or later is required to run Forge. Please make sure is the right version is installed in your enviroment. Check the user guide for more info.
|
||||
---
|
||||
|
||||
For Android users, download the APK file from [Snapshot Build](https://downloads.cardforge.org/dailysnapshots/) to your device.
|
||||
On first run, Forge will download all needed data.
|
||||
## 🛠️ Installation Guide
|
||||
|
||||
## Modes of Play
|
||||
### 📥 Desktop Installation
|
||||
1. **Latest Releases:** Download the latest version [here](https://github.com/Card-Forge/forge/releases/latest).
|
||||
2. **Snapshot Build:** For the latest development version, grab the `forge-gui-desktop` tarball from our [Snapshot Build](https://downloads.cardforge.org/dailysnapshots/).
|
||||
- **Tip:** Extract to a new folder to prevent version conflicts.
|
||||
3. **User Data Management:** Previous players’ data is preserved during upgrades.
|
||||
4. **Java Requirement:** Ensure you have **Java 17 or later** installed.
|
||||
|
||||
Forge has a variety of ways to play the game. The most popular way is our Adventure mode, which is a single player campaign that allows you to play against a variety of AI opponents.
|
||||
You walk around an overworld map, and can challenge opponents to games of Magic. As you play, you'll collect more cards and items to improve your abilities.
|
||||
### 📱 Android Installation
|
||||
- Download the **APK** from the [Snapshot Build](https://downloads.cardforge.org/dailysnapshots/). On the first launch, Forge will automatically download all necessary assets.
|
||||
|
||||
Check the [Gameplay Guide](https://github.com/Card-Forge/forge/wiki/Gameplay-Guide) for more info.
|
||||
---
|
||||
|
||||
## 🎮 Modes of Play
|
||||
|
||||
Forge offers various exciting gameplay options:
|
||||
|
||||
### 🌍 Adventure Mode
|
||||
Embark on a thrilling single-player journey where you can:
|
||||
- Explore an overworld map.
|
||||
- Challenge diverse AI opponents.
|
||||
- Collect cards and items to boost your abilities.
|
||||
|
||||

|
||||
|
||||
### 🔍 Quest Modes
|
||||
Engage in focused gameplay without the overworld exploration—perfect for quick sessions!
|
||||
|
||||
Forge has several Quest modes, which is similar but without the overworld map.
|
||||
### 🤖 AI Formats
|
||||
Test your skills against AI in multiple formats:
|
||||
- **Sealed**
|
||||
- **Draft**
|
||||
- **Commander**
|
||||
- **Cube**
|
||||
|
||||
You can also play against the AI in a variety of formats, such as Sealed, Draft, Commander and Cube.
|
||||
For comprehensive gameplay instructions, visit our [Gameplay Guide](https://github.com/Card-Forge/forge/wiki/Gameplay-Guide).
|
||||
|
||||
## Questions
|
||||
---
|
||||
|
||||
If you have any questions, please join the Discord channel. Read the #rules and the frequently-asked-questions.
|
||||
If your question is not answered there, feel free to ask in the #help channel.
|
||||
## 💬 Support & Community
|
||||
|
||||
Need help? Join our vibrant Discord community!
|
||||
- 📜 Read the **#rules** and explore the **FAQ**.
|
||||
- ❓ Ask your questions in the **#help** channel for assistance.
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contributing to Forge
|
||||
|
||||
We love community contributions! Interested in helping? Check out our [Contributing Guidelines](CONTRIBUTING.md) for details on how to get started.
|
||||
|
||||
---
|
||||
|
||||
## ℹ️ About Forge
|
||||
|
||||
Forge aims to deliver an immersive and customizable Magic: The Gathering experience for fans around the world.
|
||||
|
||||
### 📊 Repository Statistics
|
||||
|
||||
| Metric | Count |
|
||||
|----------------|-------------------------------------------------------------|
|
||||
| **⭐ Stars:** | [](https://github.com/Card-Forge/forge/stargazers) |
|
||||
| **🍴 Forks:** | [](https://github.com/Card-Forge/forge/network) |
|
||||
| **👥 Contributors:** | [](https://github.com/Card-Forge/forge/graphs/contributors) |
|
||||
|
||||
---
|
||||
|
||||
**📄 License:** [GPL-3.0](LICENSE)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.65-SNAPSHOT</version>
|
||||
<version>1.6.66-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.65-SNAPSHOT</version>
|
||||
<version>1.6.66-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-ai</artifactId>
|
||||
|
||||
@@ -923,6 +923,10 @@ public class AiController {
|
||||
if (checkCurseEffects(sa)) {
|
||||
return AiPlayDecision.CurseEffects;
|
||||
}
|
||||
// TODO maybe other location for this?
|
||||
if (!sa.isLegalAfterStack()) {
|
||||
return AiPlayDecision.AnotherTime;
|
||||
}
|
||||
Card spellHost = card;
|
||||
if (sa.isSpell()) {
|
||||
spellHost = CardCopyService.getLKICopy(spellHost);
|
||||
@@ -930,10 +934,6 @@ public class AiController {
|
||||
spellHost.setLastKnownZone(game.getStackZone()); // need to add to stack to make check Restrictions respect stack cmc
|
||||
spellHost.setCastFrom(card.getZone());
|
||||
}
|
||||
// TODO maybe other location for this?
|
||||
if (!sa.isLegalAfterStack()) {
|
||||
return AiPlayDecision.AnotherTime;
|
||||
}
|
||||
if (!sa.checkRestrictions(spellHost, player)) {
|
||||
return AiPlayDecision.AnotherTime;
|
||||
}
|
||||
@@ -946,7 +946,7 @@ public class AiController {
|
||||
}
|
||||
}
|
||||
if (sa instanceof Spell) {
|
||||
if (card.isPermanent()) {
|
||||
if (sa.getApi() == ApiType.PermanentCreature || sa.getApi() == ApiType.PermanentNoncreature) {
|
||||
return canPlayFromEffectAI((Spell) sa, false, true);
|
||||
}
|
||||
if (!player.cantLoseForZeroOrLessLife() && player.canLoseLife() &&
|
||||
|
||||
@@ -651,7 +651,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
// TODO sort negatives to remove from best Cards first?
|
||||
for (final Card crd : negatives) {
|
||||
for (Map.Entry<CounterType, Integer> e : table.filterToRemove(crd).entrySet()) {
|
||||
if (ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
|
||||
if (ComputerUtil.isNegativeCounter(e.getKey(), crd) && crd.canRemoveCounters(e.getKey())) {
|
||||
int over = Math.min(e.getValue(), c - toRemove);
|
||||
if (over > 0) {
|
||||
toRemove += over;
|
||||
@@ -762,7 +762,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
}
|
||||
}
|
||||
|
||||
// if table is empty, than no counter was removed
|
||||
// if table is empty, then no counter was removed
|
||||
return table.isEmpty() ? null : PaymentDecision.counters(table);
|
||||
}
|
||||
|
||||
|
||||
@@ -836,10 +836,9 @@ public class ComputerUtil {
|
||||
}
|
||||
|
||||
public static CardCollection chooseUntapType(final Player ai, final String type, final Card activate, final boolean untap, final int amount, SpellAbility sa) {
|
||||
CardCollection typeList =
|
||||
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
|
||||
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
|
||||
|
||||
typeList = CardLists.filter(typeList, Presets.TAPPED);
|
||||
typeList = CardLists.filter(typeList, Presets.TAPPED, c -> c.getCounters(CounterEnumType.STUN) == 0 || c.canRemoveCounters(CounterType.get(CounterEnumType.STUN)));
|
||||
|
||||
if (untap) {
|
||||
typeList.remove(activate);
|
||||
@@ -851,12 +850,7 @@ public class ComputerUtil {
|
||||
|
||||
CardLists.sortByPowerDesc(typeList);
|
||||
|
||||
final CardCollection untapList = new CardCollection();
|
||||
|
||||
for (int i = 0; i < amount; i++) {
|
||||
untapList.add(typeList.get(i));
|
||||
}
|
||||
return untapList;
|
||||
return typeList.subList(0, amount);
|
||||
}
|
||||
|
||||
public static CardCollection chooseReturnType(final Player ai, final String type, final Card activate, final Card target, final int amount, SpellAbility sa) {
|
||||
|
||||
@@ -221,6 +221,8 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiUndyingList));
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO stun counters with canRemoveCounters check
|
||||
|
||||
// remove P1P1 counters from opposing creatures
|
||||
CardCollection oppP1P1List = CardLists.filter(list,
|
||||
|
||||
@@ -155,7 +155,7 @@ public class SetStateAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// non-permanent facedown can't be turned face up
|
||||
if (!card.getRules().getType().isPermanent()) {
|
||||
if (!card.getRules().getType().isPermanent() || !card.canBeTurnedFaceUp()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.65-SNAPSHOT</version>
|
||||
<version>1.6.66-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-core</artifactId>
|
||||
|
||||
@@ -224,7 +224,7 @@ public class DeckHints {
|
||||
return true;
|
||||
}
|
||||
for (String tok : card.getTokens()) {
|
||||
if (tdb != null && tdb.containsRule(tok) && predicate.apply(tdb.getToken(tok).getRules())) {
|
||||
if (tdb != null && tdb.containsRule(tok) && rulesWithTokens(predicate).apply(tdb.getToken(tok).getRules())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.65-SNAPSHOT</version>
|
||||
<version>1.6.66-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-game</artifactId>
|
||||
|
||||
@@ -309,8 +309,10 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
|
||||
abstract public void setCounters(final Map<CounterType, Integer> allCounters);
|
||||
|
||||
abstract public boolean canRemoveCounters(final CounterType type);
|
||||
|
||||
abstract public boolean canReceiveCounters(final CounterType type);
|
||||
abstract public void subtractCounter(final CounterType counterName, final int n, final Player remover);
|
||||
abstract public int subtractCounter(final CounterType counterName, final int n, final Player remover);
|
||||
abstract public void clearCounters();
|
||||
|
||||
public boolean canReceiveCounters(final CounterEnumType type) {
|
||||
@@ -331,8 +333,8 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
addCounter(CounterType.get(counterType), n, source, table);
|
||||
}
|
||||
|
||||
public void subtractCounter(final CounterEnumType counterName, final int n, final Player remover) {
|
||||
subtractCounter(CounterType.get(counterName), n, remover);
|
||||
public int subtractCounter(final CounterEnumType counterName, final int n, final Player remover) {
|
||||
return subtractCounter(CounterType.get(counterName), n, remover);
|
||||
}
|
||||
|
||||
abstract public void addCounterInternal(final CounterType counterType, final int n, final Player source, final boolean fireEvents, GameEntityCounterTable table, Map<AbilityKey, Object> params);
|
||||
|
||||
@@ -285,7 +285,16 @@ public final class AbilityFactory {
|
||||
final String key = "Choices";
|
||||
if (mapParams.containsKey(key)) {
|
||||
List<String> names = Lists.newArrayList(mapParams.get(key).split(","));
|
||||
spellAbility.setAdditionalAbilityList(key, Lists.transform(names, input -> getSubAbility(state, input, sVarHolder)));
|
||||
spellAbility.setAdditionalAbilityList(key, Lists.transform(names, input -> {
|
||||
AbilitySub sub = getSubAbility(state, input, sVarHolder);
|
||||
if (api == ApiType.GenericChoice) {
|
||||
// support scripters adding restrictions to filter illegal choices
|
||||
sub.setRestrictions(new SpellAbilityRestriction());
|
||||
makeRestrictions(sub);
|
||||
}
|
||||
return sub;
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2526,6 +2526,23 @@ public class AbilityUtils {
|
||||
return doXMath(unlocked, expr, c, ctb);
|
||||
}
|
||||
|
||||
// Count$DistinctUnlockedDoors <Valid>
|
||||
// Counts the distinct names of unlocked doors. Used for the "Promising Stairs"
|
||||
if (sq[0].startsWith("DistinctUnlockedDoors")) {
|
||||
final String[] workingCopy = l[0].split(" ", 2);
|
||||
final String validFilter = workingCopy[1];
|
||||
|
||||
Set<String> viewedNames = new HashSet<>();
|
||||
for (Card doorCard : CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validFilter, player, c, ctb)) {
|
||||
for(CardStateName stateName : doorCard.getUnlockedRooms()) {
|
||||
viewedNames.add(doorCard.getState(stateName).getName());
|
||||
}
|
||||
}
|
||||
int distinctUnlocked = viewedNames.size();
|
||||
|
||||
return doXMath(distinctUnlocked, expr, c, ctb);
|
||||
}
|
||||
|
||||
// Manapool
|
||||
if (sq[0].startsWith("ManaPool")) {
|
||||
final String color = l[0].split(":")[1];
|
||||
|
||||
@@ -4,7 +4,6 @@ import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.CardType;
|
||||
import forge.game.*;
|
||||
@@ -27,7 +26,9 @@ import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
|
||||
@@ -1085,8 +1086,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
String selectPrompt = sa.hasParam("SelectPrompt") ? sa.getParam("SelectPrompt") : MessageUtil.formatMessage(Localizer.getInstance().getMessage("lblSelectCardFromPlayerZone", "{player's}", Lang.joinHomogenous(origin, ZoneType::getTranslatedName).toLowerCase()), decider, player);
|
||||
final String totalcmc = sa.getParam("WithTotalCMC");
|
||||
final String totalpower = sa.getParam("WithTotalPower");
|
||||
final String totalCardTypes = sa.getParam("WithTotalCardTypes");
|
||||
int totcmc = AbilityUtils.calculateAmount(source, totalcmc, sa);
|
||||
int totpower = AbilityUtils.calculateAmount(source, totalpower, sa);
|
||||
int totCardTypes = AbilityUtils.calculateAmount(source, totalCardTypes, sa);
|
||||
|
||||
CardCollection chosenCards = new CardCollection();
|
||||
if (changeType.startsWith("EACH")) {
|
||||
@@ -1160,6 +1163,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If we're choosing multiple cards, only need to show the reveal dialog the first time through.
|
||||
boolean shouldReveal = (i == 0);
|
||||
Card c = null;
|
||||
@@ -1170,6 +1174,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
c = Aggregates.random(fetchList);
|
||||
} else if (defined && !chooseFromDef) {
|
||||
c = Iterables.getFirst(fetchList, null);
|
||||
} else if (totalCardTypes != null) {
|
||||
String title = selectPrompt;
|
||||
title += "\nCard types left: " + Math.max(totCardTypes, 0);
|
||||
c = decider.getController().chooseSingleCardForZoneChange(destination, origin, sa, fetchList, shouldReveal ? delayedReveal : null, title, !mandatory, decider);
|
||||
} else {
|
||||
String title = selectPrompt;
|
||||
if (changeNum > 1) { //indicate progress if multiple cards being chosen
|
||||
@@ -1202,6 +1210,13 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
if (totalpower != null) {
|
||||
totpower -= c.getCurrentPower();
|
||||
}
|
||||
if (totalCardTypes != null) {
|
||||
totCardTypes -= Iterables.size(c.getType().getCoreTypes());
|
||||
}
|
||||
}
|
||||
|
||||
if (totalCardTypes != null && totCardTypes > 0) {
|
||||
chosenCards.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1537,7 +1552,8 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
&& !sa.hasParam("AtRandom")
|
||||
&& (!sa.hasParam("Defined") || sa.hasParam("ChooseFromDefined"))
|
||||
&& !sa.hasParam("WithTotalCMC")
|
||||
&& !sa.hasParam("WithTotalPower");
|
||||
&& !sa.hasParam("WithTotalPower")
|
||||
&& !sa.hasParam("WithTotalCardTypes");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -101,8 +101,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
// uses for multi sources -> one defined/target
|
||||
// this needs given counter type
|
||||
if (sa.hasParam("ValidSource")) {
|
||||
CardCollectionView srcCards = game.getCardsIn(ZoneType.Battlefield);
|
||||
srcCards = CardLists.getValidCards(srcCards, sa.getParam("ValidSource"), activator, host, sa);
|
||||
CardCollectionView srcCards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("ValidSource"), activator, host, sa);
|
||||
List<Card> tgtCards = getDefinedCardsOrTargeted(sa);
|
||||
|
||||
if (tgtCards.isEmpty()) {
|
||||
@@ -147,11 +146,6 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
Map<CounterType, Integer> countersToAdd = Maps.newHashMap();
|
||||
|
||||
for (Card src : srcCards) {
|
||||
// rule 121.5: If the first and second objects are the same object, nothing happens
|
||||
if (src.equals(dest)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ("All".equals(counterName)) {
|
||||
final Map<CounterType, Integer> tgtCounters = Maps.newHashMap(src.getCounters());
|
||||
for (Map.Entry<CounterType, Integer> e : tgtCounters.entrySet()) {
|
||||
@@ -183,8 +177,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
params.put("CounterType", cType);
|
||||
params.put("Source", source);
|
||||
|
||||
CardCollectionView tgtCards = game.getCardsIn(ZoneType.Battlefield);
|
||||
tgtCards = CardLists.getValidCards(tgtCards, sa.getParam("ValidDefined"), activator, host, sa);
|
||||
CardCollectionView tgtCards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("ValidDefined"), activator, host, sa);
|
||||
|
||||
if (counterNum.equals("Any")) {
|
||||
tgtCards = activator.getController().chooseCardsForEffect(
|
||||
@@ -203,6 +196,9 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
if (!dest.canReceiveCounters(cType)) {
|
||||
continue;
|
||||
}
|
||||
if (!source.canRemoveCounters(cType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Card cur = game.getCardState(dest, null);
|
||||
if (cur == null || !cur.equalsWithGameTimestamp(dest)) {
|
||||
@@ -287,7 +283,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
final List<CounterType> typeChoices = Lists.newArrayList();
|
||||
// get types of counters
|
||||
for (CounterType ct : tgtCounters.keySet()) {
|
||||
if (dest.canReceiveCounters(ct)) {
|
||||
if (dest.canReceiveCounters(ct) && source.canRemoveCounters(cType)) {
|
||||
typeChoices.add(ct);
|
||||
}
|
||||
}
|
||||
@@ -337,6 +333,9 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
if (!dest.canReceiveCounters(cType)) {
|
||||
return;
|
||||
}
|
||||
if (!src.canRemoveCounters(cType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int cmax = src.getCounters(cType);
|
||||
if (cmax <= 0) {
|
||||
|
||||
@@ -127,7 +127,18 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
|
||||
|
||||
putCounter = !Expressions.compare(value, operator, operandValue);
|
||||
} else {
|
||||
putCounter = pc.chooseBinary(sa, prompt, BinaryChoiceType.AddOrRemove, params);
|
||||
boolean canReceive = tgtCard.canReceiveCounters(ctype);
|
||||
boolean canRemove = tgtCard.canRemoveCounters(ctype);
|
||||
if (!canReceive && !canRemove) {
|
||||
return;
|
||||
}
|
||||
if (canReceive && !canRemove) {
|
||||
putCounter = true;
|
||||
} else if (!canReceive && canRemove) {
|
||||
putCounter = false;
|
||||
} else {
|
||||
putCounter = pc.chooseBinary(sa, prompt, BinaryChoiceType.AddOrRemove, params);
|
||||
}
|
||||
}
|
||||
|
||||
if (putCounter) {
|
||||
|
||||
@@ -63,8 +63,7 @@ public class CountersRemoveAllEffect extends SpellAbilityEffect {
|
||||
for (final Card tgtCard : cards) {
|
||||
if (sa.hasParam("AllCounterTypes")) {
|
||||
for (Map.Entry<CounterType, Integer> e : Lists.newArrayList(tgtCard.getCounters().entrySet())) {
|
||||
numberRemoved += e.getValue();
|
||||
tgtCard.subtractCounter(e.getKey(), e.getValue(), sa.getActivatingPlayer());
|
||||
numberRemoved += tgtCard.subtractCounter(e.getKey(), e.getValue(), sa.getActivatingPlayer());
|
||||
}
|
||||
//tgtCard.getCounters().clear();
|
||||
continue;
|
||||
@@ -74,7 +73,7 @@ public class CountersRemoveAllEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (counterAmount > 0) {
|
||||
tgtCard.subtractCounter(CounterType.getType(type), counterAmount, sa.getActivatingPlayer());
|
||||
numberRemoved += tgtCard.subtractCounter(CounterType.getType(type), counterAmount, sa.getActivatingPlayer());
|
||||
game.updateLastStateForCard(tgtCard);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,8 +110,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
// Removing energy
|
||||
if (type.equals("All")) {
|
||||
for (Map.Entry<CounterType, Integer> e : Lists.newArrayList(tgtPlayer.getCounters().entrySet())) {
|
||||
tgtPlayer.subtractCounter(e.getKey(), e.getValue(), activator);
|
||||
totalRemoved += e.getValue();
|
||||
totalRemoved += tgtPlayer.subtractCounter(e.getKey(), e.getValue(), activator);
|
||||
}
|
||||
} else {
|
||||
if (num.equals("All")) {
|
||||
@@ -120,8 +119,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
if (type.equals("Any")) {
|
||||
totalRemoved += removeAnyType(tgtPlayer, cntToRemove, sa);
|
||||
} else {
|
||||
tgtPlayer.subtractCounter(counterType, cntToRemove, activator);
|
||||
totalRemoved += cntToRemove;
|
||||
totalRemoved += tgtPlayer.subtractCounter(counterType, cntToRemove, activator);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,11 +163,11 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
if (gameCard == null || !tgtCard.equalsWithGameTimestamp(gameCard)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final Zone zone = game.getZoneOf(gameCard);
|
||||
if (type.equals("All")) {
|
||||
for (Map.Entry<CounterType, Integer> e : Lists.newArrayList(gameCard.getCounters().entrySet())) {
|
||||
gameCard.subtractCounter(e.getKey(), e.getValue(), activator);
|
||||
totalRemoved += e.getValue();
|
||||
totalRemoved += gameCard.subtractCounter(e.getKey(), e.getValue(), activator);
|
||||
}
|
||||
game.updateLastStateForCard(gameCard);
|
||||
continue;
|
||||
@@ -180,6 +178,9 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
if (type.equals("Any")) {
|
||||
totalRemoved += removeAnyType(gameCard, cntToRemove, sa);
|
||||
} else {
|
||||
if (!tgtCard.canRemoveCounters(counterType)) {
|
||||
continue;
|
||||
}
|
||||
cntToRemove = Math.min(cntToRemove, gameCard.getCounters(counterType));
|
||||
|
||||
if (zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Exile)) {
|
||||
@@ -221,14 +222,18 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
final Player activator = sa.getActivatingPlayer();
|
||||
final PlayerController pc = activator.getController();
|
||||
final Map<CounterType, Integer> tgtCounters = Maps.newHashMap(entity.getCounters());
|
||||
for (CounterType ct : ImmutableList.copyOf(tgtCounters.keySet())) {
|
||||
if (!entity.canRemoveCounters(ct)) {
|
||||
tgtCounters.remove(ct);
|
||||
}
|
||||
}
|
||||
|
||||
while (cntToRemove > 0 && !tgtCounters.isEmpty()) {
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("Target", entity);
|
||||
|
||||
String prompt = Localizer.getInstance().getMessage("lblSelectCountersTypeToRemove");
|
||||
CounterType chosenType = pc.chooseCounterType(
|
||||
ImmutableList.copyOf(tgtCounters.keySet()), sa, prompt, params);
|
||||
CounterType chosenType = pc.chooseCounterType(ImmutableList.copyOf(tgtCounters.keySet()), sa, prompt, params);
|
||||
|
||||
int max = Math.min(cntToRemove, tgtCounters.get(chosenType));
|
||||
// remove selection so player can't cheat additional trigger by choosing the same type multiple times
|
||||
|
||||
@@ -21,15 +21,6 @@ public class VillainousChoiceEffect extends SpellAbilityEffect {
|
||||
for (Player p : getDefinedPlayersOrTargeted(sa)) {
|
||||
int choiceAmount = p.getAdditionalVillainousChoices() + 1;
|
||||
|
||||
List<SpellAbility> saToRemove = Lists.newArrayList();
|
||||
|
||||
for (SpellAbility saChoice : abilities) {
|
||||
if (saChoice.getRestrictions() != null && !saChoice.getRestrictions().checkOtherRestrictions(sa.getHostCard(), saChoice, sa.getActivatingPlayer())) {
|
||||
saToRemove.add(saChoice);
|
||||
}
|
||||
}
|
||||
abilities.removeAll(saToRemove);
|
||||
|
||||
// For the AI chooseSAForEffect really should take the least good ability. Currently it just takes the first
|
||||
List<SpellAbility> chosenSAs = Lists.newArrayList();
|
||||
for(int i = 0; i < choiceAmount; i++) {
|
||||
|
||||
@@ -805,6 +805,11 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
return setState(CardStateName.FaceDown, false);
|
||||
}
|
||||
|
||||
public boolean canBeTurnedFaceUp() {
|
||||
Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
|
||||
return !getGame().getReplacementHandler().cantHappenCheck(ReplacementType.TurnFaceUp, repParams);
|
||||
}
|
||||
|
||||
public void forceTurnFaceUp() {
|
||||
turnFaceUp(false, null);
|
||||
}
|
||||
@@ -813,14 +818,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
return turnFaceUp(true, cause);
|
||||
}
|
||||
public boolean turnFaceUp(boolean runTriggers, SpellAbility cause) {
|
||||
if (!isFaceDown()) {
|
||||
if (!isFaceDown() || !canBeTurnedFaceUp()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check replacement effects
|
||||
Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
|
||||
if (game.getReplacementHandler().cantHappenCheck(ReplacementType.TurnFaceUp, repParams)) return false;
|
||||
|
||||
CardCollectionView cards = hasMergedCard() ? getMergedCards() : new CardCollection(this);
|
||||
boolean retResult = false;
|
||||
long ts = game.getNextTimestamp();
|
||||
@@ -855,10 +856,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
triggerHandler.registerActiveTrigger(this, false);
|
||||
}
|
||||
if (runTriggers) {
|
||||
// Run replacement effects
|
||||
Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
|
||||
game.getReplacementHandler().run(ReplacementType.TurnFaceUp, repParams);
|
||||
|
||||
// Run triggers
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(this);
|
||||
runParams.put(AbilityKey.Cause, cause);
|
||||
|
||||
@@ -1600,6 +1600,21 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean canRemoveCounters(final CounterType type) {
|
||||
if (isPhasedOut()) {
|
||||
return false;
|
||||
}
|
||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
|
||||
repParams.put(AbilityKey.CounterType, type);
|
||||
repParams.put(AbilityKey.Result, 0);
|
||||
repParams.put(AbilityKey.IsDamage, false);
|
||||
if (game.getReplacementHandler().cantHappenCheck(ReplacementType.RemoveCounter, repParams)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCounterInternal(final CounterType counterType, final int n, final Player source, final boolean fireEvents, GameEntityCounterTable table, Map<AbilityKey, Object> params) {
|
||||
int addAmount = n;
|
||||
@@ -1736,11 +1751,11 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void subtractCounter(final CounterType counterName, final int n, final Player remover) {
|
||||
subtractCounter(counterName, n, remover, false);
|
||||
public final int subtractCounter(final CounterType counterName, final int n, final Player remover) {
|
||||
return subtractCounter(counterName, n, remover, false);
|
||||
}
|
||||
|
||||
public final void subtractCounter(final CounterType counterName, final int n, final Player remover, final boolean isDamage) {
|
||||
public final int subtractCounter(final CounterType counterName, final int n, final Player remover, final boolean isDamage) {
|
||||
int oldValue = getCounters(counterName);
|
||||
int newValue = Math.max(oldValue - n, 0);
|
||||
|
||||
@@ -1758,12 +1773,14 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
newValue = 0;
|
||||
}
|
||||
break;
|
||||
case Replaced:
|
||||
return 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
final int delta = oldValue - newValue;
|
||||
if (delta == 0) { return; }
|
||||
if (delta == 0) { return 0; }
|
||||
|
||||
int powerBonusBefore = getPowerBonusFromCounters();
|
||||
int toughnessBonusBefore = getToughnessBonusFromCounters();
|
||||
@@ -1800,6 +1817,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
runParams.put(AbilityKey.CounterAmount, delta);
|
||||
runParams.put(AbilityKey.NewCounterAmount, newValue);
|
||||
getGame().getTriggerHandler().runTrigger(TriggerType.CounterRemovedOnce, runParams, false);
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -119,7 +119,7 @@ public class CardCopyService {
|
||||
c.setSetCode(in.getSetCode());
|
||||
|
||||
for (final CardStateName state : in.getStates()) {
|
||||
copyState(in, state, c, state);
|
||||
copyState(in, state, c, state, false);
|
||||
}
|
||||
|
||||
c.setState(in.getCurrentStateName(), false);
|
||||
|
||||
@@ -1924,6 +1924,18 @@ public class CardProperty {
|
||||
if (!card.isGoaded()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("FullyUnlocked")) {
|
||||
if (card.getUnlockedRooms().size() < 2) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("canReceiveCounters")) {
|
||||
if (!card.canReceiveCounters(CounterType.getType(property.split(" ")[1]))) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("canBeTurnedFaceUp")) {
|
||||
if (!card.canBeTurnedFaceUp()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("NoAbilities")) {
|
||||
if (!card.hasNoAbilities()) {
|
||||
return false;
|
||||
|
||||
@@ -70,10 +70,16 @@ public class CostRemoveAnyCounter extends CostPart {
|
||||
int allCounters = 0;
|
||||
for (Card c : validCards) {
|
||||
if (this.counter != null) {
|
||||
if (!c.canRemoveCounters(this.counter)) {
|
||||
continue;
|
||||
}
|
||||
allCounters += c.getCounters(this.counter);
|
||||
} else {
|
||||
for (Integer value : c.getCounters().values()) {
|
||||
allCounters += value;
|
||||
for (Map.Entry<CounterType, Integer> entry : c.getCounters().entrySet()) {
|
||||
if (!c.canRemoveCounters(entry.getKey())) {
|
||||
continue;
|
||||
}
|
||||
allCounters += entry.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import com.google.common.collect.Maps;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
@@ -78,7 +80,8 @@ public class CostUntap extends CostPart {
|
||||
@Override
|
||||
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
|
||||
final Card source = ability.getHostCard();
|
||||
return source.isTapped() && !source.isAbilitySick();
|
||||
return source.isTapped() && !source.isAbilitySick() &&
|
||||
(source.getCounters(CounterEnumType.STUN) == 0 || source.canRemoveCounters(CounterType.get(CounterEnumType.STUN)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,6 +23,8 @@ import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -90,7 +92,7 @@ public class CostUntapType extends CostPartWithList {
|
||||
if (!canUntapSource) {
|
||||
typeList.remove(source);
|
||||
}
|
||||
typeList = CardLists.filter(typeList, Presets.TAPPED);
|
||||
typeList = CardLists.filter(typeList, Presets.TAPPED, c -> c.getCounters(CounterEnumType.STUN) == 0 || c.canRemoveCounters(CounterType.get(CounterEnumType.STUN)));
|
||||
|
||||
final int amount = this.getAbilityAmount(ability);
|
||||
return (typeList.size() != 0) && (typeList.size() >= amount);
|
||||
|
||||
@@ -1049,6 +1049,7 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
for (SpellAbility sa : chosenSa) {
|
||||
Card saHost = sa.getHostCard();
|
||||
final Zone originZone = saHost.getZone();
|
||||
final CardZoneTable triggerList = new CardZoneTable(game.getLastStateBattlefield(), game.getLastStateGraveyard());
|
||||
|
||||
if (pPlayerPriority.getController().playChosenSpellAbility(sa)) {
|
||||
// 117.3c If a player has priority when they cast a spell, activate an ability, [play a land]
|
||||
@@ -1064,7 +1065,6 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
// Need to check if Zone did change
|
||||
if (currentZone != null && originZone != null && !currentZone.equals(originZone) && (sa.isSpell() || sa.isLandAbility())) {
|
||||
// currently there can be only one Spell put on the Stack at once, or Land Abilities be played
|
||||
final CardZoneTable triggerList = new CardZoneTable(game.getLastStateBattlefield(), game.getLastStateGraveyard());
|
||||
triggerList.put(originZone.getZoneType(), currentZone.getZoneType(), saHost);
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
}
|
||||
|
||||
@@ -855,6 +855,14 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return true;
|
||||
}
|
||||
|
||||
public final boolean canRemoveCounters(final CounterType type) {
|
||||
if (!isInGame()) {
|
||||
return false;
|
||||
}
|
||||
// no RE affecting players currently, skip check for performance
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCounterInternal(final CounterType counterType, final int n, final Player source, final boolean fireEvents, GameEntityCounterTable table, Map<AbilityKey, Object> params) {
|
||||
int addAmount = n;
|
||||
@@ -896,12 +904,12 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subtractCounter(CounterType counterName, int num, final Player remover) {
|
||||
public int subtractCounter(CounterType counterName, int num, final Player remover) {
|
||||
int oldValue = getCounters(counterName);
|
||||
int newValue = Math.max(oldValue - num, 0);
|
||||
|
||||
final int delta = oldValue - newValue;
|
||||
if (delta == 0) { return; }
|
||||
if (delta == 0) { return 0; }
|
||||
|
||||
setCounters(counterName, newValue, null, true);
|
||||
|
||||
@@ -917,6 +925,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
getGame().getTriggerHandler().runTrigger(TriggerType.CounterRemoved, runParams, false);
|
||||
}
|
||||
*/
|
||||
return delta;
|
||||
}
|
||||
|
||||
public final void clearCounters() {
|
||||
|
||||
@@ -18,12 +18,8 @@
|
||||
package forge.game.spellability;
|
||||
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -58,9 +54,8 @@ public abstract class AbilityStatic extends Ability implements Cloneable {
|
||||
|
||||
// Check if ability can't be attempted because of replacement effect
|
||||
// Initial usage is Karlov Watchdog preventing disguise/morph/cloak/manifest turning face up
|
||||
if (this.isTurnFaceUp()) {
|
||||
Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(c);
|
||||
if (c.getGame().getReplacementHandler().cantHappenCheck(ReplacementType.TurnFaceUp, repParams)) return false;
|
||||
if (this.isTurnFaceUp() && !c.canBeTurnedFaceUp()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.getRestrictions().canPlay(c, this);
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
<properties>
|
||||
<timestamp>${maven.build.timestamp}</timestamp>
|
||||
<maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss</maven.build.timestamp.format>
|
||||
<android.manifest.versionCodeUpdateFromVersion>true</android.manifest.versionCodeUpdateFromVersion>
|
||||
<android.manifestMerger.versionDigits>2,2,2,4</android.manifestMerger.versionDigits>
|
||||
<packaging.type>jar</packaging.type>
|
||||
<build.min.memory>-Xms1024m</build.min.memory>
|
||||
<build.max.memory>-Xmx1536m</build.max.memory>
|
||||
<alpha-version>1.6.65-SNAPSHOT</alpha-version>
|
||||
<alpha-version>1.6.66-SNAPSHOT</alpha-version>
|
||||
<sign.keystore>keystore</sign.keystore>
|
||||
<sign.alias>alias</sign.alias>
|
||||
<sign.storepass>storepass</sign.storepass>
|
||||
@@ -20,7 +22,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.65-SNAPSHOT</version>
|
||||
<version>1.6.66-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-android</artifactId>
|
||||
@@ -228,7 +230,7 @@
|
||||
<version>6.2.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<version>4.6.1</version>
|
||||
<version>4.6.2</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<sign>
|
||||
@@ -239,7 +241,6 @@
|
||||
<buildTools>30.0.3</buildTools>
|
||||
</sdk>
|
||||
<dexForceJumbo>true</dexForceJumbo>
|
||||
<androidManifestFile>${project.basedir}/AndroidManifest.xml</androidManifestFile>
|
||||
<assetsDirectory>${project.basedir}/assets</assetsDirectory>
|
||||
<resourceDirectory>${project.basedir}/res</resourceDirectory>
|
||||
<nativeLibrariesDirectory>${project.basedir}/libs</nativeLibrariesDirectory>
|
||||
@@ -266,6 +267,14 @@
|
||||
<dexArguments>--min-sdk-version=26</dexArguments>
|
||||
</dex>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>update-manifest</id>
|
||||
<goals>
|
||||
<goal>manifest-merger</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
@@ -292,7 +301,7 @@
|
||||
<version>6.2.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<version>4.6.1</version>
|
||||
<version>4.6.2</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<sign>
|
||||
@@ -306,7 +315,6 @@
|
||||
<verbose>false</verbose>
|
||||
</zipalign>
|
||||
<dexForceJumbo>true</dexForceJumbo>
|
||||
<androidManifestFile>${project.basedir}/AndroidManifest.xml</androidManifestFile>
|
||||
<assetsDirectory>${project.basedir}/assets</assetsDirectory>
|
||||
<resourceDirectory>${project.basedir}/res</resourceDirectory>
|
||||
<nativeLibrariesDirectory>${project.basedir}/libs</nativeLibrariesDirectory>
|
||||
@@ -333,6 +341,14 @@
|
||||
<dexArguments>--min-sdk-version=26</dexArguments>
|
||||
</dex>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>update-manifest</id>
|
||||
<goals>
|
||||
<goal>manifest-merger</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
@@ -393,7 +409,7 @@
|
||||
<version>6.2.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<version>4.6.1</version>
|
||||
<version>4.6.2</version>
|
||||
<inherited>true</inherited>
|
||||
<configuration>
|
||||
<sign>
|
||||
@@ -414,6 +430,12 @@
|
||||
<goal>zipalign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>update-manifest</id>
|
||||
<goals>
|
||||
<goal>manifest-merger</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="forge.app"
|
||||
android:versionCode="106650000"
|
||||
android:versionName="1.6.65" > <!-- versionName should be updated and it's used for Sentry releases tag -->
|
||||
package="forge.app">
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="26"
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.65-SNAPSHOT</version>
|
||||
<version>1.6.66-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-desktop</artifactId>
|
||||
@@ -118,7 +118,7 @@
|
||||
</goals>
|
||||
<configuration>
|
||||
<!-- TODO: insert placeholder for latest version tag -->
|
||||
<fromRef>forge-1.6.64</fromRef>
|
||||
<fromRef>forge-1.6.65</fromRef>
|
||||
<file>../forge-gui/release-files/CHANGES.txt</file>
|
||||
<templateContent>
|
||||
<![CDATA[
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.65-SNAPSHOT</version>
|
||||
<version>1.6.66-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-ios</artifactId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.65-SNAPSHOT</version>
|
||||
<version>1.6.66-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-mobile-dev</artifactId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.65-SNAPSHOT</version>
|
||||
<version>1.6.66-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-mobile</artifactId>
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
package forge;
|
||||
|
||||
import com.badlogic.gdx.Application;
|
||||
import com.badlogic.gdx.ApplicationListener;
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.*;
|
||||
import com.badlogic.gdx.Input.Keys;
|
||||
import com.badlogic.gdx.Input;
|
||||
import com.badlogic.gdx.InputProcessor;
|
||||
import com.badlogic.gdx.controllers.Controller;
|
||||
import com.badlogic.gdx.controllers.ControllerAdapter;
|
||||
import com.badlogic.gdx.controllers.ControllerListener;
|
||||
@@ -54,7 +50,7 @@ import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
|
||||
public class Forge implements ApplicationListener {
|
||||
public static final String CURRENT_VERSION = "1.6.65-SNAPSHOT";
|
||||
public static final String CURRENT_VERSION = "1.6.66-SNAPSHOT";
|
||||
|
||||
private static ApplicationListener app = null;
|
||||
static Scene currentScene = null;
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Image;
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.Forge;
|
||||
import forge.adventure.character.EnemySprite;
|
||||
import forge.adventure.pointofintrest.PointOfInterestChanges;
|
||||
@@ -432,8 +433,9 @@ public class AdventureEventData implements Serializable {
|
||||
public void generateParticipants(int numberOfOpponents) {
|
||||
participants = new AdventureEventParticipant[numberOfOpponents + 1];
|
||||
|
||||
List<EnemyData> data = Aggregates.random(WorldData.getAllEnemies(), numberOfOpponents);
|
||||
data.removeIf(q -> q.nextEnemy != null);
|
||||
//TODO: Switch this to a stream with StreamUtil.random once the guava migration branch is merged.
|
||||
Iterable<EnemyData> validParticipants = Iterables.filter(WorldData.getAllEnemies(), q -> q.nextEnemy == null);
|
||||
List<EnemyData> data = Aggregates.random(validParticipants, numberOfOpponents);
|
||||
for (int i = 0; i < numberOfOpponents; i++) {
|
||||
participants[i] = new AdventureEventParticipant().generate(data.get(i));
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ public class InventoryScene extends UIScene {
|
||||
Deck data = (deckLocation.get(selected));
|
||||
if (data == null) return;
|
||||
|
||||
done();
|
||||
//done();
|
||||
setSelected(null);
|
||||
RewardScene.instance().loadRewards(data, RewardScene.Type.Loot, null, data.getTags().contains("noSell"));
|
||||
Forge.switchScene(RewardScene.instance());
|
||||
@@ -206,15 +206,7 @@ public class InventoryScene extends UIScene {
|
||||
Deck data = deckLocation.get(selected);
|
||||
if (data == null)
|
||||
return;
|
||||
if (openDialog == null) {
|
||||
openDialog = createGenericDialog("", null, Forge.getLocalizer().getMessage("lblYes"),
|
||||
Forge.getLocalizer().getMessage("lblNo"), () -> {
|
||||
this.openBooster();
|
||||
removeDialog();
|
||||
}, this::removeDialog);
|
||||
openDialog.getContentTable().add(Controls.newTextraLabel("Open Booster Pack?"));
|
||||
}
|
||||
showDialog(openDialog);
|
||||
this.openBooster();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +269,7 @@ public class InventoryScene extends UIScene {
|
||||
useButton.layout();
|
||||
equipButton.setDisabled(true);
|
||||
|
||||
itemDescription.setText("Card Pack - " + data.getName() + "\n[%98]" + (data.getComment() == null?"":data.getComment()+" - ") + data.getAllCardsInASinglePool().countAll() + " cards");
|
||||
itemDescription.setText(data.getName() + "\n[%98]" + (data.getComment() == null?"":data.getComment()+" - ") + data.getAllCardsInASinglePool().countAll() + " cards");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import forge.item.PaperCard;
|
||||
import forge.sound.SoundEffectType;
|
||||
import forge.sound.SoundSystem;
|
||||
import forge.util.ItemPool;
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* Displays the rewards of a fight or a treasure
|
||||
@@ -318,6 +319,14 @@ public class RewardScene extends UIScene {
|
||||
}
|
||||
|
||||
public void loadRewards(Array<Reward> newRewards, Type type, ShopActor shopActor) {
|
||||
// Sort the rewards based on the rarity of the card inside the reward/ lets give items rarity
|
||||
newRewards.sort(Comparator.comparing(reward -> {
|
||||
if (reward.getCard() != null && reward.getCard().getRarity() != null) {
|
||||
return reward.getCard().getRarity().ordinal();
|
||||
}
|
||||
// Return a default value or handle the case where rarity is not present
|
||||
return Integer.MAX_VALUE; // Assuming higher values mean less priority in sorting
|
||||
}));
|
||||
clearSelectable();
|
||||
this.type = type;
|
||||
doneClicked = false;
|
||||
|
||||
@@ -11,6 +11,7 @@ import forge.item.SealedTemplate;
|
||||
import forge.item.generation.BoosterGenerator;
|
||||
import forge.item.generation.UnOpenedProduct;
|
||||
import forge.model.CardBlock;
|
||||
import forge.model.FModel;
|
||||
import forge.util.Aggregates;
|
||||
|
||||
import java.io.Serializable;
|
||||
@@ -136,7 +137,8 @@ public class AdventureEventController implements Serializable {
|
||||
List<PaperCard> cards = BoosterGenerator.getBoosterPack(StaticData.instance().getBoosters().get(setCode));
|
||||
Deck output = new Deck();
|
||||
output.getMain().add(cards);
|
||||
output.setName("Booster Pack: " + setCode);
|
||||
String editionName = FModel.getMagicDb().getEditions().get(setCode).getName();
|
||||
output.setName(editionName + " Booster");
|
||||
output.setComment(setCode);
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.65-SNAPSHOT</version>
|
||||
<version>1.6.66-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui</artifactId>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<object id="66" template="../../obj/door_up.tx" x="35.2402" y="285.749">
|
||||
<properties>
|
||||
<property name="teleport" value="../common/maps/map/vampirecastle/vampirecastle_4B.tmx"/>
|
||||
<property name="teleportObjectId" value="67"/>
|
||||
<property name="teleportObjectId" value="66"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="75" template="../../obj/treasure.tx" x="784.167" y="63.75"/>
|
||||
|
||||
@@ -12250,7 +12250,7 @@
|
||||
"difficulty": 1,
|
||||
"speed": 30,
|
||||
"scale": 0.5,
|
||||
"life": 35,
|
||||
"life": 55,
|
||||
"rewards": [
|
||||
{
|
||||
"type": "card",
|
||||
|
||||
@@ -133,4 +133,5 @@ Ravnica Remastered, 3/6/RAV, RVR
|
||||
Murders at Karlov Manor, 3/6/MKM, MKM
|
||||
Outlaws of Thunder Junction, 3/6/OTJ, OTJ
|
||||
Modern Horizons 3, 3/6/MH3, MH3
|
||||
Bloomburrow, 3/6/BLB, BLB
|
||||
Bloomburrow, 3/6/BLB, BLB
|
||||
Duskmourn: House of Horror, 3/6/DSK, DSK
|
||||
@@ -9,6 +9,6 @@ SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
SVar:X:Remembered$Amount
|
||||
SVar:MaxTgts:Count$Valid Permanent.Other+nonLand+YouCtrl
|
||||
K:Choose a Background
|
||||
DeckHas:Ability$Token & Type$Soldier
|
||||
AI:RemoveDeck:Random
|
||||
DeckHas:Ability$Token & Type$Soldier
|
||||
Oracle:When Abdel Adrian, Gorion's Ward enters, exile any number of other nonland permanents you control until Abdel Adrian leaves the battlefield. Create a 1/1 white Soldier creature token for each permanent exiled this way.\nChoose a Background (You can have a Background as a second commander.)
|
||||
|
||||
@@ -4,6 +4,6 @@ Types:Instant
|
||||
K:Devoid
|
||||
A:SP$ Counter | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | UnlessCost$ 1 | SubAbility$ DBToken | SpellDescription$ Counter target spell unless its controller pays {1}.
|
||||
SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_1_1_eldrazi_scion_sac | TokenOwner$ You | SpellDescription$ You create a 1/1 colorless Eldrazi Scion creature token. It has "Sacrifice this creature: Add {C}." ({C} represents colorless mana.)
|
||||
DeckHints:Type$Eldrazi
|
||||
DeckHas:Ability$Mana.Colorless|Token
|
||||
DeckHints:Type$Eldrazi
|
||||
Oracle:Devoid (This card has no color.)\nCounter target spell unless its controller pays {1}. You create a 1/1 colorless Eldrazi Scion creature token. It has "Sacrifice this creature: Add {C}." ({C} represents colorless mana.)
|
||||
|
||||
@@ -5,6 +5,6 @@ PT:3/2
|
||||
K:Outlast:W
|
||||
S:Mode$ Continuous | Affected$ Creature.YouCtrl+counters_GE1_P1P1 | AddKeyword$ Lifelink | Description$ Each creature you control with a +1/+1 counter on it has lifelink.
|
||||
SVar:PlayMain1:TRUE
|
||||
DeckHints:Ability$Counters
|
||||
DeckHas:Ability$Counters
|
||||
DeckHints:Ability$Counters
|
||||
Oracle:Outlast {W} ({W}, {T}: Put a +1/+1 counter on this creature. Outlast only as a sorcery.)\nEach creature you control with a +1/+1 counter on it has lifelink.
|
||||
|
||||
@@ -5,6 +5,6 @@ PT:2/3
|
||||
K:Outlast:W
|
||||
S:Mode$ Continuous | Affected$ Creature.YouCtrl+counters_GE1_P1P1 | AddKeyword$ Flying | Description$ Each creature you control wth a +1/+1 counter on it has flying.
|
||||
SVar:PlayMain1:TRUE
|
||||
DeckHints:Ability$Counters
|
||||
DeckHas:Ability$Counters
|
||||
DeckHints:Ability$Counters
|
||||
Oracle:Outlast {W} ({W}, {T}: Put a +1/+1 counter on this creature. Outlast only as a sorcery.)\nEach creature you control with a +1/+1 counter on it has flying.
|
||||
|
||||
@@ -3,6 +3,6 @@ ManaCost:1 G
|
||||
Types:Legendary Enchantment Background
|
||||
S:Mode$ Continuous | Affected$ Creature.IsCommander+YouOwn | AddStaticAbility$ DragonReduce | Description$ Commander creatures you own have "The first Dragon spell you cast each turn costs {2} less to cast."
|
||||
SVar:DragonReduce:Mode$ ReduceCost | EffectZone$ Battlefield | ValidCard$ Card.Dragon | Activator$ You | Type$ Spell | OnlyFirstSpell$ True | Amount$ 2 | Description$ The first Dragon spell you cast each turn costs {2} less to cast.
|
||||
DeckNeeds:Type$Dragon
|
||||
AI:RemoveDeck:NonCommander
|
||||
DeckNeeds:Type$Dragon
|
||||
Oracle:Commander creatures you own have "The first Dragon spell you cast each turn costs {2} less to cast."
|
||||
|
||||
@@ -4,6 +4,6 @@ Types:Creature Human Soldier
|
||||
PT:2/1
|
||||
T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | IsPresent$ Planeswalker.Basri+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ At the beginning of combat on your turn, if you control a Basri planeswalker, put a +1/+1 counter on CARDNAME.
|
||||
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1
|
||||
DeckNeeds:Type$Basri
|
||||
DeckHas:Ability$Counters
|
||||
DeckNeeds:Type$Basri
|
||||
Oracle:At the beginning of combat on your turn, if you control a Basri planeswalker, put a +1/+1 counter on Adherent of Hope.
|
||||
|
||||
@@ -5,6 +5,6 @@ K:Devoid
|
||||
A:SP$ Tap | TargetMin$ 0 | TargetMax$ 2 | TgtPrompt$ Choose target creature | ValidTgts$ Creature | SubAbility$ TrigPump | SpellDescription$ Tap up to two target creatures.
|
||||
SVar:TrigPump:DB$ Pump | Defined$ Targeted | KW$ HIDDEN This card doesn't untap during your next untap step. | Duration$ Permanent | SubAbility$ DBToken | SpellDescription$ Those creatures don't untap during their controller's next untap step.
|
||||
SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_1_1_eldrazi_scion_sac | TokenOwner$ You | SpellDescription$ Create a 1/1 colorless Eldrazi Scion creature token. It has "Sacrifice this creature: Add {C}."
|
||||
DeckHints:Type$Eldrazi
|
||||
DeckHas:Ability$Mana.Colorless|Token
|
||||
DeckHints:Type$Eldrazi
|
||||
Oracle:Devoid (This card has no color.)\nTap up to two target creatures. Those creatures don't untap during their controller's next untap step. Create a 1/1 colorless Eldrazi Scion creature token. It has "Sacrifice this creature: Add {C}."
|
||||
|
||||
@@ -5,6 +5,6 @@ A:SP$ Dig | DigNum$ 5 | ChangeNum$ 1 | SubAbility$ Dig2 | ConditionCheckSVar$ X
|
||||
SVar:Dig2:DB$ Dig | DigNum$ 5 | ChangeNum$ 2 | ConditionCheckSVar$ X | ConditionSVarCompare$ GTY
|
||||
SVar:X:Count$Valid Creature.YouCtrl
|
||||
SVar:Y:PlayerCountOther$HighestValid Creature.YouCtrl
|
||||
DeckNeeds:Color$Blue
|
||||
AI:RemoveDeck:Random
|
||||
DeckNeeds:Color$Blue
|
||||
Oracle:({2/U} can be paid with any two mana or with {U}. This card's mana value is 6.)\nLook at the top five cards of your library. If you control more creatures than each other player, put two of those cards into your hand. Otherwise, put one of them into your hand. Then put the rest on the bottom of your library in any order.
|
||||
|
||||
@@ -5,6 +5,6 @@ PT:3/4
|
||||
K:Flying
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPut | TriggerDescription$ When CARDNAME enters, put a +1/+1 counter on another target Soldier you control.
|
||||
SVar:TrigPut:DB$ PutCounter | ValidTgts$ Soldier.Other+YouCtrl | TgtPrompt$ Select another target Soldier you control | CounterType$ P1P1
|
||||
DeckHints:Type$Soldier
|
||||
DeckHas:Ability$Counters
|
||||
DeckHints:Type$Soldier
|
||||
Oracle:Flying\nWhen Aeronaut Cavalry enters, put a +1/+1 counter on another target Soldier you control.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Name:Aether Searcher
|
||||
ManaCost:7
|
||||
PT:6/4
|
||||
Types:Artifact Creature Construct
|
||||
PT:6/4
|
||||
Draft:Reveal CARDNAME as you draft it.
|
||||
Draft:Reveal the next card you draft and note its name.
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigSearchHand | TriggerDescription$ When CARDNAME enters, you may search your hand and/or library for a card with a name noted as you drafted cards named Aether Searcher. You may cast it without paying its mana cost. If you searched your library this way, shuffle.
|
||||
|
||||
@@ -7,6 +7,6 @@ K:Trample
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self+bargained | Execute$ TrigKicker | TriggerDescription$ When CARDNAME enters, if it was bargained, it fights up to one target creature you don't control. (Each deals damage equal to its power to the other.)
|
||||
SVar:TrigKicker:DB$ Fight | Defined$ TriggeredCardLKICopy | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select up to one target creature an opponent controls | TargetMin$ 0 | TargetMax$ 1
|
||||
SVar:PlayMain1:TRUE
|
||||
DeckHints:Type$Artifact|Enchantment & Ability$Token
|
||||
DeckHas:Ability$Sacrifice
|
||||
DeckHints:Type$Artifact|Enchantment & Ability$Token
|
||||
Oracle:Bargain (You may sacrifice an artifact, enchantment, or token as you cast this spell.)\nTrample\nWhen Agatha's Champion enters, if it was bargained, it fights up to one target creature you don't control. (Each deals damage equal to its power to the other.)
|
||||
|
||||
@@ -4,6 +4,6 @@ Types:Legendary Enchantment Background
|
||||
S:Mode$ Continuous | Affected$ Creature.IsCommander+YouOwn | AddTrigger$ Dies | Description$ Commander creatures you own have "Whenever an artifact or creature you control is put into a graveyard from the battlefield, each opponent loses 1 life."
|
||||
SVar:Dies:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Artifact.YouCtrl,Creature.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigDrain | TriggerDescription$ Whenever an artifact or creature you control is put into a graveyard from the battlefield, each opponent loses 1 life.
|
||||
SVar:TrigDrain:DB$ LoseLife | Defined$ Opponent | LifeAmount$ 1
|
||||
DeckHints:Type$Artifact & Ability$Sacrifice
|
||||
AI:RemoveDeck:NonCommander
|
||||
DeckHints:Type$Artifact & Ability$Sacrifice
|
||||
Oracle:Commander creatures you own have "Whenever an artifact or creature you control is put into a graveyard from the battlefield, each opponent loses 1 life."
|
||||
|
||||
@@ -5,6 +5,6 @@ S:Mode$ Continuous | Affected$ Creature.IsCommander+YouOwn | AddTrigger$ Attacks
|
||||
SVar:AttacksPlayer:Mode$ Attacks | ValidCard$ Card.Self | Attacked$ Player | Condition$ NoOpponentHasMoreLifeThanAttacked | Execute$ TrigPutCounter | TriggerDescription$ Whenever this creature attacks a player, if no opponent has more life than that player, put a +1/+1 counter on this creature. It gains deathtouch and indestructible until end of turn.
|
||||
SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 1 | Defined$ Self | SubAbility$ DBPump
|
||||
SVar:DBPump:DB$ Pump | Defined$ Self | KW$ Deathtouch & Indestructible
|
||||
DeckHas:Ability$Counters
|
||||
AI:RemoveDeck:NonCommander
|
||||
DeckHas:Ability$Counters
|
||||
Oracle:Commander creatures you own have "Whenever this creature attacks a player, if no opponent has more life than that player, put a +1/+1 counter on this creature. It gains deathtouch and indestructible until end of turn."
|
||||
|
||||
@@ -5,6 +5,6 @@ PT:2/1
|
||||
K:Outlast:1 W
|
||||
S:Mode$ Continuous | Affected$ Creature.YouCtrl+counters_GE1_P1P1 | AddKeyword$ First Strike | Description$ Each creature you control with a +1/+1 counter on it has first strike.
|
||||
SVar:PlayMain1:TRUE
|
||||
DeckHints:Ability$Counters
|
||||
DeckHas:Ability$Counters
|
||||
DeckHints:Ability$Counters
|
||||
Oracle:Outlast {1}{W} ({1}{W}, {T}: Put a +1/+1 counter on this creature. Outlast only as a sorcery.)\nEach creature you control with a +1/+1 counter on it has first strike.
|
||||
|
||||
@@ -6,6 +6,6 @@ SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | OriginAlternative$ Graveyard
|
||||
A:AB$ ChooseCard | Cost$ Sac<1/CARDNAME> | Choices$ Creature | Mandatory$ True | AILogic$ NeedsPrevention | SubAbility$ DBEffect | SpellDescription$ Prevent all combat damage a creature of your choice would deal this turn.
|
||||
SVar:DBEffect:DB$ Effect | ReplacementEffects$ RPreventNextFromSource | RememberObjects$ ChosenCard | ExileOnMoved$ Battlefield
|
||||
SVar:RPreventNextFromSource:Event$ DamageDone | IsCombat$ True | ValidSource$ Card.IsRemembered | Prevent$ True | Description$ Prevent all combat damage a creature of your choice would deal this turn.
|
||||
DeckHints:Name$Ajani, Valiant Protector
|
||||
DeckHas:Ability$Sacrifice
|
||||
DeckHints:Name$Ajani, Valiant Protector
|
||||
Oracle:When Ajani's Aid enters, you may search your library and/or graveyard for a card named Ajani, Valiant Protector, reveal it, and put it into your hand. If you search your library this way, shuffle.\nSacrifice Ajani's Aid: Prevent all combat damage a creature of your choice would deal this turn.
|
||||
|
||||
@@ -4,6 +4,6 @@ Types:Creature Cat Soldier
|
||||
PT:2/2
|
||||
T:Mode$ LifeGained | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever you gain life, put a +1/+1 counter on CARDNAME.
|
||||
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1
|
||||
DeckHints:Ability$LifeGain
|
||||
DeckHas:Ability$Counters
|
||||
DeckHints:Ability$LifeGain
|
||||
Oracle:Whenever you gain life, put a +1/+1 counter on Ajani's Pridemate.
|
||||
|
||||
@@ -5,6 +5,6 @@ PT:1/5
|
||||
T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ GE1 | Execute$ TrigDig | TriggerDescription$ At the beginning of each player's end step, if an artifact entered the battlefield under your control this turn, look at the top two cards of your library. Put one of them into your hand and the other into your graveyard.
|
||||
SVar:TrigDig:DB$ Dig | DigNum$ 2 | ChangeNum$ 1 | DestinationZone2$ Graveyard | NoReveal$ True
|
||||
SVar:X:Count$ThisTurnEntered_Battlefield_Artifact.YouCtrl
|
||||
DeckNeeds:Type$Artifact
|
||||
DeckHas:Ability$Graveyard
|
||||
DeckNeeds:Type$Artifact
|
||||
Oracle:At the beginning of each player's end step, if an artifact entered the battlefield under your control this turn, look at the top two cards of your library. Put one of them into your hand and the other into your graveyard.
|
||||
|
||||
@@ -7,7 +7,7 @@ SVar:TrigToken:DB$ Token | TokenScript$ u_2_2_drake_flying
|
||||
T:Mode$ Drawn | ValidCard$ Card.YouCtrl | Number$ 5 | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever you draw your fifth card each turn, CARDNAME and Drakes you control get +X/+X until end of turn, where X is the number of cards in your hand.
|
||||
SVar:TrigPump:DB$ PumpAll | ValidCards$ Card.Self,Drake.YouCtrl | NumAtt$ X | NumDef$ X
|
||||
SVar:X:Count$InYourHand
|
||||
AI:RemoveDeck:Random
|
||||
DeckHas:Ability$Token & Type$Drake
|
||||
DeckHints:Type$Drake
|
||||
AI:RemoveDeck:Random
|
||||
Oracle:Whenever you draw your second card each turn, create a 2/2 blue Drake creature token with flying.\nWhenever you draw your fifth card each turn, Alandra, Sky Dreamer and Drakes you control get +X/+X until end of turn, where X is the number of cards in your hand.
|
||||
|
||||
@@ -7,9 +7,9 @@ K:Trample
|
||||
K:Ward:1
|
||||
T:Mode$ Sacrificed | ValidPlayer$ You | ValidCard$ Card.token | TriggerZones$ Battlefield,Exile | Execute$ TrigPump | TriggerDescription$ Whenever you sacrifice a token, NICKNAME perpetually gets +1/+1. This ability also triggers if NICKNAME is in exile.
|
||||
SVar:TrigPump:DB$ Pump | PumpZone$ Battlefield,Exile | NumAtt$ 1 | NumDef$ 1 | Duration$ Perpetual
|
||||
AlternateMode:Adventure
|
||||
DeckHas:Ability$Discard|Token & Type$Food
|
||||
DeckHints:Ability$Token & Type$Treasure|Food|Clue
|
||||
AlternateMode:Adventure
|
||||
Oracle:Flying, Trample, Ward {1}\nWhenever you sacrifice a token, Albiorix perpetually gets +1/+1. This ability also triggers if Albiorix is in exile.
|
||||
|
||||
ALTERNATE
|
||||
|
||||
@@ -7,7 +7,7 @@ SVar:TrigToken:DB$ Token | TokenScript$ w_1_1_soldier
|
||||
T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPumpAll | TriggerDescription$ Whenever NICKNAME attacks, you may pay {8}. If you do, creatures you control get +X/+X until end of turn, where X is the number of historic permanents you control.
|
||||
SVar:TrigPumpAll:AB$ PumpAll | Cost$ 8 | ValidCards$ Creature.YouCtrl | NumAtt$ X | NumDef$ X
|
||||
SVar:X:Count$Valid Permanent.YouCtrl+Historic
|
||||
SVar:HasAttackEffect:TRUE
|
||||
DeckHas:Ability$Token
|
||||
DeckHints:Type$Artifact|Legendary|Saga
|
||||
SVar:HasAttackEffect:TRUE
|
||||
Oracle:Whenever you cast a historic spell, create a 1/1 white Soldier creature token. (Artifacts, legendaries, and Sagas are historic.)\nWhenever Alistair attacks, you may pay {8}. If you do, creatures you control get +X/+X until end of turn, where X is the number of historic permanents you control.
|
||||
|
||||
@@ -8,6 +8,6 @@ SVar:TrigInvestigate:DB$ Investigate
|
||||
A:AB$ Draw | Cost$ X W U U T Sac<1/Clue> | NumCards$ X | SubAbility$ DBGainLife | SpellDescription$ You draw X cards and gain X life.
|
||||
SVar:DBGainLife:DB$ GainLife | LifeAmount$ X
|
||||
SVar:X:Count$xPaid
|
||||
DeckHints:Ability$Investigate
|
||||
DeckHas:Ability$Investigate|Token|Sacrifice|LifeGain & Type$Artifact|Clue
|
||||
DeckHints:Ability$Investigate
|
||||
Oracle:Vigilance\nWhen Alquist Proft, Master Sleuth enters, investigate. (Create a Clue token. It's an artifact with "{2}, Sacrifice this artifact: Draw a card.")\n{X}{W}{U}{U}, {T}, Sacrifice a Clue: You draw X cards and gain X life.
|
||||
|
||||
@@ -9,8 +9,8 @@ SVar:Z:SVar$X/Plus.Y
|
||||
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigChooseCardType | TriggerDescription$ At the beginning of your end step, choose a card type, then reveal the top two cards of your library. Put all cards of the chosen type revealed this way into your hand and the rest on the bottom of your library in any order.
|
||||
SVar:TrigChooseCardType:DB$ ChooseType | Defined$ You | Type$ Card | SubAbility$ DBDig
|
||||
SVar:DBDig:DB$ Dig | DigNum$ 2 | Reveal$ True | ChangeNum$ All | ChangeValid$ Card.ChosenType | DestinationZone2$ Library | LibraryPosition$ -1
|
||||
DeckHints:Keyword$Foretell
|
||||
AI:RemoveDeck:All
|
||||
DeckHints:Keyword$Foretell
|
||||
AlternateMode:Modal
|
||||
Oracle:Alrund gets +1/+1 for each card in your hand and each foretold card you own in exile.\nAt the beginning of your end step, choose a card type, then reveal the top two cards of your library. Put all cards of the chosen type revealed this way into your hand and the rest on the bottom of your library in any order.
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ SVar:Y:Sacrificed$CardPower
|
||||
K:Craft:2 B B XMin1 ExileCtrlOrGrave<X/Creature.Other>
|
||||
SVar:X:Count$xPaid
|
||||
A:AB$ ChangeZone | Cost$ 2 B | Origin$ Graveyard | Destination$ Hand | ActivationZone$ Graveyard | SpellDescription$ Return CARDNAME from your graveyard to your hand.
|
||||
DeckHints:Ability$Discard|Mill|Sacrifice
|
||||
DeckHas:Ability$Graveyard|Sacrifice|Mill
|
||||
AI:RemoveDeck:All
|
||||
DeckHas:Ability$Graveyard|Sacrifice|Mill
|
||||
DeckHints:Ability$Discard|Mill|Sacrifice
|
||||
AlternateMode:DoubleFaced
|
||||
Oracle:When Altar of the Wretched enters, you may sacrifice a nontoken creature. If you do, draw X cards, then mill X cards, where X is that creature's power.\nCraft with one or more creatures {2}{B}{B}\n{2}{B}: Return Altar of the Wretched from your graveyard to your hand.
|
||||
|
||||
|
||||
@@ -10,6 +10,6 @@ SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ bg_1_1_insect | Remembe
|
||||
SVar:DBPutCounter:DB$ PutCounter | Defined$ Remembered | CounterNum$ X | CounterType$ P1P1 | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
SVar:X:TriggerObjectsCards$GreatestCMC
|
||||
DeckHints:Type$Insect & Ability$Graveyard
|
||||
DeckHas:Ability$Token & Ability$Graveyard
|
||||
DeckHints:Type$Insect & Ability$Graveyard
|
||||
Oracle:Flying, menace\nOther Insects you control have menace.\nWhenever one or more cards leave your graveyard, you may create a 1/1 black and green Insect creature token, then put a number of +1/+1 counters on it equal to the greatest mana value among those cards. Do this only once each turn.
|
||||
|
||||
@@ -11,6 +11,6 @@ SVar:VolverPumped:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNu
|
||||
SVar:VolverResilience:DB$ Animate | Defined$ Self | Abilities$ ABRegen | Duration$ Permanent
|
||||
SVar:ABRegen:AB$ Regenerate | Cost$ PayLife<3> | SpellDescription$ Regenerate CARDNAME.
|
||||
AI:RemoveDeck:Random
|
||||
DeckNeeds:Color$Blue|Black
|
||||
DeckHas:Ability$Counters
|
||||
DeckNeeds:Color$Blue|Black
|
||||
Oracle:Kicker {1}{U} and/or {B} (You may pay an additional {1}{U} and/or {B} as you cast this spell.)\nIf Anavolver was kicked with its {1}{U} kicker, it enters with two +1/+1 counters on it and with flying.\nIf Anavolver was kicked with its {B} kicker, it enters with a +1/+1 counter on it and with "Pay 3 life: Regenerate Anavolver."
|
||||
|
||||
@@ -7,6 +7,6 @@ SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
SVar:X:Count$Compare Y LTZ.2.0
|
||||
SVar:Y:Remembered$CardManaCost
|
||||
SVar:Z:Sacrificed$CardManaCost
|
||||
DeckNeeds:Type$Artifact|Creature|Equipment|Vehicle
|
||||
AI:RemoveDeck:Random
|
||||
DeckNeeds:Type$Artifact|Creature|Equipment|Vehicle
|
||||
Oracle:As an additional cost to cast this spell, sacrifice an artifact or creature.\nSearch your library for an Equipment or Vehicle card, put that card onto the battlefield, then shuffle. If it has mana value less than the sacrificed permanent's mana value, scry 2.
|
||||
|
||||
@@ -6,6 +6,6 @@ K:Flying
|
||||
T:Mode$ DamageDealtOnce | ValidSource$ Card.Self | Execute$ TrigChange | Delirium$ True | TriggerZones$ Battlefield | TriggerDescription$ Delirium — Whenever CARDNAME deals damage, if there are four or more card types among cards in your graveyard, exile target creature an opponent controls.
|
||||
SVar:TrigChange:DB$ ChangeZone | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | Origin$ Battlefield | Destination$ Exile
|
||||
SVar:HasCombatEffect:TRUE
|
||||
DeckHints:Ability$Graveyard|Discard
|
||||
DeckHas:Ability$Delirium
|
||||
DeckHints:Ability$Graveyard|Discard
|
||||
Oracle:Flying\nDelirium — Whenever Angel of Deliverance deals damage, if there are four or more card types among cards in your graveyard, exile target creature an opponent controls.
|
||||
|
||||
@@ -7,6 +7,6 @@ K:Vigilance
|
||||
K:Lifelink
|
||||
K:Fabricate:2
|
||||
S:Mode$ Continuous | Affected$ Creature.Other+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Other creatures you control get +1/+1.
|
||||
DeckHas:Ability$Counters|Token
|
||||
SVar:PlayMain1:TRUE
|
||||
DeckHas:Ability$Counters|Token
|
||||
Oracle:Flying, vigilance, lifelink\nFabricate 2 (When this creature enters, put two +1/+1 counters on it or create two 1/1 colorless Servo artifact creature tokens.)\nOther creatures you control get +1/+1.
|
||||
|
||||
@@ -9,7 +9,7 @@ T:Mode$ SpellCast | ValidCard$ Card.Party | ValidActivatingPlayer$ You | Execute
|
||||
SVar:TrigChoose:DB$ ChooseCard | ChoiceZone$ Hand | Choices$ Creature.Party+YouOwn | ChoiceTitle$ Choose a party creature card in your hand | Amount$ 1 | SubAbility$ DBPump
|
||||
SVar:DBPump:DB$ Pump | Defined$ ChosenCard | PumpZone$ Hand | NumAtt$ 1 | NumDef$ 1 | Duration$ Perpetual | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True
|
||||
DeckHas:Ability$Party|LifeGain
|
||||
SVar:BuffedBy:Cleric,Rogue,Warrior,Wizard
|
||||
DeckHas:Ability$Party|LifeGain
|
||||
DeckHints:Type$Rogue|Warrior|Wizard
|
||||
Oracle:Flying, lifelink\nWhenever Angel of Unity enters or you cast a party spell, choose a party creature card in your hand. It perpetually gets +1/+1. (A party card or spell is a Cleric, Rogue, Warrior, or Wizard.)
|
||||
|
||||
@@ -3,6 +3,6 @@ ManaCost:3 W
|
||||
Types:Instant
|
||||
S:Mode$ AlternativeCost | ValidSA$ Spell.Self | EffectZone$ All | Cost$ tapXType<1/Creature> | IsPresent$ Plains.YouCtrl | Description$ If you control a Plains, you may tap an untapped creature you control rather than pay this spell's mana cost.
|
||||
A:SP$ Token | TokenScript$ w_4_4_angel_flying | AtEOT$ Exile | ActivationPhases$ BeginCombat->EndCombat | StackDescription$ {p:You} creates a 4/4 white Angel creature token with flying. Exile it at the beginning of the next end step. | SpellDescription$ Cast this spell only during combat. Create a 4/4 white Angel creature token with flying. Exile it at the beginning of the next end step.
|
||||
DeckHas:Ability$Token
|
||||
AI:RemoveDeck:All
|
||||
DeckHas:Ability$Token
|
||||
Oracle:If you control a Plains, you may tap an untapped creature you control rather than pay this spell's mana cost.\nCast this spell only during combat.\nCreate a 4/4 white Angel creature token with flying. Exile it at the beginning of the next end step.
|
||||
|
||||
@@ -5,6 +5,6 @@ PT:2/3
|
||||
K:Flying
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Permanent.YouCtrl+Other+HasCounters | TriggerZones$ Battlefield | Execute$ TrigInvestigate | TriggerDescription$ Whenever another permanent you control leaves the battlefield, if it had counters on it, investigate.
|
||||
SVar:TrigInvestigate:DB$ Investigate
|
||||
DeckHints:Ability$Counters
|
||||
DeckHas:Ability$Investigate|Token|Sacrifice & Type$Artifact|Clue
|
||||
DeckHints:Ability$Counters
|
||||
Oracle:Flying\nWhenever another permanent you control leaves the battlefield, if it had counters on it, investigate.
|
||||
|
||||
@@ -9,8 +9,8 @@ T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigExile | Secondary$ True |
|
||||
SVar:TrigExile:DB$ ChangeZone | ValidTgts$ Enchantment.nonAura+YouCtrl | Origin$ Graveyard | TargetMin$ 0 | TargetMax$ 1 | Destination$ Exile | TgtPrompt$ Select up to one target non-Aura enchantment card from your graveyard | RememberChanged$ True | SubAbility$ DBCopy
|
||||
SVar:DBCopy:DB$ CopyPermanent | Defined$ Remembered | SetPower$ 3 | SetToughness$ 3 | AddTypes$ Creature & Zombie | SetColor$ Black | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
DeckHas:Ability$Token|Graveyard
|
||||
DeckNeeds:Type$Enchantment
|
||||
DeckHints:Ability$Graveyard|Mill
|
||||
SVar:HasAttackEffect:TRUE
|
||||
DeckHas:Ability$Token|Graveyard
|
||||
DeckHints:Ability$Graveyard|Mill
|
||||
DeckNeeds:Type$Enchantment
|
||||
Oracle:Menace\nOther enchantment creatures you control have menace.\nWhenever Anikthea enters or attacks, exile up to one target non-Aura enchantment card from your graveyard. Create a token that's a copy of that card, except it's a 3/3 black Zombie creature in addition to its other types.
|
||||
|
||||
@@ -4,6 +4,6 @@ Types:Artifact
|
||||
T:Mode$ CounterAddedOnce | ValidCard$ Permanent.YouCtrl | TriggerZones$ Battlefield | CounterType$ P1P1 | Execute$ TrigToken | TriggerDescription$ Whenever one or more +1/+1 counters are put on a permanent you control, you may pay {1}. If you do, create a 1/1 colorless Servo artifact creature token.
|
||||
SVar:TrigToken:AB$ Token | Cost$ 1 | TokenAmount$ 1 | TokenScript$ c_1_1_a_servo | TokenOwner$ You
|
||||
A:AB$ PutCounter | Cost$ 3 T | ValidTgts$ Permanent,Player | TgtPrompt$ Select target player or permanent | CounterType$ ExistingCounter | CounterNum$ 1 | AILogic$ AtOppEOT | SpellDescription$ Choose a counter on target permanent or player. Give that permanent or player another counter of that kind.
|
||||
DeckHints:Ability$Counters
|
||||
AI:RemoveDeck:All
|
||||
DeckHints:Ability$Counters
|
||||
Oracle:Whenever one or more +1/+1 counters are put on a permanent you control, you may pay {1}. If you do, create a 1/1 colorless Servo artifact creature token.\n{3}, {T}: Choose a counter on target permanent or player. Give that permanent or player another counter of that kind.
|
||||
|
||||
@@ -3,6 +3,6 @@ ManaCost:1 B
|
||||
Types:Instant
|
||||
A:SP$ ChangeZone | Defined$ Targeted | ValidTgts$ Creature | ConditionCheckSVar$ X | ConditionSVarCompare$ GE3 | Origin$ Battlefield | Destination$ Exile | SubAbility$ NotPoisoned | SpellDescription$ Exile target creature if it has mana value 3 or less. Corrupted — Exile that creature instead if its controller has three or more poison counters.
|
||||
SVar:NotPoisoned:DB$ ChangeZone | Defined$ Targeted | Origin$ Battlefield | Destination$ Exile | ConditionDefined$ Targeted | ConditionPresent$ Creature.cmcLE3
|
||||
DeckHints:Ability$Proliferate & Keyword$Infect|Toxic
|
||||
SVar:X:TargetedController$Counters.Poison
|
||||
DeckHints:Ability$Proliferate & Keyword$Infect|Toxic
|
||||
Oracle:Exile target creature if it has mana value 3 or less.\nCorrupted — Exile that creature instead if its controller has three or more poison counters.
|
||||
|
||||
@@ -8,6 +8,6 @@ SVar:TrigMill:DB$ Mill | Defined$ TriggeredTarget | NumCards$ X | RememberMilled
|
||||
SVar:DBDraw:DB$ Draw | Defined$ You | ConditionDefined$ Remembered | ConditionPresent$ Creature | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
SVar:X:TriggerCount$DamageAmount
|
||||
DeckNeeds:Type$Rogue
|
||||
DeckHas:Ability$Mill
|
||||
DeckNeeds:Type$Rogue
|
||||
Oracle:Other Rogues you control get +1/+1.\nWhenever one or more Rogues you control deal combat damage to a player, that player mills a card for each 1 damage dealt to them. If the player mills at least one creature card this way, you draw a card. (To mill a card, a player puts the top card of their library into their graveyard.)
|
||||
|
||||
@@ -4,7 +4,7 @@ Types:Creature Spider Mutant
|
||||
PT:0/0
|
||||
K:Graft:2
|
||||
A:AB$ Pump | Cost$ G | ValidTgts$ Creature.counters_GE1_P1P1 | TgtPrompt$ Select target creature with a +1/+1 counter | KW$ Reach | SpellDescription$ Target creature with a +1/+1 counter on it gains reach until end of turn. (It can block creatures with flying.)
|
||||
DeckNeeds:Ability$Counters
|
||||
DeckHas:Ability$Counters
|
||||
SVar:AIGraftPreference:DontMoveCounterIfLethal
|
||||
DeckHas:Ability$Counters
|
||||
DeckNeeds:Ability$Counters
|
||||
Oracle:Graft 2 (This creature enters with two +1/+1 counters on it. Whenever another creature enters, you may move a +1/+1 counter from this creature onto it.)\n{G}: Target creature with a +1/+1 counter on it gains reach until end of turn. (It can block creatures with flying.)
|
||||
|
||||
@@ -4,8 +4,8 @@ Types:Creature Elemental
|
||||
PT:1/3
|
||||
T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | ActivatorThisTurnCast$ EQ1 | Execute$ TrigPump | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast your first instant or sorcery spell each turn, CARDNAME gets +2/+0 until end of turn.
|
||||
SVar:TrigPump:DB$ Pump | Defined$ Self | NumAtt$ 2
|
||||
DeckHints:Type$Instant|Sorcery
|
||||
DeckHas:Ability$Graveyard
|
||||
DeckHints:Type$Instant|Sorcery
|
||||
AlternateMode:Adventure
|
||||
Oracle:Whenever you cast your first instant or sorcery spell each turn, Aquatic Alchemist gets +2/+0 until end of turn.
|
||||
|
||||
|
||||
@@ -8,6 +8,6 @@ T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPutCounterAll | Secondary$
|
||||
SVar:TrigPutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl+StrictlyOther | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBGainLife
|
||||
SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ X
|
||||
SVar:X:Count$Valid Creature.YouCtrl+StrictlyOther
|
||||
DeckHas:Ability$Counters|LifeGain
|
||||
SVar:HasAttackEffect:TRUE
|
||||
DeckHas:Ability$Counters|LifeGain
|
||||
Oracle:Vigilance\nWhenever Aragorn and Arwen, Wed enters or attacks, put a +1/+1 counter on each other creature you control. You gain 1 life for each other creature you control.
|
||||
|
||||
@@ -3,6 +3,6 @@ ManaCost:U R
|
||||
Types:Instant
|
||||
A:SP$ Dig | DigNum$ 4 | ChangeNum$ 1 | Optional$ True | ForceRevealToController$ True | ChangeValid$ Card.Instant,Card.Sorcery | RestRandomOrder$ True | StackDescription$ SpellDescription | SpellDescription$ Look at the top four cards of your library. You may reveal an instant or sorcery card from among them and put it into your hand. Put the rest on the bottom of your library in a random order.
|
||||
K:Flashback:3 U R
|
||||
DeckNeeds:Type$Instant|Sorcery
|
||||
DeckHas:Ability$Graveyard
|
||||
DeckNeeds:Type$Instant|Sorcery
|
||||
Oracle:Look at the top four cards of your library. You may reveal an instant or sorcery card from among them and put it into your hand. Put the rest on the bottom of your library in a random order.\nFlashback {3}{U}{R} (You may cast this card from your graveyard for its flashback cost. Then exile it.)
|
||||
|
||||
@@ -2,6 +2,6 @@ Name:Arcane Melee
|
||||
ManaCost:4 U
|
||||
Types:Enchantment
|
||||
S:Mode$ ReduceCost | ValidCard$ Instant,Sorcery | Type$ Spell | Amount$ 2 | Description$ Instant and sorcery spells cost {2} less to cast.
|
||||
DeckNeeds:Type$Instant|Sorcery
|
||||
AI:RemoveDeck:Random
|
||||
DeckNeeds:Type$Instant|Sorcery
|
||||
Oracle:Instant and sorcery spells cost {2} less to cast.
|
||||
|
||||
@@ -7,6 +7,6 @@ T:Mode$ ChangesZone | ValidCard$ Card.Self+wasCastByYou | Destination$ Battlefie
|
||||
SVar:TrigExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Select target instant or sorcery card with mana value less than or equal to CARDNAME's power | ValidTgts$ Instant.YouOwn+cmcLEX,Sorcery.YouOwn+cmcLEX | RememberChanged$ True | SubAbility$ DBPlay
|
||||
SVar:DBPlay:DB$ Play | Valid$ Card.IsRemembered | ValidZone$ Exile | Controller$ You | CopyCard$ True | WithoutManaCost$ True | ValidSA$ Spell | Optional$ True | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
DeckHints:Type$Instant|Sorcery & Color$Blue
|
||||
SVar:X:Count$CardPower
|
||||
DeckHints:Type$Instant|Sorcery & Color$Blue
|
||||
Oracle:Prototype {1}{U}{U} — 2/1 (You may cast this spell with different mana cost, color, and size. It keeps its abilities and types.)\nWhen Arcane Proxy enters, if you cast it, exile target instant or sorcery card with mana value less than or equal to Arcane Proxy's power from your graveyard. Copy that card. You may cast the copy without paying its mana cost.
|
||||
|
||||
@@ -6,6 +6,6 @@ K:First Strike
|
||||
T:Mode$ ChangesZone | ValidCard$ Card.Self | Destination$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ When CARDNAME enters, put a +1/+1 counter on each other artifact creature you control.
|
||||
SVar:TrigPutCounter:DB$ PutCounterAll | ValidCards$ Creature.Artifact+StrictlyOther+YouCtrl | CounterType$ P1P1 | CounterNum$ 1
|
||||
K:Modular:2
|
||||
DeckHas:Ability$Counters
|
||||
SVar:PlayMain1:TRUE
|
||||
DeckHas:Ability$Counters
|
||||
Oracle:First strike\nWhen Arcbound Shikari enters, put a +1/+1 counter on each other artifact creature you control.\nModular 2 (This creature enters with two +1/+1 counters on it. When it dies, you may put its +1/+1 counters on target artifact creature.)
|
||||
|
||||
@@ -3,7 +3,7 @@ ManaCost:6
|
||||
Types:Artifact Creature Golem
|
||||
PT:0/0
|
||||
K:Modular:Sunburst
|
||||
AI:RemoveDeck:Random
|
||||
DeckHas:Ability$Counters
|
||||
DeckHints:Ability$Proliferate
|
||||
AI:RemoveDeck:Random
|
||||
Oracle:Modular—Sunburst (This creature enters with a +1/+1 counter on it for each color of mana spent to cast it. When it dies, you may put its +1/+1 counters on target artifact creature.)
|
||||
|
||||
@@ -9,6 +9,6 @@ SVar:TrigRemoveCtr:DB$ RemoveCounter | Defined$ Self | CounterType$ OIL | Counte
|
||||
SVar:LoseGame:DB$ LosesGame | Defined$ You | ConditionDefined$ Self | ConditionPresent$ Card.counters_EQ0_OIL
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.OppCtrl | TriggerZones$ Battlefield | Execute$ TrigLoseLife | TriggerDescription$ Whenever a creature an opponent controls dies, its controller loses 2 life.
|
||||
SVar:TrigLoseLife:DB$ LoseLife | LifeAmount$ 2 | Defined$ TriggeredCardController
|
||||
DeckHas:Ability$Counters
|
||||
AI:RemoveDeck:Random
|
||||
DeckHas:Ability$Counters
|
||||
Oracle:Flying\nArchfiend of the Dross enters with four oil counters on it.\nAt the beginning of your upkeep, remove an oil counter from Archfiend of the Dross. Then if it has no oil counters on it, you lose the game.\nWhenever a creature an opponent controls dies, its controller loses 2 life.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user