diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index fc826e12efb..f47319913a4 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -337,17 +337,17 @@ public class ComputerUtilCost { } public static boolean isSacrificeSelfCost(final Cost cost) { - if (cost == null) { - return false; - } - for (final CostPart part : cost.getCostParts()) { - if (part instanceof CostSacrifice) { - if ("CARDNAME".equals(part.getType())) { - return true; - } - } - } - return false; + if (cost == null) { + return false; + } + for (final CostPart part : cost.getCostParts()) { + if (part instanceof CostSacrifice) { + if ("CARDNAME".equals(part.getType())) { + return true; + } + } + } + return false; } /** @@ -367,13 +367,13 @@ public class ComputerUtilCost { if (part instanceof CostTapType) { /* * Only crew with creatures weaker than vehicle - * + * * Possible improvements: * - block against evasive (flyers, intimidate, etc.) * - break board stall by racing with evasive vehicle */ if (sa.hasParam("Crew")) { - Card vehicle = AnimateAi.becomeAnimated(source, sa); + Card vehicle = AnimateAi.becomeAnimated(source, sa); final int vehicleValue = ComputerUtilCard.evaluateCreature(vehicle); String type = part.getType(); String totalP = type.split("withTotalPowerGE")[1]; @@ -390,7 +390,7 @@ public class ComputerUtilCost { return ComputerUtil.chooseTapTypeAccumulatePower(ai, type, sa, true, Integer.parseInt(totalP), exclude) != null; } - return false; + return false; } } return true; @@ -478,9 +478,9 @@ public class ComputerUtilCost { } } for (Card c : player.getCardsIn(ZoneType.Command)) { - if (cannotBeCountered) { - continue; - } + if (cannotBeCountered) { + continue; + } final String snem = c.getSVar("SpellsNeedExtraManaEffect"); if (!StringUtils.isBlank(snem)) { if (StringUtils.isNumeric(snem)) { @@ -548,7 +548,7 @@ public class ComputerUtilCost { } return ComputerUtilMana.canPayManaCost(sa, player, extraManaNeeded) - && CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa); + && CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa); } // canPayCost() public static boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView payers) { @@ -609,10 +609,10 @@ public class ComputerUtilCost { return false; } } else if (aiLogic != null && aiLogic.startsWith("LifeLE")) { - // if payer can't lose life its no need to pay unless - if (!payer.canLoseLife()) - return false; - else if (payer.getLife() <= Integer.valueOf(aiLogic.substring(6))) { + // if payer can't lose life its no need to pay unless + if (!payer.canLoseLife()) + return false; + else if (payer.getLife() <= AbilityUtils.calculateAmount(source, aiLogic.substring(6), sa)) { return true; } } else if ("WillAttack".equals(aiLogic)) { @@ -638,13 +638,13 @@ public class ComputerUtilCost { // Didn't have any of the data on the original SA to pay dependant costs return checkLifeCost(payer, cost, source, 4, sa) - && checkDamageCost(payer, cost, source, 4) - && (isMine || checkSacrificeCost(payer, cost, source, sa)) - && (isMine || checkDiscardCost(payer, cost, source, sa)) - && (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2) - && (!source.getName().equals("Perplex") || payer.getCardsIn(ZoneType.Hand).size() < 2) - && (!source.getName().equals("Breaking Point") || payer.getCreaturesInPlay().size() > 1) - && (!source.getName().equals("Chain of Vapor") || (payer.getWeakestOpponent().getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3)); + && checkDamageCost(payer, cost, source, 4) + && (isMine || checkSacrificeCost(payer, cost, source, sa)) + && (isMine || checkDiscardCost(payer, cost, source, sa)) + && (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2) + && (!source.getName().equals("Perplex") || payer.getCardsIn(ZoneType.Hand).size() < 2) + && (!source.getName().equals("Breaking Point") || payer.getCreaturesInPlay().size() > 1) + && (!source.getName().equals("Chain of Vapor") || (payer.getWeakestOpponent().getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3)); } public static Set getAvailableManaColors(Player ai, Card additionalLand) { diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index b5f86b5caf9..e9c041be794 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -1898,8 +1898,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } } } - if (keyword.startsWith("CantBeCounteredBy") || keyword.startsWith("Panharmonicon") - || keyword.startsWith("Dieharmonicon") || keyword.startsWith("Shrineharmonicon")) { + if (keyword.startsWith("CantBeCounteredBy")) { final String[] p = keyword.split(":"); sbLong.append(p[2]).append("\r\n"); } else if (keyword.startsWith("etbCounter")) { diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java index 797234ba22a..00bb23de497 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java @@ -34,6 +34,7 @@ import forge.game.GameEntity; import forge.game.GameStage; import forge.game.IIdentifiable; import forge.game.ability.AbilityUtils; +import forge.game.ability.AbilityKey; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardCollectionView; @@ -45,6 +46,7 @@ import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; +import forge.game.trigger.Trigger; import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.util.CardTranslation; @@ -458,6 +460,19 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone return false; } + public final boolean applyAbility(final String mode, final Trigger trigger, final Map runParams) { + // don't apply the ability if it hasn't got the right mode + if (!getParam("Mode").equals(mode)) { + return false; + } + + if (mode.equals("Panharmonicon")) { + return StaticAbilityPanharmonicon.applyPanharmoniconAbility(this, trigger, runParams); + } + + return false; + } + public final Cost getAttackCost(final Card attacker, final GameEntity target) { if (this.isSuppressed() || !getParam("Mode").equals("CantAttackUnless") || !this.checkConditions()) { return null; diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java new file mode 100644 index 00000000000..e920ced314f --- /dev/null +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java @@ -0,0 +1,98 @@ +package forge.game.staticability; + +import com.google.common.collect.ImmutableList; +import forge.game.Game; +import forge.game.ability.AbilityKey; +import forge.game.card.Card; +import forge.game.card.CardZoneTable; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.game.trigger.Trigger; +import forge.game.trigger.TriggerType; +import forge.game.zone.ZoneType; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class StaticAbilityPanharmonicon { + public static boolean applyPanharmoniconAbility(final StaticAbility stAb, final Trigger trigger, final Map runParams) { + final Card card = stAb.getHostCard(); + final Game game = card.getGame(); + + final Card trigHost = trigger.getHostCard(); + final TriggerType trigMode = trigger.getMode(); + + // What card is the source of the trigger? + if (!stAb.matchesValidParam("ValidCard", trigHost)) { + return false; + } + + // Is our trigger's mode among the other modes? + if (stAb.hasParam("ValidMode")) { + List modes = new ArrayList<>(Arrays.asList(stAb.getParam("ValidMode").split(","))); + if (!modes.contains(trigMode.toString())) { + return false; + } + } + + if (trigMode.equals(TriggerType.ChangesZone)) { + // Cause of the trigger – the card changing zones + final Card trigCause = (Card) runParams.get(AbilityKey.Card); + if (stAb.hasParam("ValidCause")) { + if (!trigCause.isValid(stAb.getParam("ValidCause").split(","), + game.getPhaseHandler().getPlayerTurn(), trigHost, null)) { + return false; + } + } + + if (stAb.hasParam("Origin")) { + final String origin = (String) runParams.get(AbilityKey.Origin); + if (!origin.equals(stAb.getParam("Origin"))) { + return false; + } + } + if (stAb.hasParam("Destination")) { + final String destination = (String) runParams.get(AbilityKey.Destination); + if (!destination.equals(stAb.getParam("Destination"))) { + return false; + } + } + } else if (trigMode.equals(TriggerType.ChangesZoneAll)) { + // Check if the cards have a trigger at all + final String origin = stAb.hasParam("Origin") ? stAb.getParam("Origin") : null; + final String destination = stAb.hasParam("Destination") ? stAb.getParam("Destination") : null; + final CardZoneTable table = (CardZoneTable) runParams.get(AbilityKey.Cards); + + // If the origin isn't specified, it's null – not making a list out of that. + if (origin == null) { + if (table.filterCards(null, ZoneType.smartValueOf(destination), stAb.getParam("ValidCause"), trigHost, null).isEmpty()) { + return false; + } + } else { + if (table.filterCards(ImmutableList.of(ZoneType.smartValueOf(origin)), ZoneType.smartValueOf(destination), stAb.getParam("ValidCause"), trigHost, null).isEmpty()) { + return false; + } + } + } else if (trigMode.equals(TriggerType.SpellCastOrCopy) + || trigMode.equals(TriggerType.SpellCast) || trigMode.equals(TriggerType.SpellCopy)) { + // Check if the spell cast and the caster match + final SpellAbility sa = (SpellAbility) runParams.get(AbilityKey.CastSA); + if (stAb.hasParam("ValidCause")) { + if (!sa.getHostCard().isValid(stAb.getParam("ValidCause").split(","), + game.getPhaseHandler().getPlayerTurn(), trigHost, null)) { + return false; + } + } + if (stAb.hasParam("ValidActivator")) { + if (!sa.getActivatingPlayer().isValid(stAb.getParam("ValidActivator").split(","), + game.getPhaseHandler().getPlayerTurn(), trigHost, null)) { + return false; + } + } + } + + return true; + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java index bc11bf8c90a..5068405c18f 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java @@ -34,16 +34,12 @@ import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.ability.effects.CharmEffect; -import forge.game.card.Card; -import forge.game.card.CardCollection; -import forge.game.card.CardLists; -import forge.game.card.CardPredicates; -import forge.game.card.CardState; -import forge.game.card.CardZoneTable; +import forge.game.card.*; import forge.game.keyword.KeywordInterface; import forge.game.player.Player; import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; +import forge.game.staticability.StaticAbility; import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.util.FileSection; @@ -362,7 +358,7 @@ public class TriggerHandler { for (final Trigger t : triggers) { if (!t.isStatic() && t.getHostCard().getController().equals(player) && canRunTrigger(t, mode, runParams)) { - int x = 1 + handlePanharmonicon(t, runParams, player); + int x = 1 + handlePanharmonicon(t, runParams); for (int i = 0; i < x; ++i) { runSingleTrigger(t, runParams); @@ -626,100 +622,14 @@ public class TriggerHandler { } } - private int handlePanharmonicon(final Trigger t, final Map runParams, final Player p) { - Card host = t.getHostCard(); + private int handlePanharmonicon(final Trigger t, final Map runParams) { int n = 0; - // Sanctum of All - if (host.isShrine() && host.isInZone(ZoneType.Battlefield) && p.equals(host.getController())) { - int shrineCount = CardLists.count(p.getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Shrine")); - if (shrineCount >= 6) { - for (final Card ck : p.getCardsIn(ZoneType.Battlefield)) { - for (final KeywordInterface ki : ck.getKeywords()) { - final String kw = ki.getOriginal(); - if (kw.startsWith("Shrineharmonicon")) { - final String valid = kw.split(":")[1]; - if (host.isValid(valid.split(","), p, ck, null)) { - n++; - } - } - } - } - } - } - - // not a changesZone trigger or changesZoneAll - if (t.getMode() != TriggerType.ChangesZone && t.getMode() != TriggerType.ChangesZoneAll) { - return n; - } - - // leave battlefield trigger, might be dying - // only real changeszone look back for this - if (t.getMode() == TriggerType.ChangesZone && "Battlefield".equals(t.getParam("Origin"))) { - // Need to get the last info from the trigger host - host = game.getChangeZoneLKIInfo(host); - } - - // not a Permanent you control - if (!host.isPermanent() || !host.isInZone(ZoneType.Battlefield)) { - return 0; - } - - if (t.getMode() == TriggerType.ChangesZone) { - // iterate over all cards - for (final Card ck : CardLists.filterControlledBy(p.getGame().getLastStateBattlefield(), p)) { - for (final KeywordInterface ki : ck.getKeywords()) { - final String kw = ki.getOriginal(); - if (kw.startsWith("Panharmonicon")) { - // Enter the Battlefield Trigger - if (runParams.get(AbilityKey.Destination) instanceof String) { - final String dest = (String) runParams.get(AbilityKey.Destination); - if ("Battlefield".equals(dest) && runParams.get(AbilityKey.Card) instanceof Card) { - final Card card = (Card) runParams.get(AbilityKey.Card); - final String valid = kw.split(":")[1]; - if (card.isValid(valid.split(","), p, ck, null)) { - n++; - } - } - } - } else if (kw.startsWith("Dieharmonicon")) { - // 700.4. The term dies means "is put into a graveyard from the battlefield." - if (runParams.get(AbilityKey.Origin) instanceof String) { - final String origin = (String) runParams.get(AbilityKey.Origin); - if ("Battlefield".equals(origin) && runParams.get(AbilityKey.Destination) instanceof String) { - final String dest = (String) runParams.get(AbilityKey.Destination); - if ("Graveyard".equals(dest) && runParams.get(AbilityKey.Card) instanceof Card) { - final Card card = (Card) runParams.get(AbilityKey.Card); - final String valid = kw.split(":")[1]; - if (card.isValid(valid.split(","), p, ck, null)) { - n++; - } - } - } - } - } - } - } - } else if (t.getMode() == TriggerType.ChangesZoneAll) { - final CardZoneTable table = (CardZoneTable) runParams.get(AbilityKey.Cards); - // iterate over all cards that are on the battlefield right now, don't use last state - for (final Card ck : p.getCardsIn(ZoneType.Battlefield)) { - for (final KeywordInterface ki : ck.getKeywords()) { - final String kw = ki.getOriginal(); - if (kw.startsWith("Panharmonicon")) { - // currently there is no ChangesZoneAll that would trigger on etb - final String valid = kw.split(":")[1]; - if (!table.filterCards(null, ZoneType.Battlefield, valid, ck, null).isEmpty()) { - n++; - } - } else if (kw.startsWith("Dieharmonicon")) { - // 700.4. The term dies means "is put into a graveyard from the battlefield." - final String valid = kw.split(":")[1]; - if (!table.filterCards(ImmutableList.of(ZoneType.Battlefield), ZoneType.Graveyard, - valid, ck, null).isEmpty()) { - n++; - } - } + // Checks only the battlefield, as those effects only work from there + for (final Card ca : game.getLastStateBattlefield()) { + for (final StaticAbility stAb : ca.getStaticAbilities()) { + if (stAb.applyAbility("Panharmonicon", t, runParams) && stAb.checkConditions()) { + n++; } } } diff --git a/forge-gui/res/cardsfolder/a/ancient_greenwarden.txt b/forge-gui/res/cardsfolder/a/ancient_greenwarden.txt index 23c4fd8f267..886e1ee0403 100755 --- a/forge-gui/res/cardsfolder/a/ancient_greenwarden.txt +++ b/forge-gui/res/cardsfolder/a/ancient_greenwarden.txt @@ -4,5 +4,5 @@ Types:Creature Elemental PT:5/7 K:Reach S:Mode$ Continuous | Affected$ Land.YouCtrl | MayPlay$ True | AffectedZone$ Graveyard | Description$ You may play lands from your graveyard. -K:Panharmonicon:Land:If a land entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. +S:Mode$ Panharmonicon | ValidMode$ ChangesZone,ChangesZoneAll | ValidCard$ Permanent.YouCtrl | ValidCause$ Land | Destination$ Battlefield | Description$ If a land entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. Oracle:Reach\nYou may play lands from your graveyard.\nIf a land entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. diff --git a/forge-gui/res/cardsfolder/c/clackbridge_troll.txt b/forge-gui/res/cardsfolder/c/clackbridge_troll.txt index aede0e4e57b..1c065593e16 100644 --- a/forge-gui/res/cardsfolder/c/clackbridge_troll.txt +++ b/forge-gui/res/cardsfolder/c/clackbridge_troll.txt @@ -1,3 +1,4 @@ +# TODO: Improve AI logic. Currently AI will sacrifice even if Troll can't attack at all. Name:Clackbridge Troll ManaCost:3 B B Types:Creature Troll diff --git a/forge-gui/res/cardsfolder/n/naban_dean_of_iteration.txt b/forge-gui/res/cardsfolder/n/naban_dean_of_iteration.txt index e1606a06d8f..8446863251e 100644 --- a/forge-gui/res/cardsfolder/n/naban_dean_of_iteration.txt +++ b/forge-gui/res/cardsfolder/n/naban_dean_of_iteration.txt @@ -2,7 +2,6 @@ Name:Naban, Dean of Iteration ManaCost:1 U Types:Legendary Creature Human Wizard PT:2/1 -K:Panharmonicon:Wizard.YouCtrl:If a Wizard entering the battlefield under your control causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. +S:Mode$ Panharmonicon | ValidMode$ ChangesZone,ChangesZoneAll | ValidCard$ Permanent.YouCtrl | ValidCause$ Wizard.YouCtrl | Destination$ Battlefield | Description$ If a Wizard entering the battlefield under your control causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. DeckHints:Type$Wizard -SVar:Picture:http://www.wizards.com/global/images/magic/general/naban_dean_of_iteration.jpg Oracle:If a Wizard entering the battlefield under your control causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. diff --git a/forge-gui/res/cardsfolder/p/panharmonicon.txt b/forge-gui/res/cardsfolder/p/panharmonicon.txt index 5cde6b7f7cb..05435db7b5f 100644 --- a/forge-gui/res/cardsfolder/p/panharmonicon.txt +++ b/forge-gui/res/cardsfolder/p/panharmonicon.txt @@ -1,6 +1,5 @@ Name:Panharmonicon ManaCost:4 Types:Artifact -K:Panharmonicon:Creature,Artifact:If an artifact or creature entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. -SVar:Picture:http://www.wizards.com/global/images/magic/general/panharmonicon.jpg +S:Mode$ Panharmonicon | ValidMode$ ChangesZone,ChangesZoneAll | ValidCard$ Permanent.YouCtrl | ValidCause$ Artifact,Creature | Destination$ Battlefield | Description$ If an artifact or creature entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. Oracle:If an artifact or creature entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. diff --git a/forge-gui/res/cardsfolder/s/sanctum_of_all.txt b/forge-gui/res/cardsfolder/s/sanctum_of_all.txt index 8174b7f0f28..c94a7abd996 100644 --- a/forge-gui/res/cardsfolder/s/sanctum_of_all.txt +++ b/forge-gui/res/cardsfolder/s/sanctum_of_all.txt @@ -3,6 +3,6 @@ ManaCost:W U B R G Types:Legendary Enchantment Shrine T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigSearch | TriggerDescription$ At the beginning of your upkeep, you may search your library and/or graveyard for a Shrine card and put it onto the battlefield. If you search your library this way, shuffle it. SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | OriginChoice$ True | OriginAlternative$ Graveyard | AlternativeMessage$ Would you like to search your library with this ability? If you do, your library will be shuffled. | Destination$ Battlefield | ChangeType$ Card.Shrine -K:Shrineharmonicon:Shrine.Other+YouCtrl:If an ability of another Shrine you control triggers while you control six or more Shrines, that ability triggers an additional time. +S:Mode$ Panharmonicon | ValidCard$ Shrine.Other+YouCtrl | IsPresent$ Shrine.YouCtrl | PresentCompare$ GE6 | Description$If an ability of another Shrine you control triggers while you control six or more Shrines, that ability triggers an additional time. DeckHints:Type$Shrine Oracle:At the beginning of your upkeep, you may search your library and/or graveyard for a Shrine card and put it onto the battlefield. If you search your library this way, shuffle it.\nIf an ability of another Shrine you control triggers while you control six or more Shrines, that ability triggers an additional time. diff --git a/forge-gui/res/cardsfolder/t/teysa_karlov.txt b/forge-gui/res/cardsfolder/t/teysa_karlov.txt index bc52f882a8f..1a08502d1b7 100644 --- a/forge-gui/res/cardsfolder/t/teysa_karlov.txt +++ b/forge-gui/res/cardsfolder/t/teysa_karlov.txt @@ -2,7 +2,7 @@ Name:Teysa Karlov ManaCost:2 W B Types:Legendary Creature Human Advisor PT:2/4 -K:Dieharmonicon:Creature:If a creature dying causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. +S:Mode$ Panharmonicon | ValidMode$ ChangesZone,ChangesZoneAll | ValidCard$ Permanent.YouCtrl | ValidCause$ Creature | Origin$ Battlefield | Destination$ Graveyard | Description$ If a creature dying causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. S:Mode$ Continuous | Affected$ Creature.token+YouCtrl | AddKeyword$ Vigilance & Lifelink | Description$ Creature tokens you control have vigilance and lifelink. DeckHints:Ability$Token Oracle:If a creature dying causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.\nCreature tokens you control have vigilance and lifelink. diff --git a/forge-gui/res/cardsfolder/upcoming/veyran_voice_of_duality.txt b/forge-gui/res/cardsfolder/upcoming/veyran_voice_of_duality.txt new file mode 100644 index 00000000000..00264e36133 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/veyran_voice_of_duality.txt @@ -0,0 +1,10 @@ +Name:Veyran, Voice of Duality +ManaCost:1 U R +Types:Legendary Creature Efreet Wizard +PT:2/2 +T:Mode$ SpellCastOrCopy | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Magecraft - Whenever you cast or copy an instant or sorcery spell, CARDNAME gets +1/+1 until end of turn. +SVar:TrigPump:DB$ Pump | NumAtt$ 1 | NumDef$ 1 +S:Mode$ Panharmonicon | ValidMode$ SpellCast,SpellCopy,SpellCastOrCopy | ValidCard$ Permanent.YouCtrl | ValidCause$ Instant,Sorcery | ValidActivator$ You | Description$ If you casting or copying an instant or sorcery spell causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. +SVar:BuffedBy:Instant,Sorcery +DeckNeeds:Type$Instant|Sorcery +Oracle:Magecraft — Whenever you cast or copy an instant or sorcery spell, Veyran, Voice of Duality gets +1/+1 until end of turn.\nIf you casting or copying an instant or sorcery spell causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. diff --git a/forge-gui/res/cardsfolder/y/yarok_the_desecrated.txt b/forge-gui/res/cardsfolder/y/yarok_the_desecrated.txt index 14071cff8c0..7f04d81ce14 100644 --- a/forge-gui/res/cardsfolder/y/yarok_the_desecrated.txt +++ b/forge-gui/res/cardsfolder/y/yarok_the_desecrated.txt @@ -4,5 +4,5 @@ Types:Legendary Creature Elemental Horror PT:3/5 K:Deathtouch K:Lifelink -K:Panharmonicon:Permanent:If a permanent entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. +S:Mode$ Panharmonicon | ValidMode$ ChangesZone,ChangesZoneAll | ValidCard$ Permanent | ValidCause$ Wizard.YouCtrl | Destination$ Battlefield | Description$ If a permanent entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. Oracle:Deathtouch, lifelink\nIf a permanent entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.