Fix attacking self with Kaalia trigger

This commit is contained in:
tool4EvEr
2021-11-08 18:11:26 +01:00
parent fa57889a89
commit f652bd5f29
18 changed files with 116 additions and 72 deletions

View File

@@ -770,6 +770,7 @@ public class AiController {
// one is warded and can't be paid for.
if (sa.usesTargeting()) {
for (Card tgt : sa.getTargets().getTargetCards()) {
// TODO some older cards don't use the keyword, so check for trigger instead
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) {
int amount = 0;
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);

View File

@@ -2497,4 +2497,27 @@ public class ComputerUtilCombat {
}
return poison;
}
public static GameEntity addAttackerToCombat(SpellAbility sa, Card attacker, FCollection<GameEntity> defenders) {
Combat combat = sa.getHostCard().getGame().getCombat();
if (combat != null) {
// 1. If the card that spawned the attacker was sent at a planeswalker, attack the same. Consider improving.
GameEntity def = combat.getDefenderByAttacker(sa.getHostCard());
if (def != null && def instanceof Card) {
if (((Card)def).isPlaneswalker()) {
return def;
}
}
// 2. Otherwise, go through the list of options one by one, choose the first one that can't be blocked profitably.
for (GameEntity p : defenders) {
if (p instanceof Player && !ComputerUtilCard.canBeBlockedProfitably((Player)p, attacker)) {
return p;
}
if (p instanceof Card && !ComputerUtilCard.canBeBlockedProfitably(((Card)p).getController(), attacker)) {
return p;
}
}
}
return Iterables.getFirst(defenders, null);
}
}

View File

@@ -452,21 +452,15 @@ public class AttachAi extends SpellAbilityAi {
/**
* Attach to player ai preferences.
*
* @param sa
* the sa
* @param mandatory
* the mandatory
* @param newParam TODO
*
* @return the player
*/
public static Player attachToPlayerAIPreferences(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
List<Player> targetable = new ArrayList<>();
for (final Player player : aiPlayer.getGame().getPlayers()) {
if (sa.canTarget(player)) {
targetable.add(player);
}
}
public static Player attachToPlayerAIPreferences(final Player aiPlayer, final SpellAbility sa, final boolean mandatory, List<Player> targetable) {
if ("Curse".equals(sa.getParam("AILogic"))) {
if (!mandatory) {
targetable.removeAll(aiPlayer.getAllies());
@@ -1020,7 +1014,13 @@ public class AttachAi extends SpellAbilityAi {
private static boolean attachPreference(final SpellAbility sa, final TargetRestrictions tgt, final boolean mandatory) {
GameObject o;
if (tgt.canTgtPlayer()) {
o = attachToPlayerAIPreferences(sa.getActivatingPlayer(), sa, mandatory);
List<Player> targetable = new ArrayList<>();
for (final Player player : sa.getHostCard().getGame().getPlayers()) {
if (sa.canTarget(player)) {
targetable.add(player);
}
}
o = attachToPlayerAIPreferences(sa.getActivatingPlayer(), sa, mandatory, targetable);
} else {
o = attachToCardAIPreferences(sa.getActivatingPlayer(), sa, mandatory);
}
@@ -1459,10 +1459,12 @@ public class AttachAi extends SpellAbilityAi {
*/
public static Card attachGeneralAI(final Player ai, final SpellAbility sa, final List<Card> list, final boolean mandatory,
final Card attachSource, final String logic) {
Player prefPlayer = AiAttackController.choosePreferredDefenderPlayer(ai);
Player prefPlayer;
if ("Pump".equals(logic) || "Animate".equals(logic) || "Curiosity".equals(logic) || "MoveTgtAura".equals(logic)
|| "MoveAllAuras".equals(logic)) {
prefPlayer = ai;
} else {
prefPlayer = AiAttackController.choosePreferredDefenderPlayer(ai);
}
// Some ChangeType cards are beneficial, and PrefPlayer should be
// changed to represent that
@@ -1765,6 +1767,6 @@ public class AttachAi extends SpellAbilityAi {
@Override
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
return attachToPlayerAIPreferences(ai, sa, true);
return attachToPlayerAIPreferences(ai, sa, true, (List<Player>)options);
}
}

View File

@@ -29,6 +29,7 @@ import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi;
import forge.card.MagicColor;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityKey;
@@ -50,6 +51,7 @@ import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbilityMustTarget;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import forge.util.collect.FCollection;
public class ChangeZoneAi extends SpellAbilityAi {
/*
@@ -423,7 +425,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
return canBouncePermanent(ai, sa, list) != null;
}
}
if (ComputerUtil.playImmediately(ai, sa)) {
@@ -1784,8 +1785,20 @@ public class ChangeZoneAi extends SpellAbilityAi {
*/
@Override
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
// Called when attaching Aura to player
return AttachAi.attachToPlayerAIPreferences(ai, sa, true);
// Called when attaching Aura to player or adding creature to combat
if (params.containsKey("Attacker")) {
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
}
return AttachAi.attachToPlayerAIPreferences(ai, sa, true, (List<Player>)options);
}
@Override
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
if (params.containsKey("Attacker")) {
return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
}
// should not be reached
return super.chooseSinglePlayerOrPlaneswalker(ai, sa, options, params);
}
private boolean doSacAndReturnFromGraveLogic(final Player ai, final SpellAbility sa) {

View File

@@ -12,10 +12,12 @@ import forge.ai.AiPlayDecision;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
@@ -32,6 +34,7 @@ import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerCollection;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.collect.FCollection;
public class CopyPermanentAi extends SpellAbilityAi {
@Override
@@ -244,9 +247,21 @@ public class CopyPermanentAi extends SpellAbilityAi {
@Override
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
if (params.containsKey("Attacker")) {
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
}
final List<Card> cards = new PlayerCollection(options).getCreaturesInPlay();
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
}
@Override
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
if (params.containsKey("Attacker")) {
return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
}
// should not be reached
return super.chooseSinglePlayerOrPlaneswalker(ai, sa, options, params);
}
}

View File

@@ -8,10 +8,12 @@ import forge.ai.AiAttackController;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
@@ -25,6 +27,7 @@ import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.TextUtil;
import forge.util.collect.FCollection;
public class DigAi extends SpellAbilityAi {
@@ -180,10 +183,22 @@ public class DigAi extends SpellAbilityAi {
*/
@Override
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
if (params.containsKey("Attacker")) {
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
}
// an opponent choose a card from
return Iterables.getFirst(options, null);
}
@Override
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
if (params.containsKey("Attacker")) {
return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
}
// should not be reached
return super.chooseSinglePlayerOrPlaneswalker(ai, sa, options, params);
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/

View File

@@ -9,6 +9,7 @@ import forge.ai.AiController;
import forge.ai.AiProps;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.PlayerControllerAi;
@@ -37,6 +38,7 @@ import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import forge.util.collect.FCollection;
/**
* <p>
@@ -312,15 +314,8 @@ public class TokenAi extends SpellAbilityAi {
*/
@Override
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
Combat combat = ai.getGame().getCombat();
// TokenAttacking
if (combat != null && sa.hasParam("TokenAttacking")) {
Card attacker = spawnToken(ai, sa);
for (Player p : options) {
if (!ComputerUtilCard.canBeBlockedProfitably(p, attacker)) {
return p;
}
}
if (params.containsKey("Attacker")) {
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
}
return Iterables.getFirst(options, null);
}
@@ -330,28 +325,11 @@ public class TokenAi extends SpellAbilityAi {
*/
@Override
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
Combat combat = ai.getGame().getCombat();
// TokenAttacking
if (combat != null && sa.hasParam("TokenAttacking")) {
// 1. If the card that spawned the token was sent at a planeswalker, attack the same planeswalker with the token. Consider improving.
GameEntity def = combat.getDefenderByAttacker(sa.getHostCard());
if (def != null && def instanceof Card) {
if (((Card)def).isPlaneswalker()) {
return def;
}
}
// 2. Otherwise, go through the list of options one by one, choose the first one that can't be blocked profitably.
Card attacker = spawnToken(ai, sa);
for (GameEntity p : options) {
if (p instanceof Player && !ComputerUtilCard.canBeBlockedProfitably((Player)p, attacker)) {
return p;
}
if (p instanceof Card && !ComputerUtilCard.canBeBlockedProfitably(((Card)p).getController(), attacker)) {
return p;
}
}
if (params.containsKey("Attacker")) {
return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
}
return Iterables.getFirst(options, null);
// should not be reached
return super.chooseSinglePlayerOrPlaneswalker(ai, sa, options, params);
}
/**

View File

@@ -412,7 +412,7 @@ public class GameAction {
CardCollection cards = new CardCollection(c.getMergedCards());
// replace top card with copied card for correct name for human to choose.
cards.set(cards.indexOf(c), copied);
// 721.3b
// 723.3b
if (cause != null && zoneTo.getZoneType() == ZoneType.Exile) {
cards = (CardCollection) cause.getHostCard().getController().getController().orderMoveToZoneList(cards, zoneTo.getZoneType(), cause);
} else {

View File

@@ -575,14 +575,14 @@ public abstract class SpellAbilityEffect {
FCollection<GameEntity> defs = null;
// important to update defenders here, maybe some PW got removed
combat.initConstraints();
if ("True".equalsIgnoreCase(attacking)) {
defs = (FCollection<GameEntity>) combat.getDefenders();
} else if (sa.hasParam("ChoosePlayerOrPlaneswalker")) {
if (sa.hasParam("ChoosePlayerOrPlaneswalker")) {
PlayerCollection defendingPlayers = AbilityUtils.getDefinedPlayers(host, attacking, sa);
defs = new FCollection<>();
for (Player p : defendingPlayers) {
defs.addAll(game.getCombat().getDefendersControlledBy(p));
}
} else if ("True".equalsIgnoreCase(attacking)) {
defs = (FCollection<GameEntity>) combat.getDefenders();
} else {
defs = AbilityUtils.getDefinedEntities(host, attacking, sa);
}
@@ -593,8 +593,7 @@ public abstract class SpellAbilityEffect {
Player chooser;
if (sa.hasParam("Chooser")) {
chooser = Iterables.getFirst(AbilityUtils.getDefinedPlayers(host, sa.getParam("Chooser"), sa), null);
}
else {
} else {
chooser = controller;
}
defender = chooser.getController().chooseSingleEntityForEffect(defs, sa,

View File

@@ -164,7 +164,6 @@ public class DigUntilEffect extends SpellAbilityEffect {
game.getAction().reveal(revealed, p, false);
}
if (foundDest != null) {
// Allow ordering of found cards
if ((foundDest.isKnown()) && found.size() >= 2 && !foundDest.equals(ZoneType.Exile)) {

View File

@@ -52,7 +52,7 @@ public class DrawEffect extends SpellAbilityEffect {
int actualNum = numCards;
if (upto) {
actualNum = p.getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblHowManyCardDoYouWantDraw"),0, numCards);
actualNum = p.getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblHowManyCardDoYouWantDraw"), 0, numCards);
}
final CardCollectionView drawn = p.drawCards(actualNum, sa);

View File

@@ -132,7 +132,6 @@ public class CostReturn extends CostPartWithList {
return "ReturnedCards";
}
public <T> T accept(ICostVisitor<T> visitor) {
return visitor.visit(this);
}

View File

@@ -294,7 +294,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
}
if (this.getShareAllColors() != null) {
List<Card> tgts = AbilityUtils.getDefinedCards(sa.getHostCard(), this.getShareAllColors(), sa);
List<Card> tgts = AbilityUtils.getDefinedCards(host, this.getShareAllColors(), sa);
Card first = Iterables.getFirst(tgts, null);
if (first == null) {
return false;
@@ -324,11 +324,11 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
return false;
}
if ((this.getActivationLimit() != -1) && (sa.getActivationsThisTurn() >= this.getActivationLimit())) {
if (this.getActivationLimit() != -1 && sa.getActivationsThisTurn() >= this.getActivationLimit()) {
return false;
}
if ((this.getGameActivationLimit() != -1) && (sa.getActivationsThisGame() >= this.getGameActivationLimit())) {
if (this.getGameActivationLimit() != -1 && sa.getActivationsThisGame() >= this.getGameActivationLimit()) {
return false;
}
@@ -352,7 +352,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
}
if (this.getColorToCheck() != null) {
if (!sa.getHostCard().hasChosenColor(this.getColorToCheck())) {
if (!host.hasChosenColor(this.getColorToCheck())) {
return false;
}
}
@@ -365,7 +365,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
list = new FCollection<GameObject>(game.getCardsIn(getPresentZone()));
}
final int left = Iterables.size(Iterables.filter(list, GameObjectPredicates.restriction(getIsPresent().split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa)));
final int left = Iterables.size(Iterables.filter(list, GameObjectPredicates.restriction(getIsPresent().split(","), sa.getActivatingPlayer(), host, sa)));
final String rightString = this.getPresentCompare().substring(2);
int right = AbilityUtils.calculateAmount(host, rightString, sa);
@@ -378,9 +378,9 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
if (this.getPlayerContains() != null) {
List<Player> list = new ArrayList<>();
if (this.getPlayerDefined() != null) {
list.addAll(AbilityUtils.getDefinedPlayers(sa.getHostCard(), this.getPlayerDefined(), sa));
list.addAll(AbilityUtils.getDefinedPlayers(host, this.getPlayerDefined(), sa));
}
List<Player> contains = AbilityUtils.getDefinedPlayers(sa.getHostCard(), this.getPlayerContains(), sa);
List<Player> contains = AbilityUtils.getDefinedPlayers(host, this.getPlayerContains(), sa);
if (contains.isEmpty() || !list.containsAll(contains)) {
return false;
}
@@ -417,7 +417,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
boolean result = false;
for (final GameObject o : matchTgt.getFirstTargetedSpell().getTargets()) {
if (o.isValid(this.getTargetValidTargeting().split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa)) {
if (o.isValid(this.getTargetValidTargeting().split(","), sa.getActivatingPlayer(), host, sa)) {
result = true;
break;
}
@@ -448,7 +448,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
}
if (StringUtils.isNotEmpty(getManaSpent())) {
SpellAbility castSa = sa.getHostCard().getCastSA();
SpellAbility castSa = host.getCastSA();
if (castSa == null) {
return false;
}
@@ -457,7 +457,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
}
}
if (StringUtils.isNotEmpty(getManaNotSpent())) {
SpellAbility castSa = sa.getHostCard().getCastSA();
SpellAbility castSa = host.getCastSA();
if (castSa != null && castSa.getPayingColors().hasAllColors(ColorSet.fromNames(getManaNotSpent().split(" ")).getColor())) {
return false;
}

View File

@@ -5,8 +5,8 @@ PT:*/4
K:Vigilance
S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | Description$ CARDNAME's power is equal to the number of creatures you control.
SVar:X:Count$Valid Creature.YouCtrl
T:Mode$ AttackersDeclared | AttackingPlayer$ You | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever you attack, for each opponent, create a 1/1 white Human creature token that's tapped and attacking that player or a planeswalker they control.
SVar:TrigToken:DB$ Token | TokenAmount$ Y | TokenScript$ w_1_1_human | TokenTapped$ True | TokenAttacking$ True | ChoosePlayerOrPlaneswalker$ True | TokenOwner$ You
SVar:Y:PlayerCountOpponents$Amount
T:Mode$ AttackersDeclared | AttackingPlayer$ You | Execute$ DBRepeat | TriggerZones$ Battlefield | TriggerDescription$ Whenever you attack, for each opponent, create a 1/1 white Human creature token that's tapped and attacking that player or a planeswalker they control.
SVar:DBRepeat:DB$ RepeatEach | RepeatPlayers$ Opponent | ChangeZoneTable$ True | RepeatSubAbility$ DBToken
SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_human | TokenTapped$ True | TokenAttacking$ Remembered | ChoosePlayerOrPlaneswalker$ True | TokenOwner$ You
DeckHas:Ability$Token
Oracle:Vigilance\nAdeline, Resplendent Cathar's power is equal to the number of creatures you control.\nWhenever you attack, for each opponent, create a 1/1 white Human creature token that's tapped and attacking that player or a planeswalker they control.

View File

@@ -5,5 +5,5 @@ A:SP$ DealDamage | Cost$ X X R | ValidTgts$ Creature,Player,Planeswalker | TgtPr
SVar:MaxTgts:PlayerCountPlayers$Amount/Plus.NumCreatures
SVar:NumCreatures:Count$Valid Creature,Planeswalker
SVar:X:Count$xPaid
K:Flashback:R R Discard<X/Card/card>
K:Flashback:R R Discard<X/Card/cards>
Oracle:Conflagrate deals X damage divided as you choose among any number of targets.\nFlashback—{R}{R}, Discard X cards. (You may cast this card from your graveyard for its flashback cost. Then exile it.)

View File

@@ -4,6 +4,6 @@ Types:Artifact
K:CARDNAME doesn't untap during your untap step.
A:AB$ DealDamage | Cost$ 4 T | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 2 | SpellDescription$ CARDNAME deals 2 damage to any target.
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUntap | TriggerDescription$ At the beginning of your upkeep, you may discard a card. If you do, untap CARDNAME.
SVar:TrigUntap:AB$Untap | Cost$ Discard<1/Card> | Defined$ Self
SVar:TrigUntap:AB$ Untap | Cost$ Discard<1/Card> | Defined$ Self
SVar:Picture:http://www.wizards.com/global/images/magic/general/elaborate_firecannon.jpg
Oracle:Elaborate Firecannon doesn't untap during your untap step.\n{4}, {T}: Elaborate Firecannon deals 2 damage to any target.\nAt the beginning of your upkeep, you may discard a card. If you do, untap Elaborate Firecannon.

View File

@@ -4,7 +4,7 @@ Types:Creature Cat Beast
PT:4/6
K:Vigilance
K:Lifelink
T:Mode$Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | LifeTotal$ You | LifeAmount$ GE40 | Execute$ TrigWin | TriggerDescription$ At the beginning of your upkeep, if you have 40 or more life, you win the game.
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | LifeTotal$ You | LifeAmount$ GE40 | Execute$ TrigWin | TriggerDescription$ At the beginning of your upkeep, if you have 40 or more life, you win the game.
SVar:TrigWin:DB$ WinsGame | Defined$ You
DeckHints:Ability$LifeGain
SVar:Picture:http://www.wizards.com/global/images/magic/general/felidar_sovereign.jpg

View File

@@ -3,7 +3,7 @@ ManaCost:3 G G
Types:Creature Insect
PT:6/1
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Graveyard | IsPresent$ Card.StrictlySelf | PresentZone$ Graveyard | PresentPlayer$ You | OptionalDecider$ You | Execute$ TrigChange | TriggerDescription$ At the beginning of your upkeep, if CARDNAME is in your graveyard, you may discard a card. If you do, return CARDNAME to your hand.
SVar:TrigChange:AB$ChangeZone | Cost$ Discard<1/Card> | Origin$ Graveyard | Destination$ Hand | Defined$ Self
SVar:TrigChange:AB$ ChangeZone | Cost$ Discard<1/Card> | Origin$ Graveyard | Destination$ Hand | Defined$ Self
K:Shroud
SVar:Picture:http://www.wizards.com/global/images/magic/general/gigapede.jpg
Oracle:Shroud (This creature can't be the target of spells or abilities.)\nAt the beginning of your upkeep, if Gigapede is in your graveyard, you may discard a card. If you do, return Gigapede to your hand.