Wednesday, June 25, 2008

Static and Triggered Abilities

And here is more bad news, triggered abilities are just as hard to program as static ones. Triggered abilities begin with the words “when”, “whenever” or “at”. Hypnotic Specter says, “Whenever Hypnotic Specter deals damage to an opponent, that player discards a card at random.”

The bad news is the card sets like Shadowmoor have a ton of static and triggered abilities, which makes them fun to play with but hard to program. Remember: activated abilities easy, static and triggered hard.

Since MTG Forge doesn’t support events, triggered abilities are implemented as static abilities. MTG Forge only has a few cards with triggered abilities: Baru, Fist of Krosa, Reach of Branches, Soul Warden, and its color swapped twin Essence Warden.

For those intrepid readers who want to see the Java code for static abilities, just check out the class GameActionUtil and the method executeCardStateEffects(). This is a helper method that GameAction checkStateEffects() calls. checkStateEffects() is called every time the game’s state should be checked, which is a lot. But a word of warning GameActionUtil looks very weird. I combined highly advanced Java with insomnia at 2am and this is what I came up with.

Basically each card has its own class and all of the classes are executed by a single method, executeCardStateEffects(). The good news is that all of the code for each card is together, even though the overall code is messy.

Honestly I would have a very hard time writing this code again from scratch, but it works so I don’t mess with it. Essentially all of the cards are just variations on my original Glorious Anthem code which I ingloriously cut-and-pasted multiple times.
I love throwing code at you, even though I know that everybody isn’t a programmer. The code for Serra Avatar is the shortest and easiest to understand. Serra Avatar says that its power and toughness are equal to your life total.
private static Command Serra_Avatar = new Command()
{

public void execute()
{
CardList list = new CardList();
list.addAll(AllZone.Human_Play.getCards());

list.addAll(AllZone.Computer_Play.getCards());

list = list.getName("Serra Avatar");

for(int i = 0; i < list.size(); i++)
{
Card card = list.get(i);
int n = AllZone.GameAction.getPlayerLife(card.getController()).getLife();

card.setAttack(n);
card.setDefense(n);
}//for

}//execute

};//Serra Avatar

6 comments:

Unknown said...

Interesting way to find all Serra Avatars in play, but wouldn't it be more efficient to have a method similar to addAll() that only selected a card if it had a specific title? Instead of selecting all cards in play, then reducing that list down to only Serra Avatars?

That said, though, the code is nice a clean -- very easy to understand.

Had a thought about triggered effects. You could you have a Map with triggers as keys, and Lists of Cards as values. When an event occurs, just check to see if there's an entry in the map with the corresponding key, and trigger the effects of all the cards in the list. (Of course, this assumes an encapsulation of triggers into a class.) If there's no such key, or the corresponding list is empty, there are no triggered effects waiting for that event....

Forge said...

Triggered events are easy if your rules engine supports events. My program doesn't really have a rules engine so events are very hard.

Forge said...

Serra Avatar is ok but she can't be pumped up with Giant Growth, that is the only downside.

Anonymous said...

If you are using python for your rules engine, i highly recommend using the pydispatch library. I use it for incantus (with some modifications to allow for automatic expiry of event listeners). All my game objects derive from MTGObject, which basically only has a send and listen function. That way anything in the game can send/listen for events.

I use this for both triggered abilities and static abilities (the static abilities always look for enter play and leave play events, as well as some more specific events like controller changed, etc , depending on the static ability)

Declination:+20° Area:506 sq. deg. said...

Are you hardcoding the actions of all the cards into your game?

Forge said...

Am I hardcoding actions? Hm...I'm not sure. All of the actions are specified in the Java card code. Basically the idea is to make the rules engine responsible for executing an action and just letting the card code specify what should take place.

Goblin Sharpshooter

engine.addEvent(
"When creature comes into play",
"Untap Goblin Sharpshooter"
)

or with Nightmare

engine.addEvent(
"Zone Change - Swamp",
command.execute()
)

And the command object would update Nightmare's power and toughness.