MapTool states: Tons of useful D&D 4e conditions

In polishing my campaign framework for D&D 4th Edition in MapTool (see my earlier post about tracking conditions if you missed it), I ended up creating a lot of icons to represent various states in MapTool. I love the states that came with the torrent of images over at rptutorials.net, but I needed to create some of my own, too.

   

Most of the icons I created are for numeric states. For instance, I already had -10 through +10 in both red and blue squares to represent attack penalties/bonuses and defense penalties/bonuses. I realized at some point that I really ought to have separate states for damage penalties/bonuses, so I created orange squares with those numbers (once again -10 to +10).

      

I also thought it would be nice to have a visual way to represent vulnerability and resistance. Now, I didn’t take it to the level of vulnerability/resistance to various TYPES of damage, but it’s rare to have to worry too much about that in practice. I decided that a black square with a white number in it would represent vulnerability while a white square with a black number in it would represent resistance.

  

Finally, I’ve long had a state for ongoing damage, but I thought it would also be useful to have a NUMBER associated with it (ongoing 5, ongoing 10, etc.). So, I created some green triangles with black numbers to represent ongoing damage.

      

Given all of these new states, I had to create macros to handle them; not too hard since I could base them on work I’d already done for the attack and defense modifier macros. I also thought it would be a nice touch to have a reminder pop up in the “Take damage” macro if a character had resistance or vulnerability:

[h: VulnerableReminder=if(VulnerableState==0, "", add(" (note Vulnerable ", VulnerableState,")"))]
[h: ResistReminder=if(ResistState==0, "", add(" (note Resist ", ResistState, ")"))]
[h: DamageString=add("Amount of damage taken", VulnerableReminder, ResistReminder)]
[h: x=input("Dmg|0|"+DamageString)]
[h: abort(x)]

If the character is both Vulnerable 5 and Resist 5, the message will look like this:

Anyway, all of these states are included in my campaign framework. If you’d like a ZIP file with the images for all of the states that I use, plus some additional states that come with the big torrent, you can download that ZIP file here.

What’s missing? What additional states should I include that aren’t already here?

-Michael the OnlineDM (OnlineDM1 on Twitter)

Stormy Weather – 4e Encounter with map and monsters

While I’ve mainly been running published adventures lately, every now and then I throw in some encounters that I create myself.

I’m currently running my Friday night online MapTool group through the War of the Burning Sky adventure path. We’re in adventure number five right now, and one part of the adventure called for the party to venture into the Valley of Storms. Despite the name, this valley had no storm-themed creatures in it at all, which felt like a crying shame.

As I was thinking about this encounter, I happened to be joining Mark Meredith as co-host of an episode of Dice Monkey Radio, his new podcast. The episode hasn’t gone live yet, but (spoiler alert!) I used the segment at the end of the podcast where Mark offers campaign advice to get his suggestions for some storm-themed encounters I could throw at my party. His ideas were excellent, and the result is Stormy Weather.

Download the encounter PDF

This encounter is for a party of five PCs of 15th level. You could use it in any campaign where the party is likely to meet hostile storm-themed creatures. The basic setup is that the PCs have intruded on the territory of a thunder titan and his genasi friends, and they intend to destroy the interlopers. While you could certainly handle this type of interaction via diplomacy (assuming someone speaks primordial), I wrote it as a fight.

Taking advantage of the ever-awesome Power2ool, I created monster stat blocks. I drew a map in MapTool. And the result is right here for download!

Since I’m proud of the monsters I created, I’ve reproduced their stat blocks below (click the images to enlarge them). I particularly like the thunder titan who spews lightning motes.

I ran this encounter for my group last Friday night, and they absolutely loved it. Now, that may be in part because they went after the genasi with a tornado of carnivorous hell-frogs, but it was a cool battle. Fair warning, though – they ended up using a LOT of map! Getting out of range of those genasi is a tricky business.

        

Map – scaled to a 50 pixel grid for use in MapTool and similar programs

Valley of Storms map - gridded

Valley of Storms map - no grid

MapTool macros: Condition tracking for D&D4e

At last, the culmination of my advanced MapTool topics from last week: My macros for tracking conditions in D&D 4th Edition!

Let me disclaim once again that there are lots of super-fancy campaign frameworks out there that probably handle this sort of thing far better than I do. But since I actually get pleasure from learning about MapTool macros, I write my own.

Shortcut to the end: Download the campaign file that contains all of these macros.

The goal

I already had macros that would toggle conditions like Dazed or Blinded on and off of tokens. What I wanted was a way of giving players the option of specifying an end time for the condition: Either the beginning of someone’s turn, the end of someone’s turn, or save ends (or no ending time at all, such as for Prone, which ends when you decide to get up). Then I wanted that condition to either automatically end or to see a message reminding you to save against a condition at the appropriate time.

It sounds so simple. For me, at least, it wasn’t. But I got there!

Step 1: Storing information about conditions

I already had a way of knowing whether a condition was on a token or not. MapTool has “states” that can be turned on or off, and if they’re on they display a little icon over the token. What I lacked was a way to track when the condition was supposed to end.

I ultimately went with a JSON array made up of JSON objects, stored as a property on a new library token (see the hyperlinks to understand what the heck that’s all about). I created a library token called Lib:LibToken1 and a new set of properties for it, with a single property called StatesArray. When populated, it looks something like this:

[{"TokenName":"None"},{"TokenName":"Shadow Ape","State":"Blind","EndCount":0,"EndRound":2,"AutoEnd":"1"},{"TokenName":"Torrent","State":"Quarried","EndCount":0,"EndRound":2,"AutoEnd":"1"}]

The first JSON object is just a dummy that I leave there all the time. The next object has five keys with values:

  • The name of the token affected is Shadow Ape
  • The state on it is Blind
  • The count in the initiative order in which this state should end is 0
  • The round in which the state should end is round 2
  • Yes, the state should end automatically

How do I set this up? With macros, of course!

Macro #1: Condition toggles

I already had a bunch of buttons in my campaign window that would toggle a condition on or off of a token. I just repurposed these to work with my new arrangement. Since each button’s name (with a few exceptions) matches the state that I’m turning on or off, they all have the same code, and for this example I’ll assume we’re working with the “Dazed” button.

[h: MacroArguments=json.set('{ }', "TokenName", "None", "State", getMacroName(), "EndCount", -2, "EndRound", -2, "AutoEnd", -2)]
[MACRO("ToggleState@lib:LibToken1"): MacroArguments]

The first line sets up a JSON object with two important variables: The first important variable is the name of the state I’m toggling, which is the same as the name of the macro button I’m clicking (Dazed). The second important variable is at the end of the JSON object – AutoEnd=-2. This will show up later on; it will tell my ToggleState macro that I want a pop-up to ask the player when the condition will end. The other three variables in the JSON object (TokenName, EndCound, EndRound) don’t matter for this particular macro right now.

I then use the MACRO roll option to call a macro called ToggleState that lives on a token called Lib:LibToken1, passing it the JSON object I created above.

Macro #2: ToggleState

Brace yourselves: It gets a little deeper now.

[h: SelectedTokens=getSelected()]
[h: StateName=json.get(macro.args, "State")]
[h: EndCount=json.get(macro.args, "EndCount")]
[h: EndRound=json.get(macro.args, "EndRound")]
[h: AutoEnd=json.get(macro.args, "AutoEnd")]

The first line gets a JSON array that contains all of the tokens that are currently selected (this lets me make a whole bunch of tokens Dazed at once by selecting all of them and then running the Dazed macro). The next four lines unpack the elements of the JSON object that I passed over – that JSON object is in the macro.args variable since it was the argument passed to this macro. Note that it’s possible for me to pass meaningful numbers to the macro for EndCount, EndRound and AutoEnd, but in this case the relevant part is the name of the state and AutoEnd=-2.

[h: CurrentStatesArray=getLibProperty("StatesArray","lib:LibToken1")]

I now get the StatesArray property from the library token – that’s the JSON array that contains all of the states that are currently on tokens, along with the times that they end.

[FOREACH(TokenID, SelectedTokens, " "), CODE:
 {
   [h: CurrentTokenName=getName(TokenID)]
   [h: NameAndState=json.set('{ }', "Name", CurrentTokenName, "State", StateName)]

I start looping through each of the selected tokens, running a piece of code on all of them. The first thing I do is to get the name of the first token I’ve selected, and then create a small JSON object that contains the name of the token and the state I’m toggling, for use later.

   [h, if(getState(StateName,TokenID)==0), CODE:
    {

I check to see if the token I’m working on is currently subject to the state in question (Dazed). If it’s not currently dazed, I run some more code (because I want to turn on the Dazed condition). I’ll run some different code (later) if I’m turning OFF the Dazed condition.

      [h: TokenStateObject=add('{"TokenName":"', getName(TokenID), '", "State":"', StateName, '", "EndCount":', EndCount, ', "EndRound":', EndRound, ', "AutoEnd":"', AutoEnd, '"}')]
      [ MACRO("GetExpirationInfo@Lib:LibToken1"): TokenStateObject]

I set up a JSON object that’s the same as the arguments that I passed over to this macro, except that I pop the name of the first token I’m working on into the TokenName slot. I then pass this object over to the GetExpirationInfo macro on the Lib:LibToken1 library token. That macro (which  you’ll see later) will ask the player for information about when the condition is supposed to end (assuming AutoEnd=-2 to start with, as it does here) and then pass that information back to this ToggleState macro.

      [ MACRO("GetExpirationInfo@Lib:LibToken1"): TokenStateObject]
      [h: EndCount=json.get(macro.return, "EndCount")]
      [h: EndRound=json.get(macro.return, "EndRound")]
      [h: AutoEnd=json.get(macro.return, "AutoEnd")]
      [h: TokenStateObject=add('{"TokenName":"', getName(TokenID), '", "State":"', StateName, '", "EndCount":', EndCount, ', "EndRound":', EndRound, ', "AutoEnd":"', AutoEnd, '"}')]

Similar to what we saw above, I now unpack the new JSON object that comes back from the GetExpirationInfo (it’s stored as macro.return). I then rebuild the TokenStateObject JSON with the update information about the end time for the condition.

     [CurrentStatesArray=json.append(CurrentStatesArray,TokenStateObject)]
     [h: setState(StateName,1,TokenID)]
     };

I now stick this JSON object onto the end of the CurrentStatesArray JSON array variable, adding one more token/state/ending time element to the list of all of them that are currently active. I then turn the Dazed state on for the token in question, so that the Dazed icon will appear on it.

This then ends the list of tasks I’m performing specifically for the case where I’m turning the Dazed state ON; I now move on to the case when I’m turning the Dazed state OFF.

    {
     [h, MACRO("FindToken@Lib:LibToken1"):  NameAndState]
     [h: CurrentTokenStateIndex=macro.return]
     [h: CurrentStatesArray=json.remove(CurrentStatesArray, CurrentTokenStateIndex)]
     [h: setState(StateName,0,TokenID)]
    }
   ]
 }
]

I start off by calling another macro called FindToken, passing it the JSON object I created earlier that has the name of the token in question and the state we’re toggling off. That macro (which we’ll look at later on in the post) looks through the current list of tokens and states that are active to figure out where this token/state pair lives in the list, and returns the index of this token/state pair in that array. We then remove that element from the array, and finally turn the state off of the token (so the Dazed icon will no longer appear on the image).

We then close the section of code for the “toggle off” case (first curly brace), close the “if” statement (first square bracket), close the FOREACH code block (second curly brace) and close the FOREACH statement itself (second square bracket).

At this point, the whole toggling process has been run for each selected token, toggling the state itself on or off and modifying the CurrentStatesArray variable to include the token/state/end time JSON objects for the states we’ve toggled ON and to exclude the token/state/end time JSON objects for the states we’ve toggled OFF.

[h: setLibProperty("StatesArray", CurrentStatesArray, "lib:LibToken1")]

We end the whole macro by updating the StatesArray property on the Lib:LibToken1 token to equal the updated array that we’ve been creating. Now the library token will have the correct list of tokens, states and ending conditions. Voila!

Macro #3: GetExpirationInfo

This macro is called from within the ToggleStates macro and asks the user to specify when the condition that we’re applying should end. I’ll give a shout-out here to Paul Baalham; I repurposed some of his code here to create a list of token images for a drop-down.

[h: CurrentTokenName=json.get(macro.args, "TokenName")]
[h: CurrentState=json.get(macro.args, "State")]
[h: AutoEnd=json.get(macro.args, "AutoEnd")]

First, I look at the JSON object that’s been passed to this macro and extract from it the name of the token we’re working on, the state we’re toggling (Dazed) and the value of the AutoEnd variable.

[h, if(AutoEnd==-2), CODE:
{

Here’s where the AutoEnd=-2 comes into play. The following code is only run if I’ve specifically set the AutoEnd variable equal to -2. It’s arbitrary, but it’s what I’ve picked.

 [h: CurrentLabel=add(CurrentState, " condition on ", CurrentTokenName)]
 [h: InitTokens=json.get(getInitiativeList(), "tokens")]

 [h: TokenImageList=""]

I set up a string called CurrentLabel that will say something like “Dazed condition on Goblin 5”.

I create a variable called InitTokens that has the list of tokens that are currently in the initiative order. Note that the output of getInitiativeList is a JSON object, one element of which is called “tokens”, which is ITSELF a JSON object, one of whose elements is the token IDs. Messy stuff.

I also set up an empty variable called TokenImage list that I’ll stick stuff into shortly.

 [h, FOREACH(Thingy, InitTokens, ""), CODE:

I then start a loop that goes through each of the tokens in the initiative window (each one referred to by me as Thingy just for the fun of it).

 {[ThisTokenID=json.get(Thingy, "tokenId")]
   [token(ThisTokenID): CurrentImage=getTokenImage()]
   [TokenImageList=listAppend(TokenImageList, getName(ThisTokenID) + " " + CurrentImage)]
 }
]

I get the token ID for each token, then get its image. Note that I have to use the “token” roll option in order to do this; I can only use the getTokenImage() function on the “active” token, and the token roll option lets me treat whatever token I like as active.

I then append the name of the token and its image to the TokenImageList variable. We’ll be using this in a little while.

 [h:x=input(
  "junkVar | " + CurrentLabel + " | Make selections for | LABEL | SPAN=FALSE",
  "TurnTime | None, Beginning, End, Save | On what part of the turn does the condition end? | RADIO | ORIENT=H SELECT=0 VALUE=STRING",
  "WhichTurn |"+TokenImageList +" | On whose turn does the condition end? | LIST | SELECT=0 ICON=TRUE ICONSIZE=30",
  "EndCurrentTurn | 0 | If end of own turn, does the condition end NEXT round? | CHECK"
 )]
 [h: abort(x)]
Now I bring up a dialog box where include a label that specifies what token and state I'm inquiring about, and I ask the user for three things. First, I have a set of radio buttons that ask whether this is a condition that doesn't end at a particular time, or one that ends at the beginning or end of someone's turn, or one that's "save ends". I save this as the TurnTime variable.

Next, I ask whose turn it ends on. Note that if you pick “save ends” you still need to choose the token’s own picture from the list. This is because I might have 10 goblin warriors on the map, but only Goblin Warrior 1 actually shows up in the initiative window. So, if Goblin Warrior 3 is dazed (save ends), I’d choose Goblin Warrior 1’s image from the drop-down list, since I want the “Make a saving throw” message to pop up at the end of the goblins’ collective turn. Note that the variable saved here, WhichTurn, is going to be the INDEX of the item I picked from the list, with the first token on the list having an index of zero.

Finally, I have a wonky workaround for the situation in which a character is subject to a condition on their own turn that ends at the end of their NEXT turn (such as “you get a +2 bonus to attack rolls until the end of your next turn”). If they check this box, it won’t end the condition at the end of the current turn, instead postponing it for a round.

The abort(x) means that if I clicked the Cancel button in the dialog box, the macro ends.

The pop-up looks something like this:

Next piece of code:

[h: CurrentInit=getCurrentInitiative()]
[h: CurrentRound=getInitiativeRound()]
[h: MaxInit=json.length(json.get(getInitiativeList(), "tokens"))-1]

I figure out what the current initiative count and round are. I then figure out the maximum initiative count. Note that I do this my counting how many things are in the current initiative list and subtracting 1. I do this because, as you may recall from the JSON arrays article, JSON array indexes START AT ZERO. So, if there are three things in the initiative order, the maximum count of the array is 2 (0, 1, 2).

 [SWITCH(TurnTime), CODE:

I now use a SWITCH statement, which will let me do different things depending on what has been selected for the ending time.

  case "None": {
    [h: EndCount=-1]
    [h: EndRound=-1]
    [h: AutoEnd=-1]
                        };

If the player said that the condition doesn’t end at any particular time, then I set EndCount, EndRound and AutoEnd all equal to -1.

  case "Beginning": {
    [h: EndCount=WhichTurn]
    [h: EndRound=if(WhichTurn>CurrentInit,CurrentRound,CurrentRound+1)]
    [h: AutoEnd=1]
                        };

If the player said that the condition ends at the beginning of someone’s turn, then I set the ending initiative count equal to the index of whatever token was picked from the drop-down (WhichTurn). If that token’s turn hasn’t come up yet this round, then the ending round is this round; otherwise, it’s next round. And I set AutoEnd=1 to tell another macro that this condition should end automatically.

  case "End": {
    [h: EndCount=if(WhichTurn<MaxInit,WhichTurn+1,0)]
    [h: EndRound=if(WhichTurn+1>CurrentInit && WhichTurn+1<=MaxInit,CurrentRound+EndNextRound,CurrentRound+EndNextRound+1)]
    [h: AutoEnd=1]
                        };

If this is a condition that ends at the END of someone’s turn, then I make it work the same as ending at the BEGINNING of the NEXT token’s turn. If it ends at the end of someone’s turn who’s not at the very end of the initiative order, then I simply add 1 to the index of the chosen token. On the other hand, if it ends at the end of the turn of the last token in the round, then I have to make it end at the beginning of the FIRST token in the next round (count 0).

As for choosing a round, if it ends at the end of someone whose turn is either ongoing or is coming later in the round, it ends in the current round PLUS the EndNextRound variable (in case it’s this token’s turn and the condition should end at the end of their NEXT turn). If the turn has already passed or if it’s ending at the end of the turn of the LAST token in the initiative order, then it ends another round on.

  case "Save": {
    [h: EndCount=if(WhichTurn<MaxInit,WhichTurn+1,0)]
    [h: EndRound=-1]
    [h: AutoEnd=0]
                        };

If we’re talking about a “save ends” condition, then the EndCount is identical to the “end of turn” logic, but there’s no particular round in which the condition ends (hence the -1 for EndRound). AutoEnd is set to 0 because the condition won’t end automatically at the end of the character’s turn – it will depend on a saving throw.

  default: {[r: "You somehow didn't pick anything!"]}
 ]

I end the SWITCH with the default option, which shouldn’t ever show up, but hey, I could have screwed something up in my code.

 [h: ReturnedVars=json.set("{ }", "EndCount", EndCount, "EndRound", EndRound, "AutoEnd", AutoEnd)]
};

I now create a JSON object that contains the EndCount, the EndRound and the AutoEnd for this particular token for the case in which AutoEnd was equal to -2, and I then close the section of “AutoEnd==-2” code.

{[h: ReturnedVars=MacroArgs]}
]

THIS is the piece of code that’s run if I didn’t set AutoEnd=-2. What’s up with that? Well, this lets me do something like have my Second Wind code automatically specify when the +2 to all defenses condition ends (at the beginning of the character’s next turn) rather than making the player pick this timing from the dialog box. It’s a minor thing, but I like having the ability to bypass the dialog box if I already know when the condition is supposed to end.

[h: macro.return=ReturnedVars]

Finally, no matter whether I popped up the dialog box to get the end timing or I already knew it before coming to this macro, I set the macro.return variable equal to this JSON object, for use back in the ToggleStates macro that called this macro. That’s it!

Macro #4: FindToken

This macro is called in ToggleStates when we’re turning a condition OFF from a token. It sorts through the current JSON array in the StatesArray property of the library token, looking for the JSON object in that array that corresponds to the token and state that we’re trying to turn off, so that the object can be removed from the array.

[h: CurrentTokenName=json.get(macro.args, "Name")]
[h: CurrentTokenState=json.get(macro.args, "State")]

I start off by getting the token name and state that we’re looking for from the JSON object that was passed to this macro as arguments. macro.args represents that JSON object that was passed, and it has two keys: Name and State. Easy enough, now that we understand JSON objects!

[h: CurrentStatesArray=getLibProperty("StatesArray","lib:LibToken1")]
[h: CurrentTokenStateIndex=-1]
[h: StatesArrayLength=json.length(CurrentStatesArray)]

I retrieve the current value of the StatesArray property of the library token. I initialize a variable called CurrentTokenStateIndex to be equal to -1 (so that if I don’t find this token/state pair in the array, the index I return for its location will be -1 – that is, nonexistent). I then figure out how many JSON objects are in the array right now, so that I know how many I need to loop through.

[COUNT(StatesArrayLength, ""), CODE:
 {[h: CurrentObject=json.get(CurrentStatesArray, roll.count)]
  [h: CurrentTokenStateIndex=if(json.get(CurrentObject, "TokenName")==CurrentTokenName && json.get(CurrentObject, "State")==CurrentTokenState, roll.count, CurrentTokenStateIndex)]
 }
]

I use the COUNT roll option to loop through each object in the current states array. Note that COUNT also starts at zero. If I COUNT(3), I will do stuff for roll.count=0, roll.count=1 and roll.count=2 (three times, starting at zero).

I retrieve each object in the JSON array, storing it as CurrentObject, then check to see if the TokenName and the State of that object match the token/state pair that we’re looking for. If there’s a match, then I set CurrentTokenStateIndex equal to the matching index of the array that we’re looking at. Otherwise, I leave CurrentTokenStateIndex where it was. Since it started at -1, if I go through every object in the array and never find a match, it will stay at -1 the whole time.

[h: macro.return=CurrentTokenStateIndex]

The value that I return to the ToggleStates macro is the index in the StatesArray JSON array that contains the token/state pair we’re looking to turn off. Bam!

Step 2: Checking for conditions that need to end at a particular initiative count

That does it for toggling states on and off manually and storing information about them in the StatesArray. What about having them automatically end at the appropriate time in the initiative count, or at least prompting the player for a saving throw?

I love the initiative window in MapTool; I use it even for in-person games when I’m using MapTool with my projector setup. But now I need to create some of my own macros for advancing the initiative count, because I need to check some things at each count. Specifically, are there any token conditions that need to end?

Macro #5: NextInitiative

I created a button in my campaign window called NextInitiative. All this macro does is advance the initiative count and then call another macro to check to see what happens at the new count:

[h: nextInitiative()]
[MACRO("CheckForChanges@lib:LibToken1"):""]

Yes, I have another macro on the library token, this one called CheckForChanges. You’ll note that I’m passing it a set of null arguments; I can’t just leave that part out. Go figure.

Macro #6: CheckForChanges

[h: CurrentInit=getCurrentInitiative()]
[h: CurrentRound=getInitiativeRound()]
[h: CurrentStatesArray=getLibProperty("StatesArray","lib:LibToken1")]
[h: CurrentTokenStateIndex=-1]
[h: StatesArrayLength=json.length(CurrentStatesArray)]

This macro begins with some simple housekeeping. I figure out what initiative count and what round we’re on at the moment. I figure out what’s in the current StatesArray JSON array that’s keeping track of all the conditions on all the tokens and when they need to end. I set a variable called CurrentTokenStateIndex to a -1 value, and I figure out how many objects are in the StatesArray. Yes, another loop is coming.

[COUNT(StatesArrayLength, ""), CODE:
 {[h: CurrentObject=json.get(CurrentStatesArray, roll.count)]
  [h: ObjectTokenName=json.get(CurrentObject, "TokenName")]
  [h: ObjectState=json.get(CurrentObject, "State")]
  [h: ObjectEndInit=json.get(CurrentObject, "EndCount")]
  [h: ObjectEndRound=json.get(CurrentObject, "EndRound")]
  [h: ObjectAutoEnd=json.get(CurrentObject, "AutoEnd")]

I start with the first object in the array and retrieve all five keys from that JSON object: The token name, the state, the initiative count when the state ends, the round when the state ends, and whether the state should end automatically or not.

  [if(CurrentInit==ObjectEndInit && CurrentRound==ObjectEndRound && ObjectAutoEnd==1), CODE:
   {The [ObjectState] condition on [ObjectTokenName] has ended.
    [MACRO("ToggleState2@lib:LibToken1"):CurrentObject]
   };{}
  ]

If we’re at the right initiative count and the right round for a state to end on a token and we’ve said that this state should end automatically, then I display a message in the chat window that explains what state is ending on what token. I then call another macro, called ToggleState2, to turn that state off. It’s a lot like the original ToggleState macro, but it doesn’t require looping through a bunch of tokens (since we’ve already done that).

  [if(CurrentInit==ObjectEndInit && ObjectAutoEnd==0), CODE:
   {[ObjectTokenName] needs to make a saving throw against the [ObjectState] condition.
   };{}
  ]
 }
]

If we’re at the right initiative count for a state to end and the AutoEnd variable is zero, that means it’s time for a “save ends” message to pop up. You’ll note that we don’t actually end the condition – we just display the reminder message. We then end the COUNT loop and the macro itself.

Finally, for the sake of completeness, here is the ToggleState2 macro without comment (because it’s pretty much the same stuff as ToggleState).

Macro #7: ToggleState2

[h: ArgsArray=macro.args]
[h: SelectedTokens=getSelected()]
[h: StateName=json.get(ArgsArray, "State")]
[h: TokenName=json.get(ArgsArray, "TokenName")]
[h, TOKEN(TokenName): TokenID=currentToken()]

[h: CurrentStatesArray=getLibProperty("StatesArray","lib:LibToken1")]
[h, if(getState(StateName,TokenID)==0), CODE:
  {[h, MACRO("GetExpirationInfo@Lib:LibToken1"):""]
    [h: EndCount=json.get(macro.return, "EndCount")]
    [h: EndRound=json.get(macro.return, "EndRound")]
    [h: AutoEnd=json.get(macro.return, "AutoEnd")]
    [h: TokenStateObject=add('{"TokenName":"', getName(TokenID), '", "State":"', StateName, '", "EndCount":', EndCount, ', "EndRound":', EndRound, ', "AutoEnd":"', AutoEnd, '"}')]
   };{}
]

[h: CurrentTokenName=getName(TokenID)]
[h: FindTokenArgs=json.set('{ }', "Name", CurrentTokenName, "State", StateName)]
[h, MACRO("FindToken@Lib:LibToken1"):  FindTokenArgs]

[h: CurrentTokenStateIndex=macro.return]
[h, if(getState(StateName,TokenID)==0), CODE:
 {[NewStatesArray=json.append(CurrentStatesArray,TokenStateObject)]
   [setLibProperty("StatesArray", NewStatesArray, "lib:LibToken1")]
   [h: setState(StateName,1,TokenID)]
   };
  {[h: NewStatesArray=json.remove(CurrentStatesArray, CurrentTokenStateIndex)]
    [h: setLibProperty("StatesArray", NewStatesArray, "lib:LibToken1")]
    [h: setState(StateName,0,TokenID)]
  }
]

Whew!

Okay, so it took me seven different macros to make this work – but it does work! I’m really excited about this, and I hope that this will end the “Oh wait, when does that condition end again?” discussions at the table. We shall see.

The uninterrupted code for the longer macros is below (note that ToggleState2 is above), and if you want to download my campaign file that includes all of these macros, you can do that at this link.

As always, feedback is appreciated!

-Michael, the OnlineDM

ToggleState macro:

[h: SelectedTokens=getSelected()]
[h: StateName=json.get(macro.args, "State")]
[h: EndCount=json.get(macro.args, "EndCount")]
[h: EndRound=json.get(macro.args, "EndRound")]
[h: AutoEnd=json.get(macro.args, "AutoEnd")]

[h: CurrentStatesArray=getLibProperty("StatesArray","lib:LibToken1")]

[FOREACH(TokenID, SelectedTokens, " "), CODE:
 {
   [h: CurrentTokenName=getName(TokenID)]
   [h: NameAndState=json.set('{ }', "Name", CurrentTokenName, "State", StateName)]
   [h, if(getState(StateName,TokenID)==0), CODE:
    {
      [h: TokenStateObject=add('{"TokenName":"', getName(TokenID), '", "State":"', StateName, '", "EndCount":', EndCount, ', "EndRound":', EndRound, ', "AutoEnd":"', AutoEnd, '"}')]
      [ MACRO("GetExpirationInfo@Lib:LibToken1"): TokenStateObject]
      [h: EndCount=json.get(macro.return, "EndCount")]
      [h: EndRound=json.get(macro.return, "EndRound")]
      [h: AutoEnd=json.get(macro.return, "AutoEnd")]
      [h: TokenStateObject=add('{"TokenName":"', getName(TokenID), '", "State":"', StateName, '", "EndCount":', EndCount, ', "EndRound":', EndRound, ', "AutoEnd":"', AutoEnd, '"}')]
     [CurrentStatesArray=json.append(CurrentStatesArray,TokenStateObject)]
     [h: setState(StateName,1,TokenID)]
     };
    {
     [h, MACRO("FindToken@Lib:LibToken1"):  NameAndState]
     [h: CurrentTokenStateIndex=macro.return]
     [h: CurrentStatesArray=json.remove(CurrentStatesArray, CurrentTokenStateIndex)]
     [h: setState(StateName,0,TokenID)]
    }
   ]
 }
]

[h: setLibProperty("StatesArray", CurrentStatesArray, "lib:LibToken1")]

GetExpirationInfo macro:

[h: CurrentTokenName=json.get(macro.args, "TokenName")]
[h: CurrentState=json.get(macro.args, "State")]
[h: AutoEnd=json.get(macro.args, "AutoEnd")]

[h, if(AutoEnd==-2), CODE:
{
 [h: CurrentLabel=add(CurrentState, " condition on ", CurrentTokenName)]
 [h: InitTokens=json.get(getInitiativeList(), "tokens")]

 [h: TokenImageList=""]

 [h, FOREACH(Thingy, InitTokens, ""), CODE:
  {[ThisTokenID=json.get(Thingy, "tokenId")]
    [token(ThisTokenID): CurrentImage=getTokenImage()]
    [TokenImageList=listAppend(TokenImageList, getName(ThisTokenID) + " " + CurrentImage)]
  }
 ]

 [h:x=input(
  "junkVar | " + CurrentLabel + " | Make selections for | LABEL | SPAN=FALSE",
  "TurnTime | None, Beginning, End, Save | On what part of the turn does the condition end? | RADIO | ORIENT=H SELECT=0 VALUE=STRING",
  "WhichTurn |"+TokenImageList +" | On whose turn does the condition end? | LIST | SELECT=0 ICON=TRUE ICONSIZE=30",
  "EndNextRound | 0 | If end of own turn, does the condition end NEXT round? | CHECK"
 )]
 [h: abort(x)]

 [h: CurrentInit=getCurrentInitiative()]
 [h: CurrentRound=getInitiativeRound()]
 [h: MaxInit=json.length(json.get(getInitiativeList(), "tokens"))-1]

 [SWITCH(TurnTime), CODE:
  case "None": {
    [h: EndCount=-1]
    [h: EndRound=-1]
    [h: AutoEnd=-1]
                        };
  case "Beginning": {
    [h: EndCount=WhichTurn]
    [h: EndRound=if(WhichTurn>CurrentInit,CurrentRound,CurrentRound+1)]
    [h: AutoEnd=1]
                        };
  case "End": {
    [h: EndCount=if(WhichTurn<MaxInit,WhichTurn+1,0)]
    [h: EndRound=if(WhichTurn+1>CurrentInit && WhichTurn+1<=MaxInit,CurrentRound+EndNextRound,CurrentRound+EndNextRound+1)]
    [h: AutoEnd=1]
                        };
  case "Save": {
    [h: EndCount=if(WhichTurn<MaxInit,WhichTurn+1,0)]
    [h: EndRound=if(WhichTurn+1>CurrentInit && WhichTurn+1<=MaxInit,CurrentRound,CurrentRound+1)]
    [h: EndRound=-1]
    [h: AutoEnd=0]
                        };
  default: {[r: "You somehow didn't pick anything!"]}
 ]
 [h: ReturnedVars=json.set("{ }", "EndCount", EndCount, "EndRound", EndRound, "AutoEnd", AutoEnd)]
};
{[h: ReturnedVars=MacroArgs]}
]
[h: macro.return=ReturnedVars]

FindToken macro:

[h: CurrentTokenName=json.get(macro.args, "Name")]
[h: CurrentTokenState=json.get(macro.args, "State")]
[h: CurrentStatesArray=getLibProperty("StatesArray","lib:LibToken1")]
[h: CurrentTokenStateIndex=-1]
[h: StatesArrayLength=json.length(CurrentStatesArray)]
[COUNT(StatesArrayLength, ""), CODE:
 {[h: CurrentObject=json.get(CurrentStatesArray, roll.count)]
  [h: CurrentTokenStateIndex=if(json.get(CurrentObject, "TokenName")==CurrentTokenName && json.get(CurrentObject, "State")==CurrentTokenState, roll.count, CurrentTokenStateIndex)]
 }
]
[h: macro.return=CurrentTokenStateIndex]

CheckForChanges macro:

[h: CurrentInit=getCurrentInitiative()]
[h: CurrentRound=getInitiativeRound()]
[h: CurrentStatesArray=getLibProperty("StatesArray","lib:LibToken1")]
[h: CurrentTokenStateIndex=-1]
[h: StatesArrayLength=json.length(CurrentStatesArray)]
[COUNT(StatesArrayLength, ""), CODE:
 {[h: CurrentObject=json.get(CurrentStatesArray, roll.count)]
  [h: ObjectTokenName=json.get(CurrentObject, "TokenName")]
  [h: ObjectState=json.get(CurrentObject, "State")]
  [h: ObjectEndInit=json.get(CurrentObject, "EndCount")]
  [h: ObjectEndRound=json.get(CurrentObject, "EndRound")]
  [h: ObjectAutoEnd=json.get(CurrentObject, "AutoEnd")]
  [if(CurrentInit==ObjectEndInit && CurrentRound==ObjectEndRound && ObjectAutoEnd==1), CODE:
   {The [ObjectState] condition on [ObjectTokenName] has ended.
    [MACRO("ToggleState2@lib:LibToken1"):CurrentObject]
   };{}
  ]
  [if(CurrentInit==ObjectEndInit && ObjectAutoEnd==0), CODE:
   {[ObjectTokenName] needs to make a saving throw against the [ObjectState] condition.
   };{}
  ]
 }
]

Advanced MapTool macros part 1: Intro to JSON objects

Over the past few weeks, I’ve been working on what I expected would be a not-too-hard MapTool macro project. I wanted to create a macro (or set of macros, as it turned out) for putting conditions on tokens that would also keep track of when those conditions needed to end. Seemed simple enough; after all, I already had nice little macros that would toggle a condition on or off of a selected token or group of tokens. Those looked like this:

[h: SelectedTokens=getSelected()]
[FOREACH(TokenID, SelectedTokens, " "), CODE:
 {[h: NewState=if(getState("Dazed",TokenID)==1,0,1)]
 [h: setState("Dazed",NewState,TokenID)]
 }
]

However, in figuring out how to handle the automatic tracking of the condition end time, I had to delve deeper, trying to avoid Balrogs as I went.

Yes, dear readers: I had to learn about JSON! (Cue horrified screaming)

JSON stands for JavaScript Object Notation, and it’s something I had seen used by the REAL heavy lifters among MapTool macro writers. They had JSONs all over the place in their campaigns, and I tried my best to avoid them. They looked… intimidating.

I’ll warn you all right now, if you’re just looking for casual stuff about MapTool, skip this series of posts. This is heavy-duty macro programming compared to what I’ve written before. It still pales in comparison to the stuff written by the folks who create full-on MapTool frameworks, but it’s more than I’ve done in the past. However, if you need to learn about using JSONs, I hope to present it in an easy-to-follow manner.

What is a JSON object?

There are types of JSON in MapTool: JSON objects and JSON arrays. I’m going to talk about JSON objects today.

A JSON object is a single variable or property enclosed in braces {} that can hold several pieces of information.

For instance, here is a JSON object I ended up creating for my state-tracking macros:

{"TokenName":"Brute 1","State":"Cursed","EndCount":2,"EndRound":1,"AutoEnd":"1"}

This single object contains 5 pieces of information: The TokenName (Brute 1), the State on that token (Cursed), the count in the initiative order when that state will end (2), the round in which it will end (2) and a variable that indicates whether I want the condition to simply end automatically (1 means “yes” in this case) or not.

How do you build a JSON object?

If I wanted to build the object above, I could do it by assigning as a regular variable in MapTool, enclosing the whole thing in single quotes:

[h: MyJSONObject='{"TokenName":"Brute 1","State":"Cursed","EndCount":2,"EndRound":1,"AutoEnd":"1"}']

I could also use the ADD function if I had already created separate variables for, say, TokenName and State:

[h: CurrentTokenName="Brute 1"]
[h: CurrentState="Cursed"]
[h: MyJSONObject=add('{"TokenName":"', Brute 1, '","State":"', Cursed, '","EndCount":2,"EndRound":1,"AutoEnd":"1"}']

There’s also the option of using some of MapTool’s JSON functions, such as json.set:

[h: MyJSONObject=json.set("{ }", "TokenName", CurrentTokenName, "State", CurrentState, "EndCount", 2, "EndRound", 1, "AutoEnd", 1)]

Note that there are no single quotes needed with json.set. I start with the empty braces in quotes to let MapTool know that this will be a JSON object rather than a JSON array. I then list the key/value pairs, separated by commas.

Getting information from JSON objects

Once I’ve created this object, what the heck do I do with it? Well, assuming I save it as the value of a property of some token, I can have other macros refer to this property later to get information out of it. I do this with MapTool’s json.get function.

First, let’s say I have a library token called lib:LibToken1. Great name, right? And let’s say it has a property called StateObject, which starts off being set to 0. Now let’s assume that I assign my new JSON object to this property as follows:

[h: setProperty("StateObject", MyJSONObject, "lib:LibToken1")]

So, I set the StateObject property of the token called lib:LibToken1 equal to MyJSONObject.

In a later macro I can retrieve this property from the token and extract the information:

[h: CurrentStateObject=getProperty("StateObject","lib:LibToken1")]
[h: CurrentTokenName=json.get(CurrentStateObject, "TokenName")]
[h: CurrentState=json.get(CurrentStateObject, "State")]

The first line gets the StateObject property from the lib:LibToken1 token (which, remember, is equal to whatever I put in MyJSONObject) and stores it as CurrentStateObject (so CurrentStateObject=MyJSONObject). Then I can extract the TokenName value from that object and store it as CurrentTokenName using json.get. I do the same for the State value. I could do the same for the EndCount, EndRound and AutoEnd values. And then I can do whatever I like with these values inside my macro.

Why would you use a JSON object?

This is the question that kept me away from learning about JSON stuff for so long. Why do I even need it?

In lots of cases, you don’t actually NEED to use JSON, but it can make life easier. Where I decided I finally needed to use it was when I wanted to have a single property on a token that could hold lots of different stuff (in this case, information about states on tokens and when they end). The JSON object above is an example (actually as a piece of a JSON array) that appears in the StatesArray property of a token I created specifically to hold this kind of thing. Without using JSON, I would need to create a whole bunch of different properties on the token – one each for TokenName, State, EndCount, EndRound and AutoEnd. I could do that, but it would be messier.

And if I wanted to have a varying number of these objects – one for each token and state in the game – I’d need to create a huge multitude of properties, and I’d have to set up enough of them in advance so that I’d never run out. If I have 20 minions each getting slowed by a controller’s blast, well, I’d better have 100 properties set up in advance. If they’re being slowed and blinded, now I need 200 properties.

There’s got to be an easier way, and there is: JSON arrays. That’s the next post!

Descent Into Darkness: Free heroic tier D&D4e adventure

At last, my adventure trilogy is complete. The first two adventures, The Stolen Staff and Tallinn’s Tower, were released here on the blog over the past few months. The third adventure, as I mentioned here, is now available.

Descent Into Darkness is an adventure for 4-6 heroic tier characters. The adventure is presented at level 2, 4, 6, 8 and 10. I personally recommend it at level 6, 8, or 10, but it can work at lower-level (though the PCs might be surprised and scared by the final boss).

Synopsis

The powerful wizardess Tallinn seeks adventurers to be teleported into the Underdark bearing a powerful magical artifact, the Staff of Suha. The mission: Find three other artifacts that have been stolen by unknown creatures, likely in an effort to recreate a teleportation device once used by a long-dead drow sorcerer to bring his foul armies to the overworld in conquest. The other three artifacts (Orb of Oradia, Chalice of Chale and Shield of Shalimar) must be recovered or destroyed, and the forces behind their theft must be stopped.

The adventurers discover that the powerful beholder Ergoptis has enslaved drow, diggers (new insectoid monsters), halfling thieves and mindless duergar as soldiers and hunters of artifacts. The party must fight their way through treacherous traps and puzzles to ultimately face Ergoptis and its underlings in a room dominated by a ziggurat, with a magma river crossed by bridges and floating platforms. Can they recover the final artifact and escape or destroy Ergoptis before the one-hour time limit on their teleportation ritual runs out? Or will the beholder simply add the adventurers to its army of enslaved warriors and continue its plans for domination?

Descent Into Darkness includes four new artifacts, an all-new monster (the digger), a find-the-path puzzle with custom runes and an exciting final encounter with an evil beholder.

Files

Download the full heroic tier adventure PDF (level 2/4/6/8/10)

Download the MapTool campaign file (compatible with version 1.3.b86 of MapTool)

Maps (scaled to 50 pixels per square)

Mine map - Gridded

Mine map - no grid

Thieves cavern map - gridded

Thieves cavern map - no grid

Mushroom cavern map - gridded

Mushroom cavern map - no grid

Magma cavern map - gridded

Magma cavern map - no grid

Afterword

If you decide to run this adventure or have the opportunity to play in it, I’d love to hear about it! And if you have any feedback based on your own read-throughs, I’m always trying to improve the adventures themselves. Feel free to chime in via the comments, email, or Twitter.

-Michael, the OnlineDM

onlinedungeonmaster@gmail.com

OnlineDM1 on Twitter

WATE 2-4: Factotum the Bard is back on stage!

This past Thursday evening, I had the rare opportunity to play D&D as a player rather than a DM. My wife has been feeling unwell for a while, so I try to mostly stay home with her in the evenings, but on this particular night she was getting together with another friend. A new-to-me Living Forgotten Realms module was being run at the friendly local game store, Enchanted Grounds, so I headed on down for a game.

Spoilers ahead for WATE 2-4 Stage Misdirection

The particular adventure we were playing was set in Waterdeep, the hometown of my beloved bard Factotum. This was the first time I had gotten to play an adventure with Factotum in Waterdeep, and I was excited.

I learned that the adventure begins with the PCs having various jobs in an opera house. “Star of the show” wasn’t an option, so Factotum settled for a spot in the orchestra pit with his horn, while the rest of the party either served as bouncers or sat in the audience. I asked the DM if Factotum could be an understudy, and he was fine with it. Excellent!

Imagine my delight, then, when the opening scene of the adventure involved an opera where a man was about to duel his sweetheart’s father, and the man fell to the stage – apparently dead after drinking poisoned wine. The poisoned wine was not part of the show, and it was soon accompanied by an angry crowd being riled up by some thugs. While the rest of the party sprang into action fighting the thugs, Factotum did the natural thing for him:

He jumped on stage, picked up the fallen actor’s sword, and continued the faux sword fight with the actor playing the love interest’s father.

He feinted and twirled, finding the time to shout words of majesty to his ailing compatriots and to sing powerfully to push interlopers off the stage (Majestic Word and Staggering Note), but largely focused on entertaining the crowd.

Yes, this adventure was tailor-made for Factotum.

The rest of the evening was a fun investigative romp, ultimately culminating in a fight with other actors. Factotum attacked one man who hadn’t directly menaced anyone yet, simply because the man was a terrible actor – an unforgivable offense.

WATE 2-4 is an adventure that definitely benefits from having a bard in the party. I’m sure it could have been fun without one, but I was really glad I’d brought Factotum to the table. It’s an opportunity for his fame to grow!

MapTool: Using the latest version

I first discovered MapTool in May 2010. At the time, the most up-to-date version of the program was good ol’ 1.3.b66. That’s what I downloaded, and that’s what I kept using for about a year and a half.

Why? Well, first of all, it worked. Second, I had heard that certain things that worked in older versions (macros, etc.) would not necessarily work if you tried them out in a newer version. Third, new builds kept coming out regularly, and I didn’t want my players to have to constantly download new programs just to connect to my game.

Recently, I was browsing the MapTool wiki and forums and found some discussion about the program’s performance. Specifically, I saw a note that moving a whole bunch of tokens at once was faster in newer builds.

Now, I don’t have many complaints about MapTool, but it’s definitely been a major annoyance to want to move the entire party from one section of the map to another and having to wait 30 seconds for MapTool to make this happen. So, I downloaded the current version of MapTool, 1.3.b86 (as of this writing), which has been the most recent version for months (and thus pretty stable).

Woo hoo – I can now move mobs of tokens across the map instantly! I can also run a macro on a whole bunch of tokens at once – such as when I have a bunch of enemies that are invisible to the players and I want to run my “Toggle Visibility” macro on all of them. Previously, the tokens would blink into existence one by one (which was a kind of neat effect, I suppose, but not really what I wanted). Now – poof, they all appear! Awesome!

Best of all, I haven’t found any macros that have broken yet. I try to keep my macros pretty straightforward, of course, and I’m guessing that helps. But every adventure I’ve opened so far in 1.3.b86, even though it was created in 1.3.b66, has had no problems.

The real test will come on Friday, when my regular players will have downloaded the new software and will try playing with it together. Honestly, I’m expecting it to be a non-issue, but we shall see.

So, the moral of the story for me is: Use the latest version of MapTool, as long as it’s been out for, say, a month with no updates. I don’t want to update constantly, but in this case the update is definitely worth the trouble.

MapTool macro: Attack and defense modifier states

More MapTool macro fun! Yes, I’m a nerd and proud of it.

In a game like Dungeons and Dragons 4th Edition, it’s pretty common to get temporary bonuses to attack rolls or defenses. When you’re playing with pen and paper, you have to keep track of these yourself. When you’re using MapTool, the program can help.

Now, I’m sure that some of the snazzier MapTool frameworks out there will handle temporary modifiers in super-fancy ways that actually change the calculation of attacks based on these modifiers and so on. I’m not looking for any of that. I’m simply looking for a visible reminder on a token that it has a temporary bonus or penalty.

I started creating +2 attack and-5 attack and +2 defense states in a piecemeal manner months ago. As PCs and monsters in my games started getting powers that could apply these bonuses or penalties, I created states that would remind me of them. I would then turn these states on and off very manually – right click on the token, find the right state, click on it. Repeat to turn it off.

Well, I’m sick of that, so I decided to automate the process.

First, I created a set of 20 token states: +1 to +5 attack, -1 to -5 attack, +1 to +5 defense and -1 to -5 defense. They look something like this:

      

Depending on whether I’m using MapTool online or with my projector setup, I either display these states as 3×3 grid images (online) or 2×2 grid images (projector – the larger images show up better). The red boxes are for attack (or damage) and the blue boxes are for defenses. I use 80% opacity, so you can still make out a little of the token art behind the states.

A monster with +5 to attack, one with -3 to defense, and one with +2 to attack and defense

For a while, I was creating individual toggle buttons for my states. Click a token, then click the “+2 Attack” button, and the +2 Attack state will toggle on or off. Now that I have 20 of these states, I decided to streamline. Rather than 20 buttons, I have two: One to set attack modifiers and one to set defense modifiers.

Attack bonus macro (download the macro)

[h: x=input("NewMod|0|What is the token's new attack modifier?")]
[h: abort(x)]

I start by asking for the new attack modifier in a pop-up; if the user clicks the Cancel button, the macro ends. Note that the user can specify a +2 bonus as either “2” or “+2”; the macro works either way.

[h: SelectedTokens=getSelected()]
[FOREACH(TokenID, SelectedTokens, " "), CODE:

I create a list of token IDs called SelectedTokens for however many tokens are currently selected, and then I loop through each of these tokens to perform the same code on them. This lets me apply (or remove) a bonus or penalty to a bunch of tokens at once.

 {[if(NewMod>5 || NewMod<-5), CODE: {[assert(1==0,add("<b>Error</b>: Attack modifier must be between -5 and +5"),0)]}; {}]

I throw off an error message if the user tries to enter a number over 5 or under -5, since that’s the range of states I’ve created.

  [h: OldMod=getProperty("AttackState",TokenID)]
  [h: OldState=if(OldMod>=0, add("+", OldMod, " Attack"), add(OldMod, " Attack"))]
  [h: NewState=if(NewMod>=0, add("+", NewMod, " Attack"), add(NewMod, " Attack"))]

I figure out what the current value of the token’s AttackState property is (a number from -5 to 5), and then convert this to a string like “+2 Attack” or “-3 Attack”. This corresponds to the names of the states in my campaign file. I do the same for the new attack modifier, getting the appropriate state name.

  [h, if(OldMod==0), CODE:{}; {[h: setState(OldState,0,TokenID)]}  ]
  [h, if(NewMod==0), CODE:{}; {[h: setState(NewState,1,TokenID)]}  ]

If the new or old modifier is zero, I don’t try to change it (since no state is displayed on the token for a zero bonus). Otherwise, I turn off the old state (setting it to false, or 0) and turn on the new state (setting it to true, or 1).

  [h: setProperty("AttackState", NewMod, TokenID)]
    }
   ]

I set the value of the AttackState property to whatever the user entered, so that the macro can reference that property the next time it’s run. I then close the CODE block and the FOREACH loop that I started at the top. Voila!

The whole macro is as follows:

[h: x=input("NewMod|0|What is the token's new attack modifier?")]
[h: abort(x)]

[h: SelectedTokens=getSelected()]
[FOREACH(TokenID, SelectedTokens, " "), CODE:
 {[if(NewMod>5 || NewMod<-5), CODE: {[assert(1==0,add("<b>Error</b>: Attack modifier must be between -5 and +5"),0)]}; {}]
  [h: OldMod=getProperty("AttackState",TokenID)]
  [h: OldState=if(OldMod>=0, add("+", OldMod, " Attack"), add(OldMod, " Attack"))]
  [h: NewState=if(NewMod>=0, add("+", NewMod, " Attack"), add(NewMod, " Attack"))]
  [h, if(OldMod==0), CODE:{}; {[h: setState(OldState,0,TokenID)]}  ]
  [h, if(NewMod==0), CODE:{}; {[h: setState(NewState,1,TokenID)]}  ]
  [h: setProperty("AttackState", NewMod, TokenID)]
    }
   ]

Defense bonus macro (download the macro)

The defense macro is exactly the same as the attack macro – just replace “Attack” with “Defense”.

[h: x=input("NewMod|0|What is the token's new defense modifier?")]
[h: abort(x)]

[h: SelectedTokens=getSelected()]
[FOREACH(TokenID, SelectedTokens, " "), CODE:
 {[if(NewMod>5 || NewMod<-5), CODE: {[assert(1==0,add("<b>Error</b>: Defense modifier must be between -5 and +5"),0)]}; {}]
  [h: OldMod=getProperty("DefenseState",TokenID)]
  [h: OldState=if(OldMod>=0, add("+", OldMod, " Defense"), add(OldMod, " Defense"))]
  [h: NewState=if(NewMod>=0, add("+", NewMod, " Defense"), add(NewMod, " Defense"))]
  [h, if(OldMod==0), CODE:{}; {[h: setState(OldState,0,TokenID)]}  ]
  [h, if(NewMod==0), CODE:{}; {[h: setState(NewState,1,TokenID)]}  ]
  [h: setProperty("DefenseState", NewMod, TokenID)]
    }
   ]

I hope this type of macro is useful for folks out there. As always, let me know if you have questions about my macros or requests for new macros. I love this kind of thing, as you can no doubt tell!

Note: All macros were generated with version 1.3.b66 of MapTool, but they work with 1.2.b86 as well.

Download the properties for online games with these states

Download the properties for projector games with these states

Download my complete campaign template for online games

Download my complete campaign template for projector games

Rejected for DDI – and I feel fine!

Edit: The final, polished version of the adventure can be found at this link.

I decided to submit the third adventure in my Staff of Suha trilogy to Dungeon Magazine for their consideration. The timing was right, after all; the adventure was ready to go at just about the time the submission window would be opening (October 1).

I spent a lot of time in September trying to polish the adventure itself, figuring that if I could attach the finished adventure to the “pitch” email it would help my chances. I ran the adventure four times and had a couple of friends and a couple of readers from my blog look over it and provide really useful feedback.

Since I live in the Mountain time zone, the submission window opened at 10:00 PM Friday night for me (midnight Eastern time). I was finishing my Friday night game that I run via MapTool at the time, after which I read Chris Perkins’ editorial about submitting pitches. I had already written the pitch weeks before, so I went ahead and submitted it exactly one hour after the window opened. The entirety of my email to submissions@wizards.com follows:

Descent Into Darkness – an adventure for 8th-10th level characters – 5,000 – 6,000 words

The powerful wizardess Tallinn seeks adventurers to be teleported into the Underdark bearing a powerful magical artifact, the Staff of Suha. The mission: Find three other artifacts that have been stolen by unknown creatures, likely in an effort to recreate a teleportation device once used by a long-dead drow sorcerer to bring his foul armies to the overworld in conquest. The other three artifacts (Orb of Oradia, Chalice of Chale and Shield of Shalimar) must be recovered or destroyed, and the forces behind their theft must be stopped.

The adventurers discover that the powerful beholder Ergoptis has enslaved drow, diggers (new insectoid monsters), halfling thieves and mindless duergar as soldiers and hunters of artifacts. The party must fight their way through treacherous traps and puzzles to ultimately face Ergoptis and its underlings in a room dominated by a ziggurat, with a magma river crossed by bridges and floating platforms. Can they recover the final artifact and escape or destroy Ergoptis before the one-hour time limit on their teleportation ritual runs out? Or will the beholder simply add the adventurers to its army of enslaved warriors and continue its plans for domination?

Descent Into Darkness includes four new artifacts, an all-new monster (the digger), a find-the-path puzzle with custom runes and an exciting final encounter with an evil beholder.

Link to a PDF of the current draft of the adventure, complete with maps, stat blocks, puzzles, etc: http://dl.dropbox.com/u/6875434/Descent%20into%20Darkness%20Submission.pdf

Michael, the OnlineDM

http://onlinedm.wordpress.com/

That was the pitch. I wondered how long it would take to get a response; they said that they’ll reply to everyone within two months of the close of the submission period, which meant that I could theoretically have to wait until the end of January.

Fortunately, I didn’t have to wait that long. Monday morning I received the following email from Chris Perkins:

Hi Michael,

Thanks for the adventure proposal. The “artifact hunt” story doesn’t really grab me, so I’m going to pass on this one. We see a lot of artifact hunts, and four artifacts seems a bit much (the write-ups for them alone would eat up thousands of words of text). Also, we already have an adventure in the works featuring a beholder villain.

Regards,

Chris Perkins
D&D Senior Producer
Wizards of the Coast LLC

I’ve got to say, I felt pretty good about that. No, they didn’t accept my adventure, but Chris took the time to explain what he didn’t like about the adventure. “Collect the artifacts” doesn’t interest him, and they have another adventure coming with a beholder villain. That’s totally fair.

I came away from this feeling pretty good. The best part was that I got the response quickly, which means that I can release the adventure here on the blog!

The version I actually submitted to Chris is at this link. It’s set up specifically for upper-heroic parties.

However, I also assembled the adventure in a way that can be run with any level in heroic tier (though I feel that the adventure runs best at level 6 or higher). That version can be downloaded here.

I’ll talk more about the adventure itself in a later post, but I wanted to share my thoughts about the process for anyone else who wants to submit an adventure to Dungeon.

First, just focus on the pitch, not the finished product. I’m sure that Chris didn’t even look at the link I sent him, and I don’t blame him for that. He has tons of submissions to go through, and he’s not going to read a sixteen-page PDF for each submission.

Second, be creative. It seems like the key is to pitch something that makes the editor say, “Wow, I’ve never seen anything quite like that before!” The key is novelty, not execution, when it comes to the pitch. I feel like I’ve put together a fun, solid adventure, but the things that make it fun and solid (cool combats, puzzles, magic items, etc.) aren’t the things that make a good pitch. It’s a fairly run-of-the-mill adventure premise, and that’s not going to get it past the initial screen.

Third, do your research. In my case, my adventure was rejected in part because there’s an upcoming adventure with a similar villain, which I couldn’t have known about. But I’m sure that if I had pitched an adventure whose villain was similar to something done in the past few issues of Dungeon, it would have also been rejected. And there’s no excuse for me not knowing that.

Anyway, I’m glad I went through the submission process, and I’m especially glad that Chris handled my rejection letter the way he did – quickly, professionally, and with some helpful feedback. If I get any truly inspired ideas for adventures, I might pitch again. But this process was a good one for me.

MapTool macros: Numbers on tokens – elevation, etc.

Ah, how I love to geek out with new MapTool macros!

Some months ago, I was going to be running a Living Forgotten Realms adventure that involved underwater combat, which meant that elevation could come into play. I wanted to have a visible way of displaying a token’s elevation right on the token itself, so I settled on using some token states that would display a number from 1 to 9 on the upper right corner of the token, representing its elevation.

However, I later realized that having a generic number to stick on a token is a useful thing. For instance, a later battle with a hydra showed me that it would be nice to have an indicator for the number of heads. Furthermore, I’d like to be able to display higher numbers as well, just in case.

Three monsters - one with no elevation, one with elevation 29 and one with elevation 4

Enter the new Elevation macro. First, I should explain that this macro requires you to have a certain set of token states (downloadable as a full set of properties and states here for online use or here for projector use) that include “_0” through “_9” for the ones digit and “0_” through “9_” for the tens digit. You’ll need an Elevation property on your token. And then you’ll need a macro like this one, which you can keep on either the Campaign or Global window.

[h: x=input("NewElevation|0|What is the token's new elevation?")]
[h: abort(x)]

The code above brings up an input window that asks for the new elevation and ends the macro if the user chooses the Cancel option.

[h: SelectedTokens=getSelected()]
[FOREACH(TokenID, SelectedTokens, " "), CODE:

This creates a list of token IDs called SelectedTokens, then starts a loop through each of the tokens, performing the following code on them. Thus, you could set the elevation for a group of three monsters all to the same level at once if you wished.

 {[h: OldElevation=getProperty("Elevation",TokenID)]

Figure out the token’s current Elevation property; assign it to the OldElevation variable.

   [if(NewElevation>99), CODE: {[assert(1==0,add("<b>Error</b>: Elevation cannot be higher than 99"),0)]}; {}]
   [if(NewElevation<0), CODE: {[assert(1==0,add("<b>Error</b>: Elevation cannot be lower than 0"),0)]}; {}]

Give an error message if the user tries to enter a new elevation that’s over 99 or less than zero.

   [h: OldTensDigit=FLOOR(OldElevation/10)]
   [h: OldOnesDigit=OldElevation-10*OldTensDigit]

   [h: NewTensDigit=FLOOR(NewElevation/10)]
   [h: NewOnesDigit=NewElevation-10*NewTensDigit]

Figure out the tens digit and ones digit of both the old and new elevations. I don’t know if MapTool has a modulus function, so I went with an alternative way of getting the ones digit (pretty elegant in the end, I think).

   [h: OldOnesState=add("_", OldOnesDigit)]
   [h: OldTensState=add(OldTensDigit, "_")]

   [h: NewOnesState=add("_", NewOnesDigit)]
   [h: NewTensState=add(NewTensDigit, "_")]

Since the states are called “_1” and “_2” and so on for the ones, and “1_” and “2_” and so on for the tens, I need to concatenate some strings to set up the names of the old and new states (in preparation for removing the old states and adding the new).

   [h: setState(OldOnesState,0,TokenID)]
   [h: setState(OldTensState,0,TokenID)]

   [h: setState(NewOnesState,1,TokenID)]
   [h: setState(NewTensState,1,TokenID)]

And here I actually set those states. The old states get set to zero (turned off) while the new states get set to one (turned on). Thus, if the token is changing from an elevation of 13 to an elevation of 20, I’m turning off “1_” and “_3” and turning on “2_” and “_0”.

   [h: setProperty("Elevation", NewElevation, TokenID)]

Since I’m changing the elevation states, I need to make sure I also change the elevation value on the token itself so that the macro will work again the next time I run it (the new elevation after this go-through will become the old elevation for the next time I run the macro).

   [if(NewElevation<10),CODE:{[h: setState(NewTensState,0,TokenID)]};{}]

This is just for prettiness’ sake; if the new elevation is between 1 and 9, I want to drop the zero from the tens place so the elevation will show up as “7” instead of “07” for instance.

   [if(NewElevation==0),CODE:{
     [h: setState(NewOnesState,0,TokenID)]
     [h: setState(NewTensState,0,TokenID)]};{}]

Further prettiness; If the new elevation is zero, I want no elevation states to be displayed at all, so I now remove the new states that I just added.

    }
   ]

And here I close the FOREACH loop and the Code brackets that I started way up at the top.

This macro is useful any time you want to stick a visible number on a token for whatever reason. Now that I’ve got it working, I’ll be interested to see how often it will come up. My suspicion is that I’ll end up using it a lot.

As always, please let me know what macro questions you have or any requests for new macros. I love this stuff!

Note: All macros were generated with version 1.3.b66 of MapTool.

Campaign template for online use, including this and all of my other macros

Campaign template, formatted for projector use

Campaign properties for online use

Campaign properties for projector use

Download the elevation macro alone

The full elevation macro code:

[h: x=input("NewElevation|0|What is the token's new elevation?")]
[h: abort(x)]

[h: SelectedTokens=getSelected()]
[FOREACH(TokenID, SelectedTokens, " "), CODE:
 {[h: OldElevation=getProperty("Elevation",TokenID)]
   [if(NewElevation>99), CODE: {[assert(1==0,add("<b>Error</b>: Elevation cannot be higher than 99"),0)]}; {}]
   [if(NewElevation<0), CODE: {[assert(1==0,add("<b>Error</b>: Elevation cannot be lower than 0"),0)]}; {}]

   [h: OldTensDigit=FLOOR(OldElevation/10)]
   [h: OldOnesDigit=OldElevation-10*OldTensDigit]

   [h: NewTensDigit=FLOOR(NewElevation/10)]
   [h: NewOnesDigit=NewElevation-10*NewTensDigit]

   [h: OldOnesState=add("_", OldOnesDigit)]
   [h: OldTensState=add(OldTensDigit, "_")]

   [h: NewOnesState=add("_", NewOnesDigit)]
   [h: NewTensState=add(NewTensDigit, "_")]

   [h: setState(OldOnesState,0,TokenID)]
   [h: setState(OldTensState,0,TokenID)]

   [h: setState(NewOnesState,1,TokenID)]
   [h: setState(NewTensState,1,TokenID)]

   [h: setProperty("Elevation", NewElevation, TokenID)]

   [if(NewElevation<10),CODE:{[h: setState(NewTensState,0,TokenID)]};{}]

   [if(NewElevation==0),CODE:{
     [h: setState(NewOnesState,0,TokenID)]
     [h: setState(NewTensState,0,TokenID)]};{}]
    }
   ]