Merge branch '1842-panharmonicon-trigger-lingering' into 'master'

Resolve "Panharmonicon trigger lingering"

Closes #1842

See merge request core-developers/forge!4708
This commit is contained in:
Michael Kamensky
2021-06-12 12:57:51 +00:00
30 changed files with 164 additions and 121 deletions

View File

@@ -1880,7 +1880,7 @@ public class ComputerUtilMana {
final Card offering = sa.getSacrificedAsOffering();
offering.setUsedToPay(false);
if (costIsPaid && !test) {
sa.getHostCard().getGame().getAction().sacrifice(offering, sa, null);
sa.getHostCard().getGame().getAction().sacrifice(offering, sa, null, null);
}
sa.resetSacrificedAsOffering();
}
@@ -1888,7 +1888,7 @@ public class ComputerUtilMana {
final Card emerge = sa.getSacrificedAsEmerge();
emerge.setUsedToPay(false);
if (costIsPaid && !test) {
sa.getHostCard().getGame().getAction().sacrifice(emerge, sa, null);
sa.getHostCard().getGame().getAction().sacrifice(emerge, sa, null, null);
}
sa.resetSacrificedAsEmerge();
}

View File

@@ -24,7 +24,7 @@ import forge.game.zone.ZoneType;
public class DestroyAi extends SpellAbilityAi {
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
return checkApiLogic(ai, sa);
}
@Override

View File

@@ -488,7 +488,32 @@ public class PumpAi extends PumpAiBase {
// each player sacrifices one permanent, e.g. Vaevictis, Asmadi the Dire - grab the worst for allied and
// the best for opponents
return SacrificeAi.doSacOneEachLogic(ai, sa);
} else if (sa.getParam("AILogic").equals("Destroy")) {
List<Card> tgts = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
if (tgts.isEmpty()) {
return false;
}
List<Card> alliedTgts = CardLists.filter(tgts, Predicates.or(CardPredicates.isControlledByAnyOf(ai.getAllies()), CardPredicates.isController(ai)));
List<Card> oppTgts = CardLists.filter(tgts, CardPredicates.isControlledByAnyOf(ai.getRegisteredOpponents()));
Card destroyTgt = null;
if (!oppTgts.isEmpty()) {
destroyTgt = ComputerUtilCard.getBestAI(oppTgts);
} else {
// TODO: somehow limit this so that the AI doesn't always destroy own stuff when able?
destroyTgt = ComputerUtilCard.getWorstAI(alliedTgts);
}
if (destroyTgt != null) {
sa.resetTargets();
sa.getTargets().add(destroyTgt);
return true;
}
return false;
}
if (isFight) {
return FightAi.canFightAi(ai, sa, attack, defense);
}

View File

@@ -197,6 +197,15 @@ public class Game {
}
}
public CardCollectionView copyLastStateBattlefield() {
CardCollection result = new CardCollection();
Map<Integer, Card> cachedMap = Maps.newHashMap();
for (final Player p : getPlayers()) {
result.addAll(p.getZone(ZoneType.Battlefield).getLKICopy(cachedMap));
}
return result;
}
public void updateLastStateForCard(Card c) {
if (c == null || c.getZone() == null) {
return;

View File

@@ -1278,7 +1278,7 @@ public class GameAction {
orderedNoRegCreats = true;
}
for (Card c : noRegCreats) {
sacrificeDestroy(c, null, table);
sacrificeDestroy(c, null, table, null);
}
}
if (desCreats != null) {
@@ -1290,7 +1290,7 @@ public class GameAction {
orderedDesCreats = true;
}
for (Card c : desCreats) {
destroy(c, null, true, table);
destroy(c, null, true, table, null);
}
}
setHoldCheckingStaticAbilities(false);
@@ -1376,7 +1376,7 @@ public class GameAction {
return false;
}
if (!game.getStack().hasSourceOnStack(c, SpellAbilityPredicates.isChapter())) {
sacrifice(c, null, table);
sacrifice(c, null, table, null);
checkAgain = true;
}
return checkAgain;
@@ -1404,7 +1404,7 @@ public class GameAction {
// cleanup aura
if (c.isAura() && c.isInPlay() && !c.isEnchanting()) {
sacrificeDestroy(c, null, table);
sacrificeDestroy(c, null, table, null);
checkAgain = true;
}
return checkAgain;
@@ -1556,7 +1556,7 @@ public class GameAction {
for (Card c : list) {
if (c.getCounters(CounterEnumType.LOYALTY) <= 0) {
sacrificeDestroy(c, null, table);
sacrificeDestroy(c, null, table, null);
// Play the Destroy sound
game.fireEvent(new GameEventCardDestroyed());
recheck = true;
@@ -1619,7 +1619,7 @@ public class GameAction {
"You have multiple legendary permanents named \""+name+"\" in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)", null);
for (Card c: cc) {
if (c != toKeep) {
sacrificeDestroy(c, null, table);
sacrificeDestroy(c, null, table, null);
}
}
game.fireEvent(new GameEventCardDestroyed());
@@ -1653,28 +1653,24 @@ public class GameAction {
}
for (Card c : worlds) {
sacrificeDestroy(c, null, table);
sacrificeDestroy(c, null, table, null);
game.fireEvent(new GameEventCardDestroyed());
}
return true;
}
@Deprecated
public final Card sacrifice(final Card c, final SpellAbility source) {
return sacrifice(c, source, null);
}
public final Card sacrifice(final Card c, final SpellAbility source, CardZoneTable table) {
public final Card sacrifice(final Card c, final SpellAbility source, CardZoneTable table, Map<AbilityKey, Object> params) {
if (!c.canBeSacrificedBy(source)) {
return null;
}
c.getController().addSacrificedThisTurn(c, source);
return sacrificeDestroy(c, source, table);
return sacrificeDestroy(c, source, table, params);
}
public final boolean destroy(final Card c, final SpellAbility sa, final boolean regenerate, CardZoneTable table) {
public final boolean destroy(final Card c, final SpellAbility sa, final boolean regenerate, CardZoneTable table, Map<AbilityKey, Object> params) {
Player activator = null;
if (!c.canBeDestroyed()) {
return false;
@@ -1685,6 +1681,9 @@ public class GameAction {
repRunParams.put(AbilityKey.Source, sa);
repRunParams.put(AbilityKey.Affected, c);
repRunParams.put(AbilityKey.Regeneration, regenerate);
if (params != null) {
repRunParams.putAll(params);
}
if (game.getReplacementHandler().run(ReplacementType.Destroy, repRunParams) != ReplacementResult.NotReplaced) {
return false;
@@ -1701,11 +1700,14 @@ public class GameAction {
// Run triggers
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(c);
runParams.put(AbilityKey.Causer, activator);
if (params != null) {
runParams.putAll(params);
}
game.getTriggerHandler().runTrigger(TriggerType.Destroyed, runParams, false);
// in case the destroyed card has such a trigger
game.getTriggerHandler().registerActiveLTBTrigger(c);
final Card sacrificed = sacrificeDestroy(c, sa, table);
final Card sacrificed = sacrificeDestroy(c, sa, table, params);
return sacrificed != null;
}
@@ -1713,12 +1715,12 @@ public class GameAction {
* @return the sacrificed Card in its new location, or {@code null} if the
* sacrifice wasn't successful.
*/
protected final Card sacrificeDestroy(final Card c, SpellAbility cause, CardZoneTable table) {
protected final Card sacrificeDestroy(final Card c, SpellAbility cause, CardZoneTable table, Map<AbilityKey, Object> params) {
if (!c.isInPlay()) {
return null;
}
final Card newCard = moveToGraveyard(c, cause, null);
final Card newCard = moveToGraveyard(c, cause, params);
if (table != null && newCard != null && newCard.getZone() != null) {
table.put(ZoneType.Battlefield, newCard.getZone().getZoneType(), newCard);
}

View File

@@ -638,6 +638,9 @@ public final class GameActionUtil {
}
public static CardCollectionView orderCardsByTheirOwners(Game game, CardCollectionView list, ZoneType dest, SpellAbility sa) {
if (list.size() <= 1) {
return list;
}
CardCollection completeList = new CardCollection();
for (Player p : game.getPlayers()) {
CardCollection subList = new CardCollection();

View File

@@ -73,6 +73,7 @@ public enum AbilityKey {
IsCombatDamage("IsCombatDamage"),
IndividualCostPaymentInstance("IndividualCostPaymentInstance"),
IsMadness("IsMadness"),
LastStateBattlefield("LastStateBattlefield"),
LifeAmount("LifeAmount"), //TODO confirm that this and LifeGained can be merged
LifeGained("LifeGained"),
Mana("Mana"),

View File

@@ -173,15 +173,13 @@ public class AbilityUtils {
}
}
else if (defined.equals("Targeted") && sa instanceof SpellAbility) {
final SpellAbility saTargeting = ((SpellAbility)sa).getSATargetingCard();
if (saTargeting != null) {
Iterables.addAll(cards, saTargeting.getTargets().getTargetCards());
for (TargetChoices tc : ((SpellAbility)sa).getAllTargetChoices()) {
Iterables.addAll(cards, tc.getTargetCards());
}
}
else if (defined.equals("TargetedSource") && sa instanceof SpellAbility) {
final SpellAbility saTargeting = ((SpellAbility)sa).getSATargetingSA();
if (saTargeting != null) {
for (SpellAbility s : saTargeting.getTargets().getTargetSpells()) {
for (TargetChoices tc : ((SpellAbility)sa).getAllTargetChoices()) {
for (SpellAbility s : tc.getTargetSpells()) {
cards.add(s.getHostCard());
}
}
@@ -1008,9 +1006,8 @@ public class AbilityUtils {
players.addAll(getDefinedPlayers(card, "TargetedController", sa));
}
else if ((defined.equals("Targeted") || defined.equals("TargetedPlayer")) && sa instanceof SpellAbility) {
final SpellAbility saTargeting = ((SpellAbility)sa).getSATargetingPlayer();
if (saTargeting != null) {
players.addAll(saTargeting.getTargets().getTargetPlayers());
for (TargetChoices tc : ((SpellAbility)sa).getAllTargetChoices()) {
players.addAll(tc.getTargetPlayers());
}
}
else if (defined.equals("ParentTarget") && sa instanceof SpellAbility) {
@@ -1298,9 +1295,8 @@ public class AbilityUtils {
s = ((SpellAbility)sa).getRootAbility();
}
else if (defined.equals("Targeted") && sa instanceof SpellAbility) {
final SpellAbility saTargeting = ((SpellAbility)sa).getSATargetingSA();
if (saTargeting != null) {
for (SpellAbility targetSpell : saTargeting.getTargets().getTargetSpells()) {
for (TargetChoices tc : ((SpellAbility)sa).getAllTargetChoices()) {
for (SpellAbility targetSpell : tc.getTargetSpells()) {
SpellAbilityStackInstance stackInstance = game.getStack().getInstanceFromSpellAbility(targetSpell);
if (stackInstance != null) {
SpellAbility instanceSA = stackInstance.getSpellAbility(true);
@@ -1412,7 +1408,6 @@ public class AbilityUtils {
}
return;
}
AbilityUtils.resolveApiAbility(sa, game);
}

View File

@@ -7,6 +7,7 @@ import java.util.Map;
import com.google.common.collect.Maps;
import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
@@ -46,7 +47,8 @@ public class BalanceEffect extends SpellAbilityEffect {
validCards.add(CardLists.getValidCards(players.get(i).getCardsIn(zone), valid, activator, source, sa));
min = Math.min(min, validCards.get(i).size());
}
Map<AbilityKey, Object> params = AbilityKey.newMap();
CardZoneTable table = new CardZoneTable();
for (int i = 0; i < players.size(); i++) {
Player p = players.get(i);
@@ -59,7 +61,7 @@ public class BalanceEffect extends SpellAbilityEffect {
} else { // Battlefield
for (Card card : p.getController().choosePermanentsToSacrifice(sa, numToBalance, numToBalance, validCards.get(i), valid)) {
if ( null == card ) continue;
game.getAction().sacrifice(card, sa, table);
game.getAction().sacrifice(card, sa, table, params);
}
}
}

View File

@@ -12,6 +12,7 @@ import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardZoneTable;
import forge.game.replacement.ReplacementResult;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility;
@@ -113,6 +114,8 @@ public class CounterEffect extends SpellAbilityEffect {
}
}
Map<AbilityKey, Object> params = AbilityKey.newMap();
CardZoneTable table = new CardZoneTable();
for (final SpellAbility tgtSA : sas) {
final Card tgtSACard = tgtSA.getHostCard();
// should remember even that spell cannot be countered, e.g. Dovescape
@@ -137,7 +140,7 @@ public class CounterEffect extends SpellAbilityEffect {
// Destroy Permanent may be able to be turned into a SubAbility
if (tgtSA.isAbility() && sa.hasParam("DestroyPermanent")) {
game.getAction().destroy(tgtSACard, sa, true, null);
game.getAction().destroy(tgtSACard, sa, true, table, params);
}
if (sa.hasParam("RememberCountered")) {
@@ -152,6 +155,7 @@ public class CounterEffect extends SpellAbilityEffect {
}
}
}
table.triggerChangesZoneAll(game, sa);
} // end counterResolve
/**

View File

@@ -6,6 +6,7 @@ import com.google.common.collect.Maps;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
@@ -90,10 +91,12 @@ public class DestroyAllEffect extends SpellAbilityEffect {
}
CardZoneTable table = new CardZoneTable();
Map<AbilityKey, Object> params = AbilityKey.newMap();
params.put(AbilityKey.LastStateBattlefield, game.copyLastStateBattlefield());
Map<Integer, Card> cachedMap = Maps.newHashMap();
for (Card c : list) {
if (game.getAction().destroy(c, sa, !noRegen, table) && remDestroyed) {
if (game.getAction().destroy(c, sa, !noRegen, table, params) && remDestroyed) {
card.addRemembered(CardUtil.getLKICopy(c, cachedMap));
}
}

View File

@@ -8,9 +8,10 @@ import com.google.common.collect.Maps;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable;
import forge.game.spellability.SpellAbility;
@@ -72,13 +73,16 @@ public class DestroyEffect extends SpellAbilityEffect {
card.clearRemembered();
}
CardCollection tgtCards = getTargetCards(sa);
CardCollection untargetedCards = CardUtil.getRadiance(sa);
CardCollectionView tgtCards = getTargetCards(sa);
CardCollectionView untargetedCards = CardUtil.getRadiance(sa);
if (tgtCards.size() > 1) {
tgtCards = (CardCollection) GameActionUtil.orderCardsByTheirOwners(game, tgtCards, ZoneType.Graveyard, sa);
tgtCards = GameActionUtil.orderCardsByTheirOwners(game, tgtCards, ZoneType.Graveyard, sa);
}
Map<AbilityKey, Object> params = AbilityKey.newMap();
params.put(AbilityKey.LastStateBattlefield, game.copyLastStateBattlefield());
CardZoneTable table = new CardZoneTable();
Map<Integer, Card> cachedMap = Maps.newHashMap();
for (final Card tgtC : tgtCards) {
@@ -90,24 +94,24 @@ public class DestroyEffect extends SpellAbilityEffect {
if (gameCard == null || !tgtC.equalsWithTimestamp(gameCard)) {
continue;
}
internalDestroy(gameCard, sa, table, cachedMap);
internalDestroy(gameCard, sa, table, cachedMap, params);
}
}
if (untargetedCards.size() > 1) {
untargetedCards = (CardCollection) GameActionUtil.orderCardsByTheirOwners(game, untargetedCards, ZoneType.Graveyard, sa);
untargetedCards = GameActionUtil.orderCardsByTheirOwners(game, untargetedCards, ZoneType.Graveyard, sa);
}
for (final Card unTgtC : untargetedCards) {
if (unTgtC.isInPlay()) {
internalDestroy(unTgtC, sa, table, cachedMap);
internalDestroy(unTgtC, sa, table, cachedMap, params);
}
}
table.triggerChangesZoneAll(game, sa);
}
protected void internalDestroy(Card gameCard, SpellAbility sa, CardZoneTable table, Map<Integer, Card> cachedMap) {
protected void internalDestroy(Card gameCard, SpellAbility sa, CardZoneTable table, Map<Integer, Card> cachedMap, Map<AbilityKey, Object> params) {
final Card card = sa.getHostCard();
final Game game = card.getGame();
@@ -122,9 +126,9 @@ public class DestroyEffect extends SpellAbilityEffect {
card.addRemembered(gameCard.getAttachedCards());
}
if (sac) {
destroyed = game.getAction().sacrifice(gameCard, sa, table) != null;
destroyed = game.getAction().sacrifice(gameCard, sa, table, params) != null;
} else {
destroyed = game.getAction().destroy(gameCard, sa, !noRegen, table);
destroyed = game.getAction().destroy(gameCard, sa, !noRegen, table, params);
}
if (destroyed && remDestroyed) {
card.addRemembered(gameCard);

View File

@@ -6,6 +6,7 @@ import com.google.common.collect.Maps;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
@@ -86,9 +87,12 @@ public class SacrificeAllEffect extends SpellAbilityEffect {
CardZoneTable table = new CardZoneTable();
Map<Integer, Card> cachedMap = Maps.newHashMap();
Map<AbilityKey, Object> params = AbilityKey.newMap();
params.put(AbilityKey.LastStateBattlefield, game.copyLastStateBattlefield());
for (Card sac : list) {
final Card lKICopy = CardUtil.getLKICopy(sac, cachedMap);
if (game.getAction().sacrifice(sac, sa, table) != null) {
if (game.getAction().sacrifice(sac, sa, table, params) != null) {
if (remSacrificed) {
card.addRemembered(lKICopy);
}

View File

@@ -106,10 +106,12 @@ public class SacrificeEffect extends SpellAbilityEffect {
final String remSVar = sa.getParam("RememberSacrificedSVar");
int countSacrificed = 0;
CardZoneTable table = new CardZoneTable();
Map<AbilityKey, Object> params = AbilityKey.newMap();
params.put(AbilityKey.LastStateBattlefield, game.copyLastStateBattlefield());
if (valid.equals("Self") && game.getZoneOf(card) != null) {
if (game.getZoneOf(card).is(ZoneType.Battlefield)) {
if (game.getAction().sacrifice(card, sa, table) != null) {
if (game.getAction().sacrifice(card, sa, table, params) != null) {
countSacrificed++;
if (remSacrificed) {
card.addRemembered(card);
@@ -152,8 +154,8 @@ public class SacrificeEffect extends SpellAbilityEffect {
Map<Integer, Card> cachedMap = Maps.newHashMap();
for (Card sac : choosenToSacrifice) {
final Card lKICopy = CardUtil.getLKICopy(sac, cachedMap);
boolean wasSacrificed = !destroy && game.getAction().sacrifice(sac, sa, table) != null;
boolean wasDestroyed = destroy && game.getAction().destroy(sac, sa, true, table);
boolean wasSacrificed = !destroy && game.getAction().sacrifice(sac, sa, table, params) != null;
boolean wasDestroyed = destroy && game.getAction().destroy(sac, sa, true, table, params);
// Run Devour Trigger
if (devour) {
card.addDevoured(lKICopy);

View File

@@ -519,28 +519,6 @@ public class CardFactoryUtil {
return types.size();
}
/**
* <p>
* getNeededXDamage.
* </p>
*
* @param ability
* a {@link forge.game.spellability.SpellAbility} object.
* @return a int.
*/
public static int getNeededXDamage(final SpellAbility ability) {
// when targeting a creature, make sure the AI won't overkill on X
// damage
final Card target = ability.getTargetCard();
int neededDamage = -1;
if ((target != null)) {
neededDamage = target.getNetToughness() - target.getDamage();
}
return neededDamage;
}
/**
* Adds the ability factory abilities.
*

View File

@@ -126,7 +126,7 @@ public class CostSacrifice extends CostPartWithList {
@Override
protected Card doPayment(SpellAbility ability, Card targetCard) {
// no table there, it is already handled by CostPartWithList
return targetCard.getGame().getAction().sacrifice(targetCard, ability, null);
return targetCard.getGame().getAction().sacrifice(targetCard, ability, null, null);
}
/* (non-Javadoc)

View File

@@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableList;
import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardZoneTable;
import forge.game.spellability.SpellAbility;
@@ -22,8 +23,17 @@ public class StaticAbilityPanharmonicon {
public static int handlePanharmonicon(final Game game, final Trigger t, final Map<AbilityKey, Object> runParams) {
int n = 0;
CardCollectionView cardList = null;
// currently only used for leave the battlefield trigger
if (runParams.containsKey(AbilityKey.LastStateBattlefield)) {
cardList = (CardCollectionView) runParams.get(AbilityKey.LastStateBattlefield);
}
if (cardList == null) {
cardList = t.getMode() == TriggerType.ChangesZone && "Battlefield".equals(t.getParam("Origin")) ? game.getLastStateBattlefield() : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES);
}
// Checks only the battlefield, as those effects only work from there
for (final Card ca : t.getMode() == TriggerType.ChangesZone ? game.getLastStateBattlefield() : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final Card ca : cardList) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (!stAb.getParam("Mode").equals(MODE) || stAb.isSuppressed() || !stAb.checkConditions()) {
continue;

View File

@@ -6,12 +6,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@@ -46,7 +46,7 @@ import forge.localinstance.properties.ForgeConstants;
/**
* SoundSystem - a simple sound playback system for Forge.
* Do not use directly. Instead, use the {@link forge.sound.SoundEffectType} enumeration.
*
*
* @author Agetian
*/
public class AudioClip implements IAudioClip {
@@ -104,9 +104,9 @@ public class AudioClip implements IAudioClip {
private ClipWrapper getIdleClip() {
return clips.stream()
.filter(clip -> !clip.isRunning())
.findFirst()
.orElseGet(this::addClip);
.filter(clip -> !clip.isRunning())
.findFirst()
.orElseGet(this::addClip);
}
private ClipWrapper addClip() {

View File

@@ -2,9 +2,9 @@ Name:Boom
ManaCost:1 R
AlternateMode: Split
Types:Sorcery
A:SP$ Destroy | Cost$ 1 R | TgtPrompt$ Choose target land you control to destroy | ValidTgts$ Land.YouCtrl | SubAbility$ DestroyOpp | SpellDescription$ Destroy target land you control and target land you don't control.
SVar:DestroyOpp:DB$ Destroy | TgtPrompt$ Choose target land you don't control to destroy | ValidTgts$ Land.YouDontCtrl
SVar:Picture:http://www.wizards.com/global/images/magic/general/boombust.jpg
A:SP$ Pump | Cost$ 1 R | TgtPrompt$ Choose target land you control to destroy | ValidTgts$ Land.YouCtrl | AILogic$ Destroy | Curse$ True | SubAbility$ DestroyOpp | SpellDescription$ Destroy target land you control and target land you don't control.
SVar:DestroyOpp:DB$ Pump | TgtPrompt$ Choose target land you don't control to destroy | ValidTgts$ Land.YouDontCtrl | AILogic$ Destroy | Curse$ True | SubAbility$ DBDestroy
SVar:DBDestroy:DB$ Destroy | Defined$ Targeted
Oracle:Destroy target land you control and target land you don't control.
ALTERNATE

View File

@@ -1,9 +1,9 @@
Name:Decimate
ManaCost:2 R G
Types:Sorcery
A:SP$ Destroy | Cost$ 2 R G | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | SubAbility$ DestroyCreature | SpellDescription$ Destroy target artifact, target creature, target enchantment, and target land.
SVar:DestroyCreature:DB$ Destroy | ValidTgts$ Creature | SubAbility$ DestroyEnch | TgtPrompt$ Select target creature
SVar:DestroyEnch:DB$ Destroy | ValidTgts$ Enchantment | SubAbility$ DestroyLand | TgtPrompt$ Select target enchantment
SVar:DestroyLand:DB$ Destroy | ValidTgts$ Land | TgtPrompt$ Select target land
SVar:Picture:http://www.wizards.com/global/images/magic/general/decimate.jpg
A:SP$ Pump | Cost$ 2 R G | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | SubAbility$ DestroyCreature | AILogic$ Destroy | Curse$ True | SpellDescription$ Destroy target artifact, target creature, target enchantment, and target land.
SVar:DestroyCreature:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | AILogic$ Destroy | Curse$ True | SubAbility$ DestroyEnch
SVar:DestroyEnch:DB$ Pump | ValidTgts$ Enchantment | TgtPrompt$ Select target enchantment | AILogic$ Destroy | Curse$ True | SubAbility$ DestroyLand
SVar:DestroyLand:DB$ Pump | ValidTgts$ Land | TgtPrompt$ Select target land | AILogic$ Destroy | Curse$ True | SubAbility$ DBDestroy
SVar:DBDestroy:DB$ Destroy | Defined$ Targeted
Oracle:Destroy target artifact, target creature, target enchantment, and target land.

View File

@@ -1,7 +1,7 @@
Name:Fumarole
ManaCost:3 B R
Types:Sorcery
A:SP$ Destroy | Cost$ 3 B R PayLife<3> | ValidTgts$ Creature | TgtPrompt$ Select target creature | SubAbility$ DBDestroy | SpellDescription$ Destroy target creature and target land.
SVar:DBDestroy:DB$ Destroy | ValidTgts$ Land | TgtPrompt$ Select target land
SVar:Picture:http://www.wizards.com/global/images/magic/general/fumarole.jpg
A:SP$ Pump | Cost$ 3 B R PayLife<3> | ValidTgts$ Creature | TgtPrompt$ Select target creature | AILogic$ Destroy | Curse$ True | SubAbility$ DBLand | SpellDescription$ Destroy target creature and target land.
SVar:DBLand:DB$ Destroy | ValidTgts$ Land | TgtPrompt$ Select target land | AILogic$ Destroy | Curse$ True | SubAbility$ DBDestroy
SVar:DBDestroy:DB$ Destroy | Defined$ Targeted
Oracle:As an additional cost to cast this spell, pay 3 life.\nDestroy target creature and target land.

View File

@@ -3,6 +3,7 @@ ManaCost:3 R
Types:Creature Goblin
PT:2/2
T:Mode$ AttackerUnblocked | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigDestroyCreature | TriggerDescription$ Whenever CARDNAME attacks and isn't blocked, you may sacrifice it. If you do, destroy target creature and target land.
SVar:TrigDestroyCreature:AB$ Destroy | Cost$ Sac<1/CARDNAME> | ValidTgts$ Creature | TgtPrompt$ Select target creature | SubAbility$ DBDestroyLand
SVar:DBDestroyLand:DB$ Destroy | ValidTgts$ Land | TgtPrompt$ Select target land
SVar:TrigDestroyCreature:AB$ Pump | Cost$ Sac<1/CARDNAME> | ValidTgts$ Creature | TgtPrompt$ Select target creature | AILogic$ Destroy | Curse$ True | SubAbility$ DBLand
SVar:DBLand:DB$ Pump | ValidTgts$ Land | TgtPrompt$ Select target land | AILogic$ Destroy | Curse$ True | SubAbility$ DBDestroy
SVar:DBDestroy:DB$ Destroy | Defined$ Targeted
Oracle:Whenever Goblin Grenadiers attacks and isn't blocked, you may sacrifice it. If you do, destroy target creature and target land.

View File

@@ -4,8 +4,8 @@ Types:Sorcery
A:SP$ Charm | Cost$ R G | Choices$ DBDestroy1,DBDestroy2,DBDestroy3 | CharmNum$ 1
SVar:DBDestroy1:DB$ Destroy | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | SpellDescription$ Destroy target artifact.
SVar:DBDestroy2:DB$ Destroy | ValidTgts$ Enchantment | TgtPrompt$ Select target enchantment | SpellDescription$ Destroy target enchantment.
SVar:DBDestroy3:DB$ Destroy | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | SpellDescription$ Destroy target artifact and target enchantment. | SubAbility$ DestroyEnch
SVar:DestroyEnch:DB$ Destroy | ValidTgts$ Enchantment | TgtPrompt$ Select target enchantment
SVar:DBDestroy3:DB$ Pump | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | AILogic$ Destroy | Curse$ True | SubAbility$ DestroyEnch | SpellDescription$ Destroy target artifact and target enchantment.
SVar:DestroyEnch:DB$ Pump | ValidTgts$ Enchantment | TgtPrompt$ Select target enchantment | AILogic$ Destroy | Curse$ True | SubAbility$ DBDestroy
SVar:DBDestroy:DB$ Destroy | Defined$ Targeted
AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/hull_breach.jpg
Oracle:Choose one —\n• Destroy target artifact.\n• Destroy target enchantment.\n• Destroy target artifact and target enchantment.

View File

@@ -1,7 +1,7 @@
Name:Plague Spores
ManaCost:4 B R
Types:Sorcery
A:SP$ Destroy | Cost$ 4 B R | ValidTgts$ Creature.nonBlack | TgtPrompt$ Select nonblack creature | NoRegen$ True | SubAbility$ DestroyLand | SpellDescription$ Destroy target nonblack creature and target land. They can't be regenerated.
SVar:DestroyLand:DB$ Destroy | ValidTgts$ Land | NoRegen$ True | TgtPrompt$ Select target land
SVar:Picture:http://www.wizards.com/global/images/magic/general/plague_spores.jpg
A:SP$ Pump | Cost$ 4 B R | ValidTgts$ Creature.nonBlack | TgtPrompt$ Select nonblack creature | AILogic$ Destroy | Curse$ True | SubAbility$ DBLand | SpellDescription$ Destroy target nonblack creature and target land. They can't be regenerated.
SVar:DBLand:DB$ Pump | ValidTgts$ Land | TgtPrompt$ Select target land | AILogic$ Destroy | Curse$ True | SubAbility$ DBDestroy
SVar:DBDestroy:DB$ Destroy | Defined$ Targeted | NoRegen$ True
Oracle:Destroy target nonblack creature and target land. They can't be regenerated.

View File

@@ -2,10 +2,10 @@ Name:Reign of Chaos
ManaCost:2 R R
Types:Sorcery
A:SP$ Charm | Cost$ 2 R R | Choices$ DBDestroy1,DBDestroy2 | CharmNum$ 1
SVar:DBDestroy1:DB$ Destroy | ValidTgts$ Plains | TgtPrompt$ Select target Plains | SubAbility$ DBDestroyWhite | SpellDescription$ Destroy target Plains and target white creature.
SVar:DBDestroy2:DB$ Destroy | ValidTgts$ Island | TgtPrompt$ Select target Island | SubAbility$ DBDestroyBlue | SpellDescription$ Destroy target Island and target blue creature.
SVar:DBDestroyWhite:DB$ Destroy | ValidTgts$ Creature.White | TgtPrompt$ Select target white creature
SVar:DBDestroyBlue:DB$ Destroy | ValidTgts$ Creature.Blue | TgtPrompt$ Select target blue creature
SVar:DBDestroy1:DB$ Pump | ValidTgts$ Plains | TgtPrompt$ Select target Plains | AILogic$ Destroy | Curse$ True | SubAbility$ DBDestroyWhite | SpellDescription$ Destroy target Plains and target white creature.
SVar:DBDestroy2:DB$ Pump | ValidTgts$ Island | TgtPrompt$ Select target Island | AILogic$ Destroy | Curse$ True | SubAbility$ DBDestroyBlue | SpellDescription$ Destroy target Island and target blue creature.
SVar:DBDestroyWhite:DB$ Pump | ValidTgts$ Creature.White | TgtPrompt$ Select target white creature | AILogic$ Destroy | Curse$ True | SubAbility$ DBDestroy
SVar:DBDestroyBlue:DB$ Pump | ValidTgts$ Creature.Blue | TgtPrompt$ Select target blue creature | AILogic$ Destroy | Curse$ True | SubAbility$ DBDestroy
SVar:DBDestroy:DB$ Destroy | Defined$ Targeted
AI:RemoveDeck:Random
SVar:Picture:http://www.wizards.com/global/images/magic/general/reign_of_chaos.jpg
Oracle:Choose one —\n• Destroy target Plains and target white creature.\n• Destroy target Island and target blue creature.

View File

@@ -1,7 +1,7 @@
Name:Spiteful Blow
ManaCost:4 B B
Types:Sorcery
A:SP$ Destroy | Cost$ 4 B B | ValidTgts$ Creature | TgtPrompt$ Select target creature | SubAbility$ DBDestroy | SpellDescription$ Destroy target creature and target land.
SVar:DBDestroy:DB$ Destroy | ValidTgts$ Land | TgtPrompt$ Select target land
SVar:Picture:http://www.wizards.com/global/images/magic/general/spiteful_blow.jpg
A:SP$ Pump | Cost$ 4 B B | ValidTgts$ Creature | TgtPrompt$ Select target creature | SubAbility$ DBLand | AILogic$ Destroy | Curse$ True | SpellDescription$ Destroy target creature and target land.
SVar:DBLand:DB$ Pump | ValidTgts$ Land | TgtPrompt$ Select target land | AILogic$ Destroy | Curse$ True | SubAbility$ DBDestroy
SVar:DBDestroy:DB$ Destroy | Defined$ Targeted
Oracle:Destroy target creature and target land.

View File

@@ -1,7 +1,7 @@
Name:Stomp and Howl
ManaCost:2 G
Types:Sorcery
A:SP$ Destroy | Cost$ 2 G | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | SpellDescription$ Destroy target artifact and target enchantment. | SubAbility$ DestroyEnch
SVar:DestroyEnch:DB$ Destroy | ValidTgts$ Enchantment | TgtPrompt$ Select target enchantment
SVar:Picture:http://www.wizards.com/global/images/magic/general/stomp_and_howl.jpg
A:SP$ Pump | Cost$ 2 G | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | AILogic$ Destroy | Curse$ True | SubAbility$ DestroyEnch | SpellDescription$ Destroy target artifact and target enchantment.
SVar:DestroyEnch:DB$ Pump | ValidTgts$ Enchantment | TgtPrompt$ Select target enchantment | AILogic$ Destroy | Curse$ True | SubAbility$ DBDestroy
SVar:DBDestroy:DB$ Destroy | Defined$ Targeted
Oracle:Destroy target artifact and target enchantment.

View File

@@ -1,9 +1,9 @@
Name:Volcanic Offering
ManaCost:4 R
Types:Instant
A:SP$ Destroy | Cost$ 4 R | ValidTgts$ Land.nonBasic+YouDontCtrl | TgtPrompt$ Select target nonbasic land you don't control | SubAbility$ DBDestroyLand | SpellDescription$ Destroy target nonbasic land you don't control and target nonbasic land of an opponent's choice you don't control.
SVar:DBDestroyLand:DB$ Destroy | TargetingPlayer$ Player.Opponent | ValidTgts$ Land.nonBasic+YouDontCtrl | TgtPrompt$ Select target nonbasic land the caster of this spell don't control | SubAbility$ DBDamage
A:SP$ Pump | Cost$ 4 R | ValidTgts$ Land.nonBasic+YouDontCtrl | TgtPrompt$ Select target nonbasic land you don't control | AILogic$ Destroy | Curse$ True | SubAbility$ DBDestroyLand | SpellDescription$ Destroy target nonbasic land you don't control and target nonbasic land of an opponent's choice you don't control.
SVar:DBDestroyLand:DB$ Pump | TargetingPlayer$ Player.Opponent | ValidTgts$ Land.nonBasic+YouDontCtrl | TgtPrompt$ Select target nonbasic land the caster of this spell don't control | AILogic$ Destroy | Curse$ True | SubAbility$ DBDestroy
SVar:DBDestroy:DB$ Destroy | Defined$ Targeted | SubAbility$ DBDamage
SVar:DBDamage:DB$ DealDamage | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select target creature you don't control | NumDmg$ 7 | SubAbility$ DBDamage2 | SpellDescription$ CARDNAME deals 7 damage to target creature you don't control and 7 damage to target creature of an opponent's choice you don't control.
SVar:DBDamage2:DB$ DealDamage | TargetingPlayer$ Player.Opponent | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select target creature the caster of this spell don't control | NumDmg$ 7
SVar:Picture:http://www.wizards.com/global/images/magic/general/volcanic_offering.jpg
Oracle:Destroy target nonbasic land you don't control and target nonbasic land of an opponent's choice you don't control.\nVolcanic Offering deals 7 damage to target creature you don't control and 7 damage to target creature of an opponent's choice you don't control.

View File

@@ -3,8 +3,8 @@ ManaCost:2 B
Types:Creature Snake Wall
PT:2/4
K:Defender
A:AB$ Destroy | Cost$ 3 | Defined$ Self | Activator$ Player | SubAbility$ SnakeBite | SpellDescription$ Destroy CARDNAME and target creature it's blocking. Any player may activate this ability.
SVar:SnakeBite:DB$ Destroy | ValidTgts$ Creature.blockedBySource
A:AB$ Pump | Cost$ 3 | Defined$ Self | Activator$ Player | AILogic$ Destroy | Curse$ True | SubAbility$ SnakeBite | SpellDescription$ Destroy CARDNAME and target creature it's blocking. Any player may activate this ability.
SVar:SnakeBite:DB$ Pump | ValidTgts$ Creature.blockedBySource | AILogic$ Destroy | Curse$ True | SubAbility$ DBDestroy
SVar:DBDestroy:DB$ Destroy | Defined$ Targeted
AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/wall_of_vipers.jpg
Oracle:Defender (This creature can't attack.)\n{3}: Destroy Wall of Vipers and target creature it's blocking. Any player may activate this ability.

View File

@@ -673,7 +673,7 @@ public class HumanPlay {
final Card offering = ability.getSacrificedAsOffering();
offering.setUsedToPay(false);
if (!manaInputCancelled) {
game.getAction().sacrifice(offering, ability, table);
game.getAction().sacrifice(offering, ability, table, null);
}
ability.resetSacrificedAsOffering();
}
@@ -681,7 +681,7 @@ public class HumanPlay {
final Card emerge = ability.getSacrificedAsEmerge();
emerge.setUsedToPay(false);
if (!manaInputCancelled) {
game.getAction().sacrifice(emerge, ability, table);
game.getAction().sacrifice(emerge, ability, table, null);
}
ability.resetSacrificedAsEmerge();
}
@@ -785,7 +785,7 @@ public class HumanPlay {
if (ability.getSacrificedAsOffering() != null) {
System.out.println("Finishing up Offering");
offering.setUsedToPay(false);
activator.getGame().getAction().sacrifice(offering, ability, null);
activator.getGame().getAction().sacrifice(offering, ability, null, null);
ability.resetSacrificedAsOffering();
}
}
@@ -796,7 +796,7 @@ public class HumanPlay {
if (ability.getSacrificedAsEmerge() != null) {
System.out.println("Finishing up Emerge");
emerge.setUsedToPay(false);
activator.getGame().getAction().sacrifice(emerge, ability, null);
activator.getGame().getAction().sacrifice(emerge, ability, null, null);
ability.resetSacrificedAsEmerge();
}
}