Programming a card
Post MTG Forge Related Programming Questions Here
Moderators: timmermac, Blacksmith, KrazyTheFox, Agetian, friarsol, CCGHQ Admins
Re: Programming a card
by Triadasoul » 07 Dec 2009, 19:18
I wanted to add Transmute keyword (it's very similar to cycling). Where else except CardFactoryUtil should i add a code ?
- Code: Select all
public static SpellAbility ability_transmute(final Card sourceCard, final String transmuteCost)
{
final SpellAbility transmute = new Ability_Hand(sourceCard, transmuteCost)
{
private static final long serialVersionUID = -4960704261761785512L;
public boolean canPlayAI() {return false;}
public boolean canPlay()
{
if ((AllZone.Phase.getPhase().equals(Constant.Phase.Main2)&& AllZone.Phase.getActivePlayer() == sourceCard.getController()) || (AllZone.Phase.getPhase().equals(Constant.Phase.Main1) && AllZone.Phase.getActivePlayer() == sourceCard.getController()) )
return true;
else
return false;
}
public void resolve()
{
PlayerZone lib = AllZone.getZone(Constant.Zone.Library, sourceCard.getController());
PlayerZone hand = AllZone.getZone(Constant.Zone.Hand, sourceCard.getController());
CardList cards = new CardList(lib.getCards());
CardList sameCost = new CardList();
for (int i=0;i<cards.size();i++)
{
if( CardUtil.getConvertedManaCost(cards.get(i).getManaCost()) == CardUtil.getConvertedManaCost(sourceCard.getManaCost()) )
{
sameCost.add(cards.get(i));
}
}
if(sameCost.size() == 0)
return;
Object o = AllZone.Display.getChoiceOptional("Select a card", sameCost.toArray());
if(o != null)
{
//ability.setTargetCard((Card)o);
//AllZone.Stack.add(ability);
Card c1 = (Card)o;
lib.remove(c1);
hand.add(c1);
}
AllZone.GameAction.shuffle(sourceCard.getController());
}
};
transmute.setDescription("Transmute " +transmuteCost +" (" +transmuteCost +", Discard this card: Search your library for a card with the same converted mana cost as the discarded card, reveal that card, and put it into your hand. Then shuffle your library. Play this ability only any time you could play a sorcery.)");
transmute.setStackDescription(sourceCard +" Transmute: Search your library for a card with the same converted mana cost.");
return transmute;
}//ability_transmute()
- Triadasoul
- Posts: 223
- Joined: 21 Jun 2008, 20:17
- Has thanked: 0 time
- Been thanked: 4 times
Re: Programming a card
by mtgrares » 07 Dec 2009, 20:06
I briefly looked over the code. Your SpellAbility.canPlay() doesn't check to see if the card is in your hand. The easiest way to do that is to call super.canPlay() which calls Ability_Hand.canPlay() which just verifies that the card is in your hand.
And since I'm a perfectionist, here is the code from Ability_Hand.canPlay(). (The Serializable stuff can be removed. I used to write stuff to the hard drive as a primitive undo function.)
And since I'm a perfectionist, here is the code from Ability_Hand.canPlay(). (The Serializable stuff can be removed. I used to write stuff to the hard drive as a primitive undo function.)
- Code: Select all
abstract public class Ability_Hand extends SpellAbility implements java.io.Serializable
{
public boolean canPlay()
{
PlayerZone zone = AllZone.getZone(getSourceCard());
return zone.is(Constant.Zone.Hand, getSourceCard().getController());
}
etc... other methods
}
- mtgrares
- DEVELOPER
- Posts: 1352
- Joined: 08 Sep 2008, 22:10
- Has thanked: 3 times
- Been thanked: 12 times
Re: Programming a card
by DennisBergkamp » 07 Dec 2009, 20:54
Ah yes, it looks like you should add && super.canPlay() into canPlay().
I think you also need to actually add the ability in CardFactory and CardFactory_Creatures to the card, from a keyword I assume. The code will be very close to the cycling code, I would also add it right after that:
By the way, I had another thought about Dovescape.... it seems if you add it at the top of executePlayCardEffects(), like so:
I think you also need to actually add the ability in CardFactory and CardFactory_Creatures to the card, from a keyword I assume. The code will be very close to the cycling code, I would also add it right after that:
- Code: Select all
if (hasKeyword(card, "Cycling") != -1)
{
int n = hasKeyword(card, "Cycling");
if (n != -1)
{
String parse = card.getKeyword().get(n).toString();
card.removeIntrinsicKeyword(parse);
String k[] = parse.split(":");
final String manacost = k[1];
card.addSpellAbility(CardFactoryUtil.ability_cycle(card, manacost));
}
}//Cycling
By the way, I had another thought about Dovescape.... it seems if you add it at the top of executePlayCardEffects(), like so:
- Code: Select all
public static void executePlayCardEffects(SpellAbility sa)
{
// experimental:
// this method check for cards that have triggered abilities whenever a
// card gets played
// (called in MagicStack.java)
Card c = sa.getSourceCard();
playCard_Dovescape(c); //keep this one top
playCard_Emberstrike_Duo(c);
playCard_Gravelgill_Duo(c);
.... etc.
-
DennisBergkamp - AI Programmer
- Posts: 2602
- Joined: 09 Sep 2008, 15:46
- Has thanked: 0 time
- Been thanked: 0 time
Re: Programming a card
by Marek14 » 07 Dec 2009, 22:01
If you're doing Transmute, how about landcycling? Generalized XXXcycling code would allow for Scourge landcyclers (Chartooth Cougar, Elvish Aberration, Eternal Dragon, Noble Templar, Shoreline Ranger, Twisted Abomination and Wirewood Guardian), Future Sight cyclers (Homing Sliver [that would be harder] and Vedalken AEthermage), Conflux basic landcyclers (Absorb Vis, Fiery Fall, Gleam of Resistance, Sylvan Bounty and Traumatic Visions), and Alara Reborn double landcyclers (Igneous Pouncer, Jhessian Zombies, Pale Recluse, Sanctum Plowbeast and Valley Rannet)
Re: Programming a card
by Triadasoul » 08 Dec 2009, 06:49
Thank you for your advises and feedback
! I've just added transmute keyword, and found that cycling and transmute doesn't work for lands. What code should i add to CardFactory_Lands? Simply copying code for cycling/transmute from Cardfactory_Creatures doesn't work here.
Also i had a problem coding Honden of Seeing Winds within spell ability. For example Honden of Cleansing Fire works fine within ability:

Also i had a problem coding Honden of Seeing Winds within spell ability. For example Honden of Cleansing Fire works fine within ability:
- Code: Select all
private static void upkeep_Honden_of_Cleansing_Fire()
{
final String player = AllZone.Phase.getActivePlayer();
PlayerZone play = AllZone.getZone(Constant.Zone.Play, player);
CardList list = new CardList();
list.addAll(play.getCards());
list = list.getName("Honden of Cleansing Fire");
for (int i = 0; i < list.size(); i++) {
final Ability ability = new Ability(list.get(i), "0")
{
public void resolve() {
PlayerZone Play = AllZone.getZone(Constant.Zone.Play, player);
CardList hondlist = new CardList();
hondlist.addAll(Play.getCards());
hondlist = hondlist.getType("Shrine");
for (int j = 0; j < hondlist.size() ; j++)
{
AllZone.GameAction.getPlayerLife(player).addLife(2);
}}
};
ability.setStackDescription(list.get(i)+" - " + list.get(i).getController() + " gains 2 life for each Shrine he controls.");
AllZone.Stack.add(ability);
}
}// upkeep_Honden_of_Cleansing_Fire
- Code: Select all
private static void upkeep_Honden_of_Seeing_Winds()
{
final String player = AllZone.Phase.getActivePlayer();
PlayerZone play = AllZone.getZone(Constant.Zone.Play, player);
CardList list = new CardList();
list.addAll(play.getCards());
list = list.getName("Honden of Seeing Winds");
for (int i = 0; i < list.size(); i++) {
final Ability ability2 = new Ability(list.get(i), "0")
{
public void resolve() {
PlayerZone Play = AllZone.getZone(Constant.Zone.Play, player);
CardList hondlist = new CardList();
hondlist.addAll(Play.getCards());
hondlist = hondlist.getType("Shrine");
for (int j = 0; j < hondlist.size() ; j++)
{
AllZone.GameAction.drawCard(player);
}}
};
ability2.setStackDescription(list.get(i)+" - " + list.get(i).getController() + " draws a card for each Shrine he controls.");
AllZone.Stack.add(ability2);
}
}// upkeep_Honden_of_Seeing_Winds
- Code: Select all
private static void upkeep_Honden_of_Seeing_Winds()
{
final String player = AllZone.Phase.getActivePlayer();
PlayerZone play = AllZone.getZone(Constant.Zone.Play, player);
CardList list = new CardList();
list.addAll(play.getCards());
list = list.getName("Honden of Seeing Winds");
for (int i = 0; i < list.size(); i++) {
// final Ability ability2 = new Ability(list.get(i), "0")
// {
// public void resolve() {
PlayerZone Play = AllZone.getZone(Constant.Zone.Play, player);
CardList hondlist = new CardList();
hondlist.addAll(Play.getCards());
hondlist = hondlist.getType("Shrine");
for (int j = 0; j < hondlist.size() ; j++)
{
AllZone.GameAction.drawCard(player);
}//}
// };
// ability2.setStackDescription(list.get(i)+" - " + list.get(i).getController() + " draws a card for each Shrine he controls.");
// AllZone.Stack.add(ability2);
}
}// upkeep_Honden_of_Seeing_Winds
- Triadasoul
- Posts: 223
- Joined: 21 Jun 2008, 20:17
- Has thanked: 0 time
- Been thanked: 4 times
Re: Programming a card
by zerker2000 » 08 Dec 2009, 08:11
Yes, apparently right now the draw step isn't separate from upkeep, and any attempt to draw during it will give you that turn's draw and nothing else. For multiple cycling, make the if a while
O forest, hold thy wand'ring son
Though fears assail the door.
O foliage, cloak thy ravaged one
In vestments cut for war.
--Eladamri, the Seed of Freyalise
Though fears assail the door.
O foliage, cloak thy ravaged one
In vestments cut for war.
--Eladamri, the Seed of Freyalise
- zerker2000
- Programmer
- Posts: 569
- Joined: 09 May 2009, 21:40
- Location: South Pasadena, CA
- Has thanked: 0 time
- Been thanked: 0 time
Re: Programming a card
by Triadasoul » 08 Dec 2009, 08:24
For typecycling it works. Thank you =)
And about Honden i use AllZone.GameAction.drawCard(player); in one case in the stack and in other out of it. In second case it works.
And about Honden i use AllZone.GameAction.drawCard(player); in one case in the stack and in other out of it. In second case it works.
- Triadasoul
- Posts: 223
- Joined: 21 Jun 2008, 20:17
- Has thanked: 0 time
- Been thanked: 4 times
Re: Programming a card
by Triadasoul » 08 Dec 2009, 11:53
And another question about Leechridden Swamp. Ai can simultaneously tap it for ability and pay for it with tapping the same land for mana. Is it a bug of my coding or it's the ai payment bug?
- Code: Select all
//*************** START *********** START **************************
else if(cardName.equals("Leechridden Swamp"))
{
//tap sacrifice
final Ability_Tap ability = new Ability_Tap(card, "B")
{
private static final long serialVersionUID = 5441640362881917927L;
public boolean canPlay()
{
CardList play = new CardList();
if(card.getController().equals(Constant.Player.Human))
play.addAll(AllZone.Human_Play.getCards());
else
play.addAll(AllZone.Computer_Play.getCards());
play = play.filter(new CardListFilter(){
public boolean addCard(Card c) {
return (CardUtil.getColors(c).contains(Constant.Color.Black));
}
});
if ( play.size()>=2 )
return true;
else return false;
}//canPlay()
public void resolve()
{
AllZone.GameAction.getPlayerLife(AllZone.GameAction.getOpponent(card.getController())).subtractLife(1);
}
};
card.addSpellAbility(ability);
ability.setStackDescription(card + " - Each opponent loses 1 life.");
ability.setDescription("tap, B: Each opponent loses 1 life. Activate this ability only if you control two or more black permanents.");
}//*************** END ************ END **************************
- Triadasoul
- Posts: 223
- Joined: 21 Jun 2008, 20:17
- Has thanked: 0 time
- Been thanked: 4 times
Re: Programming a card
by Chris H. » 08 Dec 2009, 12:23
`Triadasoul wrote:I've just added transmute keyword, and found that cycling and transmute doesn't work for lands.
Thank you for the work that you have submitted. The next beta release will be one of our biggest and best.

Someone noticed earlier this year that they could cycle a land during the computer's turn. Our code base has changed and it appears that we can no longer cycle lands during the computer's turn.
I will try to remember to add a NOTE to these lands and merge this into our SVN.
-
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: Programming a card
by Marek14 » 10 Dec 2009, 14:36
Early Worldwake card:
Leatherback Baloth
G G G
Creature Beast
no text
4/5
Of course, picture has to be supplied by hand...
http://forums.mtgsalvation.com/showthread.php?t=204435
There's another full card known (with a picture), but that's probably for you to implement. It can be probably done by slight modification of, say, Faerie Conclave.
http://forums.mtgsalvation.com/showthread.php?t=204793
Leatherback Baloth
G G G
Creature Beast
no text
4/5
Of course, picture has to be supplied by hand...
http://forums.mtgsalvation.com/showthread.php?t=204435
There's another full card known (with a picture), but that's probably for you to implement. It can be probably done by slight modification of, say, Faerie Conclave.
http://forums.mtgsalvation.com/showthread.php?t=204793
Re: Programming a card
by Triadasoul » 10 Dec 2009, 21:24
I want to add a soulshift keyword, but i don't know where should i add code for it, the main idea was to put it into gameactionutil in "destroy creature part" something like this:
- Code: Select all
public static int shouldSoulshift(Card c) {
ArrayList<String> a = c.getKeyword();
for (int i = 0; i < a.size(); i++)
if (a.get(i).toString().startsWith("Soulshift"))
return i;
return -1;
}
public static void executeDestroyCreatureCardEffects(Card c, Card destroyed)
{
if (c.getName().equals("Goblin Sharpshooter"))
destroyCreature_Goblin_Sharpshooter(c, destroyed);
while (shouldSoulshift(destroyed) != -1)
{
int n = shouldSoulshift((destroyed);
if (n != -1)
{
String parse = destroyed.getKeyword().get(n).toString();
destroyed.removeIntrinsicKeyword(parse);
String k[] = parse.split(":");
final String manacost = k[1];
AllZone.Stack.add((CardFactoryUtil.ability_soulshift(destroyed, manacost));
- Triadasoul
- Posts: 223
- Joined: 21 Jun 2008, 20:17
- Has thanked: 0 time
- Been thanked: 4 times
Re: Programming a card
by DennisBergkamp » 11 Dec 2009, 02:37
Yes, that's exactly where it should be put. Note though, that currently cards that trigger things on leaving play are a bit buggy, because mass destroy effects (WoG, Tranquility, ...) in Forge destroy things one by one, as opposed to simultaneously. Might not be a super big deal with Soulshift.
About Cycling/Transmute not working with lands... I hacked in a solution and it seems to work alright now, if a land has Cycling/Transmute (or any other ability that's an "Ability_Hand") a dialog will show up with the options "Play Land" and "Cycling:2". If a land has already been played that turn, (and no Exploration / Fastbond is in play), the cycling ability will be used.
I'll submit this to the SVN asap, but currently I'm still in the middle of fixing some more combat bugs.
About Cycling/Transmute not working with lands... I hacked in a solution and it seems to work alright now, if a land has Cycling/Transmute (or any other ability that's an "Ability_Hand") a dialog will show up with the options "Play Land" and "Cycling:2". If a land has already been played that turn, (and no Exploration / Fastbond is in play), the cycling ability will be used.
I'll submit this to the SVN asap, but currently I'm still in the middle of fixing some more combat bugs.
-
DennisBergkamp - AI Programmer
- Posts: 2602
- Joined: 09 Sep 2008, 15:46
- Has thanked: 0 time
- Been thanked: 0 time
Re: Programming a card
by Triadasoul » 11 Dec 2009, 08:04
I tried that code but it doesn't even trigger when creature with Soulshift is destroyed. I also put it in Cardfactory_Creatures so there could be a description for Soulshift, but it doesn't matter.
Cardfactoryutil
PS: I' tried to add "destroyCreature_Prowess_of_the_Fair(c, destroyed);" to Soulcatcher trigger and it works, but when i change
Cards.txt entry:
Cardfactoryutil
- Code: Select all
public static Ability ability_Soulshift(final Card sourceCard, final String Manacost)
{
final Ability Soulshift = new Ability(sourceCard,"0")
{
private static final long serialVersionUID = -4960704261761785512L;
public void resolve()
{
PlayerZone lib = AllZone.getZone(Constant.Zone.Graveyard, sourceCard.getController());
PlayerZone hand = AllZone.getZone(Constant.Zone.Hand, sourceCard.getController());
CardList cards = new CardList(lib.getCards());
CardList sameCost = new CardList();
for (int i=0;i<cards.size();i++)
{
if( CardUtil.getConvertedManaCost(cards.get(i).getManaCost()) <= CardUtil.getConvertedManaCost(sourceCard.getManaCost()) )
{
sameCost.add(cards.get(i));
}
}
if(sameCost.size() == 0)
return;
if (sourceCard.getController().equals(Constant.Player.Human)) {
Object o = AllZone.Display.getChoiceOptional("Select a card", sameCost.toArray());
if(o != null)
{
//ability.setTargetCard((Card)o);
//AllZone.Stack.add(ability);
Card c1 = (Card)o;
lib.remove(c1);
hand.add(c1);
}}
else //Wiser choice should be here
{
Card choice = null;
sameCost.shuffle();
choice = sameCost.getCard(0);
if (!(choice == null)) {
lib.remove(choice);
hand.add(choice);}
}
}
};
Soulshift.setDescription("Soulshift " +Manacost +" - When this permanent is put into a graveyard from play, you may return target Spirit card with converted mana cost " + Manacost + "or less from your graveyard to your hand.");
Soulshift.setStackDescription(sourceCard +"Soulshift " +sourceCard.getController()+ " may return target Spirit card with converted mana cost " + Manacost + "or less from his graveyard to his hand.");
return Soulshift;
}//ability_Soulshift()
PS: I' tried to add "destroyCreature_Prowess_of_the_Fair(c, destroyed);" to Soulcatcher trigger and it works, but when i change
- Code: Select all
else if (c.getName().equals("Soulcatcher") && destroyed.getKeyword().contains("Flying"))
destroyCreature_Soulcatcher(c, destroyed);
- Code: Select all
else if (c.getName().equals("Prowess of the Fair") && destroyed.getKeyword().contains("Flying"))
destroyCreature_Soulcatcher(c, destroyed);
Cards.txt entry:
- Code: Select all
Prowess of the Fair
1 B
Tribal Enchantment Elf
Whenever another nontoken Elf is put into your graveyard from the battlefield, you may put a 1/1 green Elf Warrior creature token onto the battlefield.
- Triadasoul
- Posts: 223
- Joined: 21 Jun 2008, 20:17
- Has thanked: 0 time
- Been thanked: 4 times
Re: Programming a card
by Triadasoul » 11 Dec 2009, 11:53
Is there an other way to get turn controller other then AllZone.Phase.getActivePlayer() ? (Because it returns opposite player during attack phase effects or abilities microphases)
- Triadasoul
- Posts: 223
- Joined: 21 Jun 2008, 20:17
- Has thanked: 0 time
- Been thanked: 4 times
Re: Programming a card
by DennisBergkamp » 12 Dec 2009, 01:15
Hmm, but shouldn't Prowess of the Fair be called through something like this:
- Code: Select all
else if (c.getName().equals("Prowess of the Fair") && destroyed.getType().contains("Elf")) && !destroyed.isToken() && !c.equals(destroyed) )
destroyCreature_Prowess_of_the_Fair(c, destroyed);
-
DennisBergkamp - AI Programmer
- Posts: 2602
- Joined: 09 Sep 2008, 15:46
- Has thanked: 0 time
- Been thanked: 0 time
Who is online
Users browsing this forum: Bing [Bot] and 41 guests