It is currently 18 Apr 2024, 13:31
   
Text Size

MagicWars 1.2.0 - Script your card - Part I

Moderators: nantuko84, CCGHQ Admins

MagicWars 1.2.0 - Script your card - Part I

Postby nantuko84 » 14 Dec 2009, 20:39

Today I will continue describing how to create your own card in MagicWars starting version 1.2.0 (that will be published soon). At the moment, we have about 200 script cards and they work fine.

Let's get cracking!
Before reading this article, you'll need to read all about where and how cards are stored now here: http://www.slightlymagic.net/forum/viewtopic.php?f=45&t=1938

First, I'd like to write about Groovy Closures in groovy script, this is what can be found by google:
A Groovy Closure is like a "code block" or a method pointer. It is a piece of code that is defined and then executed at a later point.

Yep, it's true. You write code, then pass it as a parameter and it will be executed later in the game. It is put into "{" and "}" symbols. For well-known Shock the closure is: {dealDamage($target, 2, $this)}
So later it will be executed with injected $target that can be permanent or player and $this that is card itself.

When you create your card, you should follow these two steps:

1. First add spell or ability (using addSpell, addAbility, addAbilityTap, addAbilityTriggered, etc.)
2. Then add specific parameters for just added spell or ability (setCondition, setTrigger, setTarget, setSpecificTarget, addKicker, etc.)


Tip: in cards/scripts you can find common.g that contains all API function you can use.

Adding a spell or an ability

addSpell(Closure) - add any spell without any targeting or condition or spell that you will set parameters later (Step 2.)

mana cost is taken from card description

examples:
Code: Select all
//Wrath of God
addSpell({
     for (creature in $allCreatures) {
          destroyNoRegeneration(creature)
     }
})
Code: Select all
// Roar of the Wurm
addSpell({
   def token = createToken(card,"name=Wurm,type=Creature&Wurm,
color=Green,abilities=no,power=6,toughness=6");
   addPermanent(token);
})
addFlashback("3 G") // <-- this just adds Flashback to the spell
addAbility(Closure[,Cost="0"[,May=""]]) - add ability to permanent
Cost - mana cost to pay
May - is Yes\No question that will be asked before paying for ability
Cost and May are optional parameters with default values "0" and "" correspondingly

examples:
Code: Select all
// Ant Queen (M10)
addAbility({
    def token = createToken(card, "name=Insect,type=Creature&Insect,
color=Green,abilities=no,power=1,toughness=1");
    addPermanent(token);
}, "1 G")
addAbilityTap(Closure[,Cost="0"])
- the same as Ability but permanent will also be tapped as a cost

addTriggeredAbility(Closure[,Cost="0"[,May=""]])
- ability that will be triggered by some condition. you do need to set trigger condition later in the script by setTrigger function that will be described later

examples:

Code: Select all
// Reckless Scholar (ZEN)
addAbilityTap({
    drawCard($target, 1)
    def discard = Action($this, {
        discardCard($targetPlayer, $chosen);
    })
    discard.setTargetPlayerID($target)
    discard.setNeedsDiscardCard(true)
    run(discard)
})
setTarget("Player")
Code: Select all
// Hagra Crocodile (ZEN)
addAbilityTriggered("Landfall - Whenever a land enters the battlefield
under your control, Hagra Crocodile gets +2/+2 until end of turn.",
{
    pumpUntilEOT($this, 2, 2)
})

setCondition({
    return $card.isLand() && $card.controller == $this.controller
})

setTrigger("@moveToZone,from=Any,to=Battlefield")
next time I will describe the second step: setting targets, conditions, triggers for already added spells and abilities
nantuko84
DEVELOPER
 
Posts: 266
Joined: 08 Feb 2009, 21:14
Has thanked: 2 times
Been thanked: 9 times

Re: MagicWars 1.2.0 - Script your card - Part I

Postby Arch » 14 Dec 2009, 22:13

Some comments on your post although not really on the actual content of it:

It looks like you're passing a string to many of your functions containing various configuration. Don't you have access to named parameters in Groovy? Such that you could write something like:

Code: Select all
createToken(card, name="Wurm", power=6, ...)
I haven't used Groovy myself but I belive most of it's competing languages has something similar which leads me to belive that Groovy would have that as well. That way you wouldn't have to parse it yourself, you'd get the types automatically and so on.


Also, since I'm a fan of useful programming features, I must point out that a closure is only a closure if it captures something from it's surrounding environment. A block of code that doesn't do that should probably "just" be called a first class function. Looking at your examples I would say that Wrath, Scholar and Croc are probably functions while the others are actual closures.
User avatar
Arch
Programmer
 
Posts: 206
Joined: 04 Jul 2009, 09:35
Has thanked: 0 time
Been thanked: 15 times

Re: MagicWars 1.2.0 - Script your card - Part I

Postby nantuko84 » 15 Dec 2009, 08:22

It looks like you're passing a string to many of your functions containing various configuration. Don't you have access to named parameters in Groovy? Such that you could write something like:
not many, but there is some
it was our initial idea to pass strings and then parse them by regexp in java
it was at the very beginning on connection groovy to MagicWars and it worked

now I reread groovy specs time to time to find such new features that can make you life easier and code more readable. One thing I was happy to find yesterday is the code replacing this one in java:
Code: Select all
for (int i = 0; i < stack.size(); i++) {
    SpellAbility sa = stack.peek(i);
    if (sa.isSpell() && GameManager.canBeCountered(sa.getSourceCard())) {
        return true;
    }
}
by just:
Code: Select all
return stack.any{it.spell && canBeCountered(it.sourceCard)}
and you are totally right. what createToken does is calling java function SpellCreateTokenFactory.createToken(Card creator, String pattern) whilst there is createToken(Card creator, String name, String types, String abilities, String colors, int attack, int defense)

when you know this, you can update api that is used from scripted cards
now all of them in api.mw that goes with groovy folder, and there you can find this method:
Code: Select all
def createToken = {source, pattern -> tokenFactory.createToken(source, pattern)}
Today I've replaced it with (keeping in mind, that parameters go in back order)

Code: Select all
def createToken(params, source) {
   tokenFactory.createToken(source, params.name, params.types, params.abilities, params.colors, params.power, params.toughness)
}
and now Ant Queen looks like:
Code: Select all
   addAbility({
      def token = createToken($this, name:"Insect", types:"Creature&Insect", colors:"Green", abilities:"no", power:1, toughness:1)
      addPermanent(token)
   }, "1 G")
thanks for your comment ;)

Code: Select all
Also, since I'm a fan of useful programming features, I must point out that a closure is only a closure if it captures something from it's surrounding environment. A block of code that doesn't do that should probably "just" be called a first class function. Looking at your examples I would say that Wrath, Scholar and Croc are probably functions while the others are actual closures.
I've got closure definition from the site. As far as I know every closure returns anything (if you don't specify it by return keyword, then closure returns last statement that is closure by itself). Did you mean that there was some sort of misunderstanding from the terminology point of view?

regards
nantuko84
DEVELOPER
 
Posts: 266
Joined: 08 Feb 2009, 21:14
Has thanked: 2 times
Been thanked: 9 times

Re: MagicWars 1.2.0 - Script your card - Part I

Postby Arch » 15 Dec 2009, 09:46

That's definitly better looking code. The only thing I would consider changing is the "Creature&Insect"; I'd use a list there instead.


The closure comment is only about terminology as you figured. I'm assuming it's this site you got it from. It does seem they've got it slightly wrong.

It's not until you get to "free variables" that the block of code actually becomes a closure. Looking at the second example in that chapter for instance;

Code: Select all
def localMethod() {
  def localVariable = new java.util.Date()
  return { println localVariable }
}

def clos = localMethod()

println "Executing the Closure:"
clos()          //prints the date when "localVariable" was defined
The variable localVariable was declared inside the localMethod; meaning it would not normally be available outside of that scope. As soon as localMethod stops executing that variable would be gone in one way or another. But since Groovy supports closures the localVariable continues to live on inside the { println localVariable } code block.

One easy way to check if you have a closure or a first class function would be to move the code block. If it's possible to define it anywhere it's not a closure. The below code for instance would not work, hence { println localVariable } is a closure.

Code: Select all
def clos = { println localVariable }
clos()
User avatar
Arch
Programmer
 
Posts: 206
Joined: 04 Jul 2009, 09:35
Has thanked: 0 time
Been thanked: 15 times

Re: MagicWars 1.2.0 - Script your card - Part I

Postby nantuko84 » 15 Dec 2009, 12:38

I guess from that point of view def localVariable = new java.util.Date() is just the same as using final keyword in java. at least can't see any differences
so localMethod just returns predefined closure: { println SomeDate@Instance }
nantuko84
DEVELOPER
 
Posts: 266
Joined: 08 Feb 2009, 21:14
Has thanked: 2 times
Been thanked: 9 times

Re: MagicWars 1.2.0 - Script your card - Part I

Postby Arch » 15 Dec 2009, 12:53

I belive that's a valid way of thinking of it. Except that Groovy quite possibly handles it differently under the hood.
User avatar
Arch
Programmer
 
Posts: 206
Joined: 04 Jul 2009, 09:35
Has thanked: 0 time
Been thanked: 15 times

Re: MagicWars 1.2.0 - Script your card - Part I

Postby Incantus » 15 Dec 2009, 13:54

All functions close over global variables. Closures basically generalize that concept to allow nested functions to work intuitively (you should be able to access any variables defined in parent scopes all the way up to the global namespace). Under the covers, however, global variables and free variables are implemented differently, but that's an implementation issue.
Incantus
DEVELOPER
 
Posts: 267
Joined: 29 May 2008, 15:53
Has thanked: 0 time
Been thanked: 3 times


Return to MagicWars

Who is online

Users browsing this forum: No registered users and 8 guests


Who is online

In total there are 8 users online :: 0 registered, 0 hidden and 8 guests (based on users active over the past 10 minutes)
Most users ever online was 4143 on 23 Jan 2024, 08:21

Users browsing this forum: No registered users and 8 guests

Login Form