spReturnTgt
Post MTG Forge Related Programming Questions Here
Moderators: timmermac, Blacksmith, KrazyTheFox, Agetian, friarsol, CCGHQ Admins
3 posts
• Page 1 of 1
spReturnTgt
by Chris H. » 09 Oct 2010, 00:03
Third draft+ of an overdue keyword, spReturnTgt. This will replace the spRaiseDead keyword. It will also handle Resurrect type spells as it can be set to return a card from graveyard to hand or battlefield. This keyword will now return cards from graveyard to the top of library.
This keyword required two new methods to be added to SpellAbility.java. The new methods getTargetList() and setTargetList() is needed for the spReturnTgt input. Some of the spReturnTgt cards return more than just a single card and I needed something similar to getTargetCard() and setTargetCard().
A null bug appeared in setTargetList() sometime after ForgeSVN r2699 and this should now be fixed.
Urborg Uprising was initially causing a null bug but this has now been fixed.
Syntax:
Examples:
SpellAbility.getTargetList() and setTargetList() code:
Keyword code:
CardFActoryUtil input code:
EDIT 1: updated keyword code
EDIT 2: updated setTargetList() code and fixed bug
EDIT 3: added Buyback
EDIT 4: added Drawback
EDIT 5: added "any number" of target cards.
EDIT 6: several existing cards have been converted to this keyword.
This keyword required two new methods to be added to SpellAbility.java. The new methods getTargetList() and setTargetList() is needed for the spReturnTgt input. Some of the spReturnTgt cards return more than just a single card and I needed something similar to getTargetCard() and setTargetCard().
A null bug appeared in setTargetList() sometime after ForgeSVN r2699 and this should now be fixed.
Urborg Uprising was initially causing a null bug but this has now been fixed.
Syntax:
- Code: Select all
spReturnTgt:{Num Cards/Parameters}:{Type}:{To Zone}:{DrawBack}:{Spell Desc}
Examples:
- Code: Select all
Name:Call to Mind
ManaCost:2 U
Types:Sorcery
Text:Return target instant or sorcery card from your graveyard to your hand.
K:spReturnTgt:1:Instant,Sorcery:Hand
SVar:Rarity:Uncommon
SVar:Picture:http://www.wizards.com/global/images/magic/general/call_to_mind_.jpg
End
Name:Call to the Netherworld
ManaCost:B
Types:Sorcery
Text:Return target black creature card from your graveyard to your hand.
K:spReturnTgt:1:Creature.Black:Hand
K:Madness:0
SVar:Rarity:Common
SVar:Picture:http://www.wizards.com/global/images/magic/general/call_to_the_netherworld_.jpg
End
Name:Raise Dead
ManaCost:B
Types:Sorcery
Text:Return target creature card from your graveyard to your hand.
K:spReturnTgt:1:Creature:Hand
SVar:Rarity:Common
SVar:Picture:http://resources.wizards.com/magic/cards/9ed/en-us/card83220.jpg
End
Name:Reclaim
ManaCost:G
Types:Instant
Text:Put target card from your graveyard on top of your library.
K:spReturnTgt:1:Card:TopofLibrary
SVar:RemAIDeck:True
SVar:Rarity:Common
SVar:Picture:http://www.wizards.com/global/images/magic/general/reclaim.jpg
End
Name:Reconstruction
ManaCost:U
Types:Sorcery
Text:Return target artifact card from your graveyard to your hand.
K:spReturnTgt:1:Artifact:Hand
SVar:Rarity:Common
SVar:Picture:http://www.wizards.com/global/images/magic/general/reconstruction.jpg
End
Name:Resurrection
ManaCost:2 W W
Types:Sorcery
Text:Return target creature card from your graveyard to the battlefield.
K:spReturnTgt:1:Creature:Battlefield
SVar:Rarity:Uncommon
SVar:Picture:http://www.wizards.com/global/images/magic/general/resurrection.jpg
End
SpellAbility.getTargetList() and setTargetList() code:
- Code: Select all
public CardList getTargetList() {
if (targetList == null) return null;
return targetList;
}
public void setTargetList(CardList list) {
// The line below started to create a null error at forge.CardFactoryUtil.canTarget(CardFactoryUtil.java:3329)
// after ForgeSVN r2699. I hope that commenting out the line below will not result in other bugs. :)
// targetPlayer = null;//reset setTargetPlayer()
targetList = list;
StringBuilder sb = new StringBuilder();
sb.append(getSourceCard().getName()).append(" - targeting ");
for (int i = 0; i < targetList.size(); i++) {
if (!targetList.get(i).isFaceDown()) sb.append(targetList.get(i));
else sb.append("Morph(").append(targetList.get(i).getUniqueNumber()).append(")");
if (i < targetList.size() - 1) sb.append(", ");
}
setStackDescription(sb.toString());
}
Keyword code:
- Code: Select all
/**
* Generic return target card(s) from graveyard to Hand, Battlefield or Top of Library
* spReturnTgt:{Num Cards/Parameters}:{Type}:{To Zone}:{DrawBack}:{Spell Desc}
*
* X Count/Costs are not yet implemented.
*/
if (hasKeyword(card, "spReturnTgt") != -1) {
int n = hasKeyword(card, "spReturnTgt");
String parse = card.getKeyword().get(n).toString();
card.removeIntrinsicKeyword(parse);
String k[] = parse.split(":");
final boolean returnUpTo[] = {false};
final boolean anyNumber[] = {false};
final int numCardsToReturn;
String np[] = k[1].split("/");
if (np[0].equals("AnyNumber")) {
anyNumber[0] = true;
numCardsToReturn = 0;
} else {
numCardsToReturn = Integer.parseInt(np[0]);
}
if (np.length > 1) {
if (np[1].equals("UpTo")) {
returnUpTo[0] = true;
}
}
// Artifact, Creature, Enchantment, Land, Permanent, Instant, Sorcery, Card
// White, Blue, Black, Red, Green, Colorless, MultiColor
// non-Artifact, non-Creature, non-Enchantment, non-Land, non-Permanent,
// non-White, non-Blue, non-Black, non-Red, non-Green, non-Colorless, non-MultiColor
String Targets = k[2];
final String Tgts[] = Targets.split(",");
final String Destination = k[3];
String desc = "";
final String Drawback[] = {"none"};
if (k.length > 4) {
if (k[4].contains("Drawback$")){
String kk[] = k[4].split("\\$");
Drawback[0] = kk[1];
} else {
desc = k[4];
}
}
if (k.length > 5) {
desc = k[5];
}
final SpellAbility spRtrnTgt = new Spell(card) {
private static final long serialVersionUID = 7970018872459137897L;
@Override
public boolean canPlay() {
if (returnUpTo[0] || anyNumber[0]) return true;
return getGraveyardList().size() >= numCardsToReturn;
}
@Override
public boolean canPlayAI() {
if (AllZone.Phase.getTurn() <= 3) return false;
CardList results = new CardList();
CardList choices = getGraveyardList();
// We want cards like Footbottom Feast to return at least two cards
if (anyNumber[0]
&& choices.size() >= 2) {
choices.shuffle();
setTargetList(choices);
return true;
}
if (choices.size() > 0) {
for (int nctr = 0; nctr < numCardsToReturn; nctr ++) {
for (int i = 0; i < Tgts.length; i++) {
if (Tgts[i].startsWith("Artifact")) {
if (CardFactoryUtil.AI_getBestArtifact(choices) != null) {
Card c = CardFactoryUtil.AI_getBestArtifact(choices);
results.add(c);
choices.remove(c);
}
} else if (Tgts[i].startsWith("Creature")) {
if (CardFactoryUtil.AI_getBestCreature(choices) != null) {
Card c = CardFactoryUtil.AI_getBestCreature(choices);
results.add(c);
choices.remove(c);
}
} else if (Tgts[i].startsWith("Enchantment")) {
if (CardFactoryUtil.AI_getBestEnchantment(choices, card, true) != null) {
Card c = CardFactoryUtil.AI_getBestEnchantment(choices, card, true);
results.add(c);
choices.remove(c);
}
} else if (Tgts[i].startsWith("Land")) {
if (CardFactoryUtil.AI_getBestLand(choices) != null) {
Card c = CardFactoryUtil.AI_getBestLand(choices);
results.add(c);
choices.remove(c);
}
} else if (Tgts[i].startsWith("Permanent")) {
if (CardFactoryUtil.AI_getMostExpensivePermanent(choices, card, true) != null) {
Card c = CardFactoryUtil.AI_getMostExpensivePermanent(choices, card, true);
results.add(c);
choices.remove(c);
}
} else if (Tgts[i].startsWith("Instant")) {
if (CardFactoryUtil.AI_getMostExpensivePermanent(choices, card, true) != null) {
// Card c = CardFactoryUtil.AI_getMostExpensivePermanent(choices, card, true);
Card c = CardFactoryUtil.getRandomCard(choices);
results.add(c);
choices.remove(c);
}
} else if (Tgts[i].startsWith("Sorcery")) {
if (CardFactoryUtil.AI_getMostExpensivePermanent(choices, card, true) != null) {
// Card c = CardFactoryUtil.AI_getMostExpensivePermanent(choices, card, true);
Card c = CardFactoryUtil.getRandomCard(choices);
results.add(c);
choices.remove(c);
}
} else {
if (CardFactoryUtil.AI_getMostExpensivePermanent(choices, card, true) != null) {
// Card c = CardFactoryUtil.AI_getMostExpensivePermanent(choices, card, true);
Card c = CardFactoryUtil.getRandomCard(choices);
results.add(c);
choices.remove(c);
}
}
}// for i
}// for nctr
}// if choices
if (!anyNumber[0]) {
CardList targets = new CardList();
if (results.size() >= numCardsToReturn) {
results.shuffle();
for (int i = 0; i < numCardsToReturn; i++) {
targets.add(results.get(i));
}
} else if (results.size() >= 1
&& returnUpTo[0]) {
targets = results;
}
if (targets.size() > 0) {
setTargetList(targets);
return true;
}
}
return false;
}// canPlayAI()
@Override
public void resolve() {
CardList targets = getTargetList();
PlayerZone grave = AllZone.getZone(Constant.Zone.Graveyard, card.getController());
String player = card.getController();
for (Card c:targets) {
if (AllZone.GameAction.isCardInZone(c, grave)) {
if (Destination.equals("Hand")) {
PlayerZone zone = AllZone.getZone(Constant.Zone.Hand, player);
AllZone.GameAction.moveTo(zone, c);
}
else if (Destination.equals("Battlefield")) {
PlayerZone zone = AllZone.getZone(Constant.Zone.Play, player);
AllZone.GameAction.moveTo(zone, c);
}
else if (Destination.equals("TopofLibrary")) {
// PlayerZone zone = AllZone.getZone(Constant.Zone.Play, player);
AllZone.GameAction.moveToTopOfLibrary(c);
}
}
}// for
if (!Drawback[0].equals("none")) {
CardFactoryUtil.doDrawBack(Drawback[0], 0, card.getController(), AllZone.GameAction.getOpponent(
card.getController()), card.getController(), card, card, this);
}
}// resolve()
CardList getGraveyardList() {
String player = card.getController();
PlayerZone grave = AllZone.getZone(Constant.Zone.Graveyard, player);
CardList list = new CardList(grave.getCards());
list = list.getValidCards(Tgts);
// AI will not use a Boggart Birth Rite to return a Boggart Birth Rite.
// In testing the AI targeted a Sage's Knowledge with a Deja Vu.
// Fixed this by having AI pick a random Instant or Sorcery
// rather than picking the card with highest casting cost.
if (card.getController().equals(Constant.Player.Computer)) {
list = list.getNotName(card.getName());
if (Destination.equals("Battlefield")
&& !AllZone.Phase.getPhase().equals(Constant.Phase.Main1)) {
list = list.getNotKeyword("At the beginning of the end step, destroy CARDNAME.");
list = list.getNotKeyword("At the beginning of the end step, exile CARDNAME.");
list = list.getNotKeyword("At the beginning of the end step, sacrifice CARDNAME.");
}
/* // I failed to solve the problem above with this code.
CardList tmp = list;
for (int i = 0; i < tmp.size(); i++) {
ArrayList<String> kw = tmp.get(i).getKeyword();
for (int j = 0; j < kw.size(); j++) {
if (kw.get(j).toString().startsWith("spReturnTgt")) {
list.remove(kw.get(j));
}
}
}
*/
}
return list;
}// getGraveyardList()
};// spRtrnTgt
spRtrnTgt.setBeforePayMana(CardFactoryUtil.spReturnTgt_input_targetCards_InGraveyard(
card, spRtrnTgt, returnUpTo[0], numCardsToReturn, Tgts, anyNumber[0]));
if (desc.length() > 0) {
spRtrnTgt.setDescription(desc);
}
card.clearSpellAbility();
card.addSpellAbility(spRtrnTgt);
if (Destination.equals("Hand")) {
card.setSVar("PlayMain1", "TRUE");
}
String bbCost = card.getSVar("Buyback");
if (!bbCost.equals("")) {
SpellAbility bbRtrnTgt = spRtrnTgt.copy();
bbRtrnTgt.setManaCost(CardUtil.addManaCosts(card.getManaCost(), bbCost));
StringBuilder sb = new StringBuilder();
sb.append("Buyback ").append(bbCost).append(" (You may pay an additional ").append(bbCost);
sb.append(" as you cast this spell. If you do, put this card into your hand as it resolves.)");
bbRtrnTgt.setDescription(sb.toString());
bbRtrnTgt.setIsBuyBackAbility(true);
bbRtrnTgt.setBeforePayMana(CardFactoryUtil.spReturnTgt_input_targetCards_InGraveyard(
card, bbRtrnTgt, returnUpTo[0], numCardsToReturn, Tgts, anyNumber[0]));
card.addSpellAbility(bbRtrnTgt);
}
}// spReturnTgt
CardFActoryUtil input code:
- Code: Select all
public static Input spReturnTgt_input_targetCards_InGraveyard(
final Card card, final SpellAbility spell, final boolean UpTo,
final int numCards, final String Tgts[], final boolean anyNumber) {
Input target = new Input() {
private static final long serialVersionUID = 816838038180106359L;
@Override
public void showMessage() {
CardList grave = getGraveyardList();
CardList targets = new CardList();
if (UpTo) {
for (int i = 0; i < numCards; i++) {
if (grave.size() > 0) {
Object o = AllZone.Display.getChoiceOptional("Select a card", grave.toArray());
if (o == null) break;
Card c = (Card) o;
targets.add(c);
grave.remove(c);
}
}
} else if (anyNumber) {
int max = grave.size();
for (int i = 0; i < max; i++) {
if (grave.size() > 0) {
Object o = AllZone.Display.getChoiceOptional("Select a card", grave.toArray());
if (o == null) break;
Card c = (Card) o;
targets.add(c);
grave.remove(c);
}
}
} else if (grave.size() > numCards) {
for (int i = 0; i < numCards; i++) {
Object o = AllZone.Display.getChoice("Select a card", grave.toArray());
Card c = (Card) o;
targets.add(c);
grave.remove(c);
}
} else if (grave.size() == numCards) {
targets = grave;
}
if (targets.size() > 0) {
spell.setTargetList(targets);
stopSetNext(new Input_PayManaCost(spell));
} else stop();
}// showMessage()
public CardList getGraveyardList() {
CardList list = new CardList();
PlayerZone zone = AllZone.getZone(Constant.Zone.Graveyard, card.getController());
list.addAll(zone.getCards());
list = list.getValidCards(Tgts);
return list;
}
};// Input
return target;
}//spReturnTgt_input_targetCards_InGraveyard()
EDIT 1: updated keyword code
EDIT 2: updated setTargetList() code and fixed bug
EDIT 3: added Buyback
EDIT 4: added Drawback
EDIT 5: added "any number" of target cards.
EDIT 6: several existing cards have been converted to this keyword.
-
Chris H. - Forge Moderator
- Posts: 6320
- Joined: 04 Nov 2008, 12:11
- Location: Mac OS X Yosemite
- Has thanked: 644 times
- Been thanked: 643 times
Re: spReturnTgt
by Sloth » 09 Oct 2010, 09:29
Ah, finaly. Good work Chris!
There were lots of requests for some of the cards now possible (i.e. Call to Mind).
May I suggest TopOfLibrary as another option of {To Zone} for cards like Salvage and Reinforcements. Don't bother with canPlayAI, these cards can't be used effectively by the AI in any way.
There were lots of requests for some of the cards now possible (i.e. Call to Mind).
May I suggest TopOfLibrary as another option of {To Zone} for cards like Salvage and Reinforcements. Don't bother with canPlayAI, these cards can't be used effectively by the AI in any way.
-
Sloth - Programmer
- Posts: 3498
- Joined: 23 Jun 2009, 19:40
- Has thanked: 125 times
- Been thanked: 507 times
Re: spReturnTgt
by Chris H. » 10 Oct 2010, 00:47
Thank you Sloth. Over the last year I tried to finish this keyword conversion. Sometimes I got stuck with other work or I could not figure ourt how to pass a list of target cards from the input to the SpellAbility Resolve method.
There is still some more work to do on it. We now have 31 cards using this keyword. There are a few more that I might be able to add.
There is still some more work to do on it. We now have 31 cards using this keyword. There are a few more that I might be able to add.
-
Chris H. - Forge Moderator
- Posts: 6320
- Joined: 04 Nov 2008, 12:11
- Location: Mac OS X Yosemite
- Has thanked: 644 times
- Been thanked: 643 times
3 posts
• Page 1 of 1
Who is online
Users browsing this forum: No registered users and 42 guests