C21 Veyran & Panharmonicon rework

This commit is contained in:
Hythonia
2021-04-09 15:15:29 +00:00
committed by Hans Mackowiak
parent c93c582ed5
commit cc8e8b2dd1
13 changed files with 169 additions and 138 deletions

View File

@@ -337,17 +337,17 @@ public class ComputerUtilCost {
} }
public static boolean isSacrificeSelfCost(final Cost cost) { public static boolean isSacrificeSelfCost(final Cost cost) {
if (cost == null) { if (cost == null) {
return false; return false;
} }
for (final CostPart part : cost.getCostParts()) { for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostSacrifice) { if (part instanceof CostSacrifice) {
if ("CARDNAME".equals(part.getType())) { if ("CARDNAME".equals(part.getType())) {
return true; return true;
} }
} }
} }
return false; return false;
} }
/** /**
@@ -367,13 +367,13 @@ public class ComputerUtilCost {
if (part instanceof CostTapType) { if (part instanceof CostTapType) {
/* /*
* Only crew with creatures weaker than vehicle * Only crew with creatures weaker than vehicle
* *
* Possible improvements: * Possible improvements:
* - block against evasive (flyers, intimidate, etc.) * - block against evasive (flyers, intimidate, etc.)
* - break board stall by racing with evasive vehicle * - break board stall by racing with evasive vehicle
*/ */
if (sa.hasParam("Crew")) { if (sa.hasParam("Crew")) {
Card vehicle = AnimateAi.becomeAnimated(source, sa); Card vehicle = AnimateAi.becomeAnimated(source, sa);
final int vehicleValue = ComputerUtilCard.evaluateCreature(vehicle); final int vehicleValue = ComputerUtilCard.evaluateCreature(vehicle);
String type = part.getType(); String type = part.getType();
String totalP = type.split("withTotalPowerGE")[1]; String totalP = type.split("withTotalPowerGE")[1];
@@ -390,7 +390,7 @@ public class ComputerUtilCost {
return ComputerUtil.chooseTapTypeAccumulatePower(ai, type, sa, true, return ComputerUtil.chooseTapTypeAccumulatePower(ai, type, sa, true,
Integer.parseInt(totalP), exclude) != null; Integer.parseInt(totalP), exclude) != null;
} }
return false; return false;
} }
} }
return true; return true;
@@ -478,9 +478,9 @@ public class ComputerUtilCost {
} }
} }
for (Card c : player.getCardsIn(ZoneType.Command)) { for (Card c : player.getCardsIn(ZoneType.Command)) {
if (cannotBeCountered) { if (cannotBeCountered) {
continue; continue;
} }
final String snem = c.getSVar("SpellsNeedExtraManaEffect"); final String snem = c.getSVar("SpellsNeedExtraManaEffect");
if (!StringUtils.isBlank(snem)) { if (!StringUtils.isBlank(snem)) {
if (StringUtils.isNumeric(snem)) { if (StringUtils.isNumeric(snem)) {
@@ -548,7 +548,7 @@ public class ComputerUtilCost {
} }
return ComputerUtilMana.canPayManaCost(sa, player, extraManaNeeded) return ComputerUtilMana.canPayManaCost(sa, player, extraManaNeeded)
&& CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa); && CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa);
} // canPayCost() } // canPayCost()
public static boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView<Player> payers) { public static boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView<Player> payers) {
@@ -609,10 +609,10 @@ public class ComputerUtilCost {
return false; return false;
} }
} else if (aiLogic != null && aiLogic.startsWith("LifeLE")) { } else if (aiLogic != null && aiLogic.startsWith("LifeLE")) {
// if payer can't lose life its no need to pay unless // if payer can't lose life its no need to pay unless
if (!payer.canLoseLife()) if (!payer.canLoseLife())
return false; return false;
else if (payer.getLife() <= Integer.valueOf(aiLogic.substring(6))) { else if (payer.getLife() <= AbilityUtils.calculateAmount(source, aiLogic.substring(6), sa)) {
return true; return true;
} }
} else if ("WillAttack".equals(aiLogic)) { } 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 // Didn't have any of the data on the original SA to pay dependant costs
return checkLifeCost(payer, cost, source, 4, sa) return checkLifeCost(payer, cost, source, 4, sa)
&& checkDamageCost(payer, cost, source, 4) && checkDamageCost(payer, cost, source, 4)
&& (isMine || checkSacrificeCost(payer, cost, source, sa)) && (isMine || checkSacrificeCost(payer, cost, source, sa))
&& (isMine || checkDiscardCost(payer, cost, source, sa)) && (isMine || checkDiscardCost(payer, cost, source, sa))
&& (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2) && (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2)
&& (!source.getName().equals("Perplex") || 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("Breaking Point") || payer.getCreaturesInPlay().size() > 1)
&& (!source.getName().equals("Chain of Vapor") || (payer.getWeakestOpponent().getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3)); && (!source.getName().equals("Chain of Vapor") || (payer.getWeakestOpponent().getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3));
} }
public static Set<String> getAvailableManaColors(Player ai, Card additionalLand) { public static Set<String> getAvailableManaColors(Player ai, Card additionalLand) {

View File

@@ -1898,8 +1898,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} }
} }
} }
if (keyword.startsWith("CantBeCounteredBy") || keyword.startsWith("Panharmonicon") if (keyword.startsWith("CantBeCounteredBy")) {
|| keyword.startsWith("Dieharmonicon") || keyword.startsWith("Shrineharmonicon")) {
final String[] p = keyword.split(":"); final String[] p = keyword.split(":");
sbLong.append(p[2]).append("\r\n"); sbLong.append(p[2]).append("\r\n");
} else if (keyword.startsWith("etbCounter")) { } else if (keyword.startsWith("etbCounter")) {

View File

@@ -34,6 +34,7 @@ import forge.game.GameEntity;
import forge.game.GameStage; import forge.game.GameStage;
import forge.game.IIdentifiable; import forge.game.IIdentifiable;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.AbilityKey;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
@@ -45,6 +46,7 @@ import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger;
import forge.game.zone.Zone; import forge.game.zone.Zone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.CardTranslation; import forge.util.CardTranslation;
@@ -458,6 +460,19 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
return false; return false;
} }
public final boolean applyAbility(final String mode, final Trigger trigger, final Map<AbilityKey, Object> 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) { public final Cost getAttackCost(final Card attacker, final GameEntity target) {
if (this.isSuppressed() || !getParam("Mode").equals("CantAttackUnless") || !this.checkConditions()) { if (this.isSuppressed() || !getParam("Mode").equals("CantAttackUnless") || !this.checkConditions()) {
return null; return null;

View File

@@ -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<AbilityKey, Object> 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<String> 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;
}
}

View File

@@ -34,16 +34,12 @@ import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.ability.effects.CharmEffect; import forge.game.ability.effects.CharmEffect;
import forge.game.card.Card; import forge.game.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.keyword.KeywordInterface; import forge.game.keyword.KeywordInterface;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.AbilitySub; import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility;
import forge.game.zone.Zone; import forge.game.zone.Zone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.FileSection; import forge.util.FileSection;
@@ -362,7 +358,7 @@ public class TriggerHandler {
for (final Trigger t : triggers) { for (final Trigger t : triggers) {
if (!t.isStatic() && t.getHostCard().getController().equals(player) && canRunTrigger(t, mode, runParams)) { 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) { for (int i = 0; i < x; ++i) {
runSingleTrigger(t, runParams); runSingleTrigger(t, runParams);
@@ -626,100 +622,14 @@ public class TriggerHandler {
} }
} }
private int handlePanharmonicon(final Trigger t, final Map<AbilityKey, Object> runParams, final Player p) { private int handlePanharmonicon(final Trigger t, final Map<AbilityKey, Object> runParams) {
Card host = t.getHostCard();
int n = 0; int n = 0;
// Sanctum of All // Checks only the battlefield, as those effects only work from there
if (host.isShrine() && host.isInZone(ZoneType.Battlefield) && p.equals(host.getController())) { for (final Card ca : game.getLastStateBattlefield()) {
int shrineCount = CardLists.count(p.getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Shrine")); for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (shrineCount >= 6) { if (stAb.applyAbility("Panharmonicon", t, runParams) && stAb.checkConditions()) {
for (final Card ck : p.getCardsIn(ZoneType.Battlefield)) { n++;
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++;
}
}
} }
} }
} }

View File

@@ -4,5 +4,5 @@ Types:Creature Elemental
PT:5/7 PT:5/7
K:Reach K:Reach
S:Mode$ Continuous | Affected$ Land.YouCtrl | MayPlay$ True | AffectedZone$ Graveyard | Description$ You may play lands from your graveyard. 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. 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.

View File

@@ -1,3 +1,4 @@
# TODO: Improve AI logic. Currently AI will sacrifice even if Troll can't attack at all.
Name:Clackbridge Troll Name:Clackbridge Troll
ManaCost:3 B B ManaCost:3 B B
Types:Creature Troll Types:Creature Troll

View File

@@ -2,7 +2,6 @@ Name:Naban, Dean of Iteration
ManaCost:1 U ManaCost:1 U
Types:Legendary Creature Human Wizard Types:Legendary Creature Human Wizard
PT:2/1 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 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. 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.

View File

@@ -1,6 +1,5 @@
Name:Panharmonicon Name:Panharmonicon
ManaCost:4 ManaCost:4
Types:Artifact 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. 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.
SVar:Picture:http://www.wizards.com/global/images/magic/general/panharmonicon.jpg
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. 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.

View File

@@ -3,6 +3,6 @@ ManaCost:W U B R G
Types:Legendary Enchantment Shrine 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. 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 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 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. 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.

View File

@@ -2,7 +2,7 @@ Name:Teysa Karlov
ManaCost:2 W B ManaCost:2 W B
Types:Legendary Creature Human Advisor Types:Legendary Creature Human Advisor
PT:2/4 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. S:Mode$ Continuous | Affected$ Creature.token+YouCtrl | AddKeyword$ Vigilance & Lifelink | Description$ Creature tokens you control have vigilance and lifelink.
DeckHints:Ability$Token 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. 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.

View File

@@ -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.

View File

@@ -4,5 +4,5 @@ Types:Legendary Creature Elemental Horror
PT:3/5 PT:3/5
K:Deathtouch K:Deathtouch
K:Lifelink 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. 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.