Help!

Discuss macro implementations, ask for macro help (to share your creations, see User Creations, probably either Campaign Frameworks or Drop-in Resources).

Moderators: dorpond, trevor, Azhrei, giliath, jay, Mr.Ice

vstraydogstrutv
Kobold
Posts: 15
Joined: Sun Jul 04, 2010 7:20 am

Help!

Post by vstraydogstrutv »

Hi everyone. New to the forum, fairly new to MapTools and digging into the macros.

I have a macro I'm trying to write that will give my visible PC tokens the appropriate amount of XP for killing a monster.

Here's what I have so far, though this is subject to change:
[h: CurrentHP = CurrentHP ]
[h: CurrentHP = CurrentHP - damage ]
[h: bar.Health = CurrentHP / HP ]
[r, if(CurrentHP <= 0): table(getProperty("CR"), getProperty("CR")) + " experience." ]
[h, if(CurrentHP <= 0): setState("Dead", 1)]

Ideally, what I would like to accomplish is this:
If the currentHP <= 0, then it gets the visible PC's tokens, getProperty("ECL") for each token, adds that together and divides by the number of visible tokens (to get the average ECL); and references THAT number with the monster's CR (challenge rating) to get the total EXPERIENCE (from the tables) THEN divides that number by the visible PC tokens AND FINALLY references the "Add XP" macro on those PC tokens and adds the appropriate experience.

To clarify: if monster's hp is 0, get visible PC's, find average for ECL & cross-reference that number to select appropriate table, get monster's CR to select appropriate XP from selected table, divide XP by the number of visible PC's, add new XP value to each visPC.

Any thoughts? I'll tell you right now that the whole JSON thing is a little confusing to me, so small, simple words would be appreciated ;)

User avatar
mfrizzell
Dragon
Posts: 762
Joined: Sat Feb 13, 2010 2:35 am
Location: California

Re: Help!

Post by mfrizzell »

I wrote a macro to do the same end result but in a very different way. Basically what I did was create a loop that checked to see if a token was dead, if it was an NPC and if those were true simply get a property on the token that was it's XP value. The loop continued to add XP to the pot until there were no more dead NPC's(I think it might have been visible NPC's otherwise you would be getting XP for NPC more than once if they were on the same map).
I just displayed my total to chat so the players could add it on their own. But another loop through PC's adding to their XP total a correct division would be possible to.
DCI/RPGA# 7208328396 Skype ID mfrizzell77
Characters:
Strabor - Dwarf Avenger 5th Level
Tikkanan - Human Warlock 2nd Level
----------------------------------------------------
"People are more violently opposed to fur than leather because it's safer to harass rich women than motorcycle gangs."

MikeKozar
Cave Troll
Posts: 73
Joined: Sat Sep 12, 2009 6:35 pm

Re: Help!

Post by MikeKozar »

Okay, everything you want to do can be done, but it's a moderately complex process.

First, we need to decide on who to split the XP among - you mentioned Visible, but that runs the risk of missing people when you are zoomed in. You could get all PC tokens on the map, using PC as a flag to indicate the party. Here's some sample code that will return a list of names:

Code: Select all

[h: Conditions= '{ pc:1 }']
[r: PlayerList=getTokenNames(",",Conditions)]

It is worth checking out the full array of options on getTokenNames(). You can quickly check for everyone within a radius, exclude anyone with a "Dead" state, and a couple other tricks. This is just set up to check for a PC flag on the Token properties page.
[wfunc]getTokenNames[/wfunc]


Once we have a list of player names, we use a listCount and a FOREACH to build your average ECL, and then compare that to your monster. Here's an example of that:

Code: Select all

[h: PlayerList="Adam,Bob,Charlie"]
[h: PlayerCount=listCount(Playerlist)]
[h: Total=0]
[h, FOREACH(Player,Playerlist), CODE:{
   [Total=Total+getProperty("ECL",Player)]
   }]
[Average=floor(Total/PlayerCount)]

I used floor() to have the program round down; ceil() would round up, and you could use a function like average() if you wanted it with a decimal point.
FOREACH()
[wfunc]floor[/wfunc]
[wfunc]ceil[/wfunc]
[wfunc]average[/wfunc]

So now we have a list of players, and the party's average ECL. You had a plan to compare it to the monster's CL and send that to a table, which will return a total number of XP. Once you have that, you need to distribute it. This seems to work:

Code: Select all

[h: PlayerList="Adam,Bob,Charlie"]
[h: PlayerCount=listCount(Playerlist)]
[h: TotalXP=33333]
[h: RewardXP=floor(TotalXP/Playercount)]
[h, FOREACH(Player,Playerlist), CODE:{
   [switchToken(Player)]
   [XP=XP+RewardXP]
   }]

This is where we get into that Trusted Macro thing - the code is set up to keep players from messing with other peoples character sheets. If a player needs to run this program, it will need to be on a macro that they cannot edit; I usually save all of my macros to a [wiki]Library_Token[/wiki] and call them with macro(). Short version: Only the GM can hand out XP to everybody at once.

Does that help?

vstraydogstrutv
Kobold
Posts: 15
Joined: Sun Jul 04, 2010 7:20 am

Re: Help!

Post by vstraydogstrutv »

Yes! I'm fairly certain I get all of that, and what I don't I'm sure I'll figure out; got a knack for that.

Thank you ever so much for such a prompt reply. I'm glad to see such an active, helpful forum. I use Obsidian Portal to keep track of my quest itself and they have a similarly helpful forum, and it's nice to know that there are people willing to be so kind.

vstraydogstrutv
Kobold
Posts: 15
Joined: Sun Jul 04, 2010 7:20 am

Re: Help!

Post by vstraydogstrutv »

Also, I did want to select just the visible PC tokens, as I want only the ones attacking said monster to get the xp if they kill it. If another group is taking on another monster, they would get that xp, or if someone is hiding and not taking part in the battle, they would get none. I see that as just the nature of the RPG beast; if you don't participate, don't expect to gain any levels.

vstraydogstrutv
Kobold
Posts: 15
Joined: Sun Jul 04, 2010 7:20 am

Re: Help!

Post by vstraydogstrutv »

Is there a way to stop the macro from continuing through the rest of its code? I haven't run across it yet in the Wiki.

Something like: if(condition is not met): end macro.

User avatar
biodude
Dragon
Posts: 444
Joined: Sun Jun 15, 2008 2:40 pm
Location: Montréal, QC

Re: Help!

Post by biodude »

vstraydogstrutv wrote:Is there a way to stop the macro from continuing through the rest of its code? I haven't run across it yet in the Wiki.

Something like: if(condition is not met): end macro.


Check out [wfunc]abort[/wfunc]. Note that abort will end macro execution if the argument passed to it is == 0. Thus, you could achieve what you describe like so:

Code: Select all

[H, IF( condition is met ): abort(0) ]
OR

Code: Select all

[H: abort( if( condition , 1 , 0 ) )]


There is also [wfunc]assert[/wfunc], which is intended more for custom error messages. It, too stops macro execution when the first argument evaluates to 0, and then sends a custom error message (to the user running the macro). It's handy for checking input and arguments passed in to a function.

Code: Select all

[H: assert( condition , "error message if condition is not met" )]
"The trouble with communicating is believing you have achieved it"
[ d20 StatBlock Importer ] [ Batch Edit Macros ] [ Canned Speech UI ] [ Lib: Math ]

MikeKozar
Cave Troll
Posts: 73
Joined: Sat Sep 12, 2009 6:35 pm

Re: Help!

Post by MikeKozar »

The issue with Abort() is that it also discards output. It's not so much an END as a CANCEL.

What I wind up doing is a lot of IF statements as the code goes on...IF(Hits>Dodge), CODE: etc. This can be kind of a pain when you have complicated code, since the MapTools macro language only supports nesting 2 IF statements.

IF(Dodged)
IF(ArmorDefense)
...

Would work, but


IF(Dodged)
IF(ArmorDefense)
IF(DamageReduction)
...

Wouldn't work. You get used to it. One of the tricks is to call macros on Library tokens, like so:

Code: Select all

   [macro("[email protected]:SR4"):0]



You also mentioned visible tokens; be careful you don't zoom in far enough to miss people. You could have a list on the monster of anyone who landed a hit on it, provided you don't have a healer hiding in the back who deserves Xp.

prestidigitator
Dragon
Posts: 317
Joined: Fri Apr 23, 2010 8:17 pm

Re: Help!

Post by prestidigitator »

MikeKozar wrote:The issue with Abort() is that it also discards output. It's not so much an END as a CANCEL.

Yes. And not only cancels the current macro, but any macro that called it, and any macro that called that... all the way up the call chain.

However, there's an (ugly) solution. You can save all output in a property somewhere (because unlike chat output, other side effects remain) and use [wfunc]execLink[/wfunc] (with deferment) to write the output to chat and clear the buffer.... :mrgreen:
"He knows not how to know who knows not also how to un-know." --Sir Richard Burton

vstraydogstrutv
Kobold
Posts: 15
Joined: Sun Jul 04, 2010 7:20 am

Re: Help!

Post by vstraydogstrutv »

Thanks for the tips, all. As far as the abort portion goes, it worked out well for what I needed.

Here's what I did (for those of you reading, here's the key: MCHP=monster's current hp; ECL=encounter level; Average=Player's average ECL; number=The number of xp each player gets after it's divided by the those players):

Code: Select all

[h: MCHP = getProperty("MCHP") - damage ]
[h: bar.Health = MCHP / HP ]
[h, if(MCHP <= 0): setState("Dead", 1)]

[h, if(MCHP >= 1): abort(0)]
[h:Conditions= '{ pc:1 }']
[h: PlayerList = getTokenNames(", ",Conditions)]
[h: PlayerCount=listCount(Playerlist)]
[h: Total=0]
[h, FOREACH(Player,Playerlist), CODE:{
   [Total=Total+getProperty("ECL",Player)]
   }]
[h:Average= floor(Total/PlayerCount)]
[h: XP = table(Average, getProperty("CR")) ]
[h: number = floor(XP/PlayerCount)]
[h, FOREACH(Player,Playerlist), CODE:{
   [switchToken(Player)]
   [Experience=Experience+number]
   }]


I still haven't worked in visible tokens into it, because for some reason even if I specify that I want just PC tokens, it still includes the monster in the list. So for now, I figure I'll just let all of my PC's get XP, even if they aren't involved. I have a vacation coming up, so I'll probably iron all of that out then.

User avatar
mfrizzell
Dragon
Posts: 762
Joined: Sat Feb 13, 2010 2:35 am
Location: California

Re: Help!

Post by mfrizzell »

vstraydogstrutv wrote:Thanks for the tips, all. As far as the abort portion goes, it worked out well for what I needed.

Here's what I did (for those of you reading, here's the key: MCHP=monster's current hp; ECL=encounter level; Average=Player's average ECL; number=The number of xp each player gets after it's divided by the those players):

Code: Select all

[h: MCHP = getProperty("MCHP") - damage ]
[h: bar.Health = MCHP / HP ]
[h, if(MCHP <= 0): setState("Dead", 1)]

[h, if(MCHP >= 1): abort(0)]
[h:Conditions= '{ pc:1 }']
[h: PlayerList = getTokenNames(", ",Conditions)]
[h: PlayerCount=listCount(Playerlist)]
[h: Total=0]
[h, FOREACH(Player,Playerlist), CODE:{
   [Total=Total+getProperty("ECL",Player)]
   }]
[h:Average= floor(Total/PlayerCount)]
[h: XP = table(Average, getProperty("CR")) ]
[h: number = floor(XP/PlayerCount)]
[h, FOREACH(Player,Playerlist), CODE:{
   [switchToken(Player)]
   [Experience=Experience+number]
   }]


I still haven't worked in visible tokens into it, because for some reason even if I specify that I want just PC tokens, it still includes the monster in the list. So for now, I figure I'll just let all of my PC's get XP, even if they aren't involved. I have a vacation coming up, so I'll probably iron all of that out then.


A couple of thought here. You could give the PC's a 'Character State' then just do a search for visible tokens and refine it with the Character state.
As for your monsters, you could look for state dead or dying however the flow of your game goes. Or like the character state, you could set a state a death called 'XP' then remove it when you run the macro to collect. Just thoughts off the top of my head.
DCI/RPGA# 7208328396 Skype ID mfrizzell77
Characters:
Strabor - Dwarf Avenger 5th Level
Tikkanan - Human Warlock 2nd Level
----------------------------------------------------
"People are more violently opposed to fur than leather because it's safer to harass rich women than motorcycle gangs."

prestidigitator
Dragon
Posts: 317
Joined: Fri Apr 23, 2010 8:17 pm

Re: Help!

Post by prestidigitator »

vstraydogstrutv wrote:I still haven't worked in visible tokens into it, because for some reason even if I specify that I want just PC tokens, it still includes the monster in the list.


This: "{ pc:1 }" is not a valid JSON object. You must put quotes around the field name, like so "{'pc':1}" or, better yet, use something like:

Code: Select all

json.set("{}", "pc", 1)


Also, you might want to try the [wfunc]getPC[/wfunc] or [wfunc]getPCNames[/wfunc] function instead of [wfunc]getTokenNames[/wfunc] unless you want to add more conditions (such as selected, state, player visibility, etc.).
"He knows not how to know who knows not also how to un-know." --Sir Richard Burton

User avatar
Azhrei
Site Admin
Posts: 12058
Joined: Mon Jun 12, 2006 1:20 pm
Location: Tampa, FL

Re: Help!

Post by Azhrei »

prestidigitator wrote:This: "{ pc:1 }" is not a valid JSON object. You must put quotes around the field name, like so "{'pc':1}" or, better yet, use something like:

Well, technically, it is valid JSON syntax. It's just not safe to do in the general case because the property name could be a reserved word.

In MTscript there are currently no reserved words within JSON objects, but once we get JavaScript support words like "for" and "while" will be reserved and will cause syntax errors if used without single or double quotes.

prestidigitator
Dragon
Posts: 317
Joined: Fri Apr 23, 2010 8:17 pm

Re: Help!

Post by prestidigitator »

Azhrei wrote:Well, technically, it is valid JSON syntax. It's just not safe to do in the general case because the property name could be a reserved word.

Huh. Okay. Well, I stand corrected, though I know I've run into problems in MapTool before when forgetting to add the quotes. I remember because it was one of the most annoying problems to try to track down.... And I don't think I was using spaces or punctuation or anything in the key names. Hmm.
"He knows not how to know who knows not also how to un-know." --Sir Richard Burton

User avatar
aliasmask
Deity
Posts: 8610
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: Help!

Post by aliasmask »

I've noticed that "class" can not be used in a json as a key word. I didn't know (or realize) I could leave off the quotes for keywords.

Post Reply

Return to “Macros”