[AM Macros] Lindsay's PF/3.5 Campaign FW (11-13-15)

Discussion concerning lmarkus' campaign framework for D&D3.x and Pathfinder.

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

Forum rules
Discussion regarding lmarkus001's framework only. Other posts deleted without notice! :)
User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW (9-1-15)

Post by aliasmask »

I also have a group of new macros for dealing with the character advancement chart in regards to xp and gold.
Lib:libDnD35Pathfinder wrote:
/// AM Update \\\

Code: Select all

@@ @tabTable
@PROPS@ fontColor=black ; autoExecute=true ; fontSize=11pt ; sortBy=500 ; color=aqua ; playerEditable=false ; applyToSelected=false ; group=AM Updates ; tooltip= ; minWidth=94 ; 
<!-- tabTable(rawData,headers,rowReference) 
   rawData - tab deliminated table where all the last column data is filled in (error if not)
   headers - Use the first line as a header making the inside dimension an object where the key is the column name
      A value of 0 is no header, a value of 1 includes the header and a value of 2 makes the internal object just a string prop.
   rowReference - Use the first data item as a row header. This makes the outside dimension an object where the key is the first data item
      This item is still included in the data object.
      
   Convert tab deliminated text to a two dimensional json object.
-->

[H: rawData = arg(0)]
[H, if(argCount() >= 2): headers = arg(1); headers = 0]
[H, if(argCount() >= 3): rowRef = arg(2); rowRef = 0]

[H: TAB = decode("%09")]
[H: EOL = decode("%0A")]

[H: rawDataObj = json.fromList(rawData,EOL)]
[H, if(rowRef): returnData = "{}"; "[]"]

[H, foreach(line,rawDataObj), code: {
   [H: index = roll.count]
   [H: lineData = json.fromList(line,TAB)]
   [H: firstData = json.get(lineData,0)]
   [H, if(index == 0 && headers), code: {
      [H: propList = json.fromList(line,TAB)]
   };{}]
   [H, if(headers && index >= 1), code: {
      [H: lineObj = "{}"]
      [H, foreach(item,lineData): lineObj = json.set(lineObj,json.get(propList,roll.count),item)]
   };{
      [H: lineObj = lineData]
   }]
   [H, if(headers == 2): lineObj = json.toStrProp(lineObj)]
   [H, if(rowRef && index >= 1): returnData = json.set(returnData,firstData,lineObj)]
   [H, if(! rowRef && index >= 1): returnData = json.append(returnData,lineObj)]
}]

[H: macro.return = returnData]

!!
@@ @getMonsterXp
@PROPS@ fontColor=black ; autoExecute=true ; fontSize=11pt ; sortBy=500 ; color=aqua ; playerEditable=false ; applyToSelected=true ; group=AM Updates ; tooltip= ; minWidth=94 ; 
<!-- getMonsterXp(cr): xp -->

[H: monsterCr = round(100 * arg(0))/100]
[H: monsterXpTable = getLibProperty("monsterXp")]
[H: monsterXp = json.get(json.get(monsterXpTable,monsterCr),"monster.xp")]

[H: macro.return = monsterXp]

!!
@@ @getCharXpGold
@PROPS@ fontColor=black ; autoExecute=true ; fontSize=11pt ; sortBy=500 ; color=aqua ; playerEditable=false ; applyToSelected=false ; group=AM Updates ; tooltip= ; minWidth=94 ; 
<!-- getCharXpGold(progression,level): XP or gold value
   progression - fast,medium,slow,gold are the options for returning the respective xp or gold value from advancement table
   level - From 1 to 20. Level 1 is a homebrew value.
-->

[H: progression = lower(arg(0))]
[H: charLevel = arg(1)]

[H: propName = "xp."+progression]
[H: charAdvancementTable = getLibProperty("charAdvancement")]
[H: levelAdvancement = json.get(charAdvancementTable,charLevel)]

[H: macro.return = json.get(levelAdvancement,propName)]

!!
@@ @initXpTables
@PROPS@ fontColor=black ; autoExecute=true ; fontSize=11pt ; sortBy=500 ; color=aqua ; playerEditable=false ; applyToSelected=false ; group=AM Updates ; tooltip= ; minWidth=94 ; 
[H: charAdvancement = 'xp.level	xp.slow	xp.medium	xp.fast	xp.gold
1	0	0	0	500
2	3000	2000	1300	1000
3	7500	5000	3300	3000
4	14000	9000	6000	6000
5	23000	15000	10000	10500
6	35000	23000	15000	16000
7	53000	35000	23000	23500
8	77000	51000	34000	33000
9	115000	75000	50000	46000
10	160000	105000	71000	62000
11	235000	155000	105000	82000
12	330000	220000	145000	108000
13	475000	315000	210000	140000
14	665000	445000	295000	185000
15	955000	635000	425000	240000
16	1350000	890000	600000	315000
17	1900000	1300000	850000	410000
18	2700000	1800000	1200000	530000
19	3850000	2550000	1700000	685000
20	5350000	3600000	2400000	880000']

[H: charAdvancement.table = am.tabTable(charAdvancement,1,1)]
[H: setLibProperty("charAdvancement",charAdvancement.table)]

[H: monsterXp = 'monster.cr	monster.xp
0.13	50
0.17	65
0.25	100
0.33	135
0.50	200
1	400
2	600
3	800
4	1200
5	1600
6	2400
7	3200
8	4800
9	6400
10	9600
11	12800
12	19200
13	25600
14	38400
15	51200
16	76800
17	102400
18	153600
19	204800
20	307200
21	409600
22	614400
23	819200
24	1228800
25	1638400
26	2457600
27	3276800
28	4915200
29	6553600
30	9830400']

[H: monsterXp.table = am.tabTable(monsterXp,1,1)]
[H: setLibProperty("monsterXp",monsterXp.table)]

!!
Once the macros are loaded you will need to run initXpTables once on the lib token.

Add this to onCampaignLoad

Code: Select all

...
[H: defineFunction("am.tabTable","tabTable@"+thisLib,1)]
[H: defineFunction("am.getMonsterXp","getMonsterXp@"+thisLib,1)]
[H: defineFunction("am.getCharXpGold","getCharXpGold@"+thisLib,1)]
...
After update, run onCampaignLoad once for current session. It'll be done automatically in the future.

Add this to the Pathfinder token type:

Code: Select all

*sheet.monsterXp (XP Award):[R, if(isNPC()): am.getMonsterXp(eval( "" + getStrProp(getProperty("Levels"),"ECL")));""]
I haven't done anything yet with the PC xp or gold progression but the function will retrieve the value from the table given the column name (fast,medium,slow,gold), level and return the entry. For example:

Code: Select all

[H: abort(input("input.progression|Fast,Medium,Slow|Chose XP Progression|LIST|value=string","input.level|1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20|Chose Level|LIST|value=string"))]
[H: thisLevel = am.getCharXpGold(input.progression,input.level)]
[H: nextLevel = am.getCharXpGold(input.progression,min(20,input.level+1))]
[H: startGold = am.getCharXpGold("gold",input.level)]
[R: strformat("Level: %{thisLevel}/%{nextLevel}; Gold: %{startGold}")]

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW (10-29-15)

Post by aliasmask »

MIRROR IMAGE MACRO

Okay, this has a bunch of requirements that are not in the normal framework, but it will activate, set state images (and bar) to show the number of mirror images +1 (includes the caster). Basically and thematically, it's easier to show the number of possible targets than the number of extra images. So, if you have 6 images, there are 7 of you. Roll 1d7 and on a 1 the caster may be hit.

Active Mod
You need to create an active mod for this spell "Mirror Image". When creating it you need to set the state to MirrorImage. That's it.

Images for state and bar
The zip file has the state image and counter images for the bar you need to create. I also added some screen shots to help you set those up. The bar has 11 increments starting with blank, counter_1 to 9 and ending in blank for full bar.

Macro
You'll need to add the mirrorImage macro to Lib:libDnD35Pathfinder. The calling macro could be on a token or in the campaign macros. You can use [macro:] or create a UDF (Wiki: defineFunction()).

One thing to note is that attacks that are at 5 less than the target's AC can break an image. So, when it says May Hit Caster, this is what I'm talking about. You can handle this 1 or 2 ways. Just set all the potential hits for your number of attacks, then decide which images were destroyed or run macro once for each potential hit separately. The problem with doing it all at once is the May Hit may actually break an image if that attack was less than target's AC screwing up the next roll. What you could do is just ignore the next roll when that happens and go down to the one that has the correct 1:X chance to hit. But if you do it one at a time, when that happens you can set the number of images down by one manually.
Mirror Image Example.jpg
Mirror Image Example.jpg (78.67 KiB) Viewed 7125 times
||| mirrorImage |||

Code: Select all

@@ @mirrorImage
<!-- Check Active Mod -->
[H: isActiveMod = json.contains(json.get(PrivateJSON,"ActiveTempModSets"),"Mirror Image")]

<!-- subModToggle settings -->
[H: modToToggle = "Mirror Image" ]
[H: modType = 0 ] <!-- modType 0 = mod, 1 = item -->
[H: tcasterlvl = getProperty("am.casterLevel")]
[H, if(json.isEmpty(tcasterlvl)): tcasterlvl = 3] <!-- min level for mirror image -->
[H: output = "[]"]

<!-- if not active, activate -->
[H, if(! isActiveMod), code: {
   [H: selectLevel = tcasterlvl -1]
   [H: abort(input(strformat("tcasterlvl|1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20|Set Caster Level|RADIO|orient=H value=string select=%{selectLevel}")))]
   [H: setProperty("am.casterLevel",tcasterlvl)]
   [H, if(isPC()): CLtext = strformat("<b>Caster Level: <font color=blue>%{tcasterlvl}</font></b>"); CLtext = ""]
   [MACRO( "subModToggle@Lib:libDnD35Pathfinder" ): json.set( "{}", "tokenID", currentToken(), "setID", modToToggle, "setType", modType, "casterlevel", tcasterlvl ) ]
   [H: output = json.append(output,trim(macro.return)+CLtext)]
   [H: setBar("Counter",min(9,1d4+floor(tcasterlvl/3)+1)/10)]
   [H: defaultNumAttacks = 0]
};{
   [H: defaultNumAttacks = 1]
}]

<!-- bar should be on because it holds the count of the images plus the caster -->
[H, if(isBarVisible("Counter")): imageCount = floor(getBar("Counter") * 10); imageCount = 0]

<!-- allow user to modify the starting number of images -->
[H: abort(input("tip|<html><b>* Current number of images including the caster.</b></html>|Changing value to 1 or less will end spell.|LABEL|SPAN=TRUE",
   strformat("newCount|<html><b color=gray>0</b></html>,<html><b color=gray>1</b></html>,2,3,4,5,6,7,8,9|Current Images (+1)|RADIO|select=%{imageCount} orient=H"),
   "tip|<html><br><b>* Number of attacks that threaten to break an image.</b></html>|Once all images are broken, then the spell ends.|LABEL|SPAN=TRUE",
   strformat("numAttacks|0,1,2,3,4,5,6,7,8,9|How many attacks|RADIO|orient=H select=%{defaultNumAttacks}")
))]

[H, if(newCount != imageCount): output = json.append(output,strformat("Images changed from %{imageCount} to %{newCount} by user."))]

<!-- count the swings it takes to destroy all the images -->
[H: attackCounter = numAttacks]
[H: imagesDestroyed = 0]
[H: attackNum = 0]
[H, while(imagesDestroyed < newCount -1 && attackCounter), code: {
   [H: attackNum = roll.count +1]
   [H: imagesRemaining = newCount - imagesDestroyed]
   [H: chance = strformat("(1:%{imagesRemaining})")]
   [H: miss = roll(1,imagesRemaining) -1]
   [H, if(miss): imagesDestroyed = imagesDestroyed +1]
   [H: attackCounter = attackCounter -1]
   [H,if(! miss): output = json.append(output,strformat("Attack %{attackNum} %{chance}: <font color=red>May Hit Caster</font>")); 
      output = json.append(output,strformat("Attack %{attackNum} %{chance}: <font color=blue>Destroys Image</font>"))]
}]

[H: imagesRemaining = newCount - imagesDestroyed]
[H, if(imagesRemaining <= 1), code: {
   [H: output = json.append(output,strformat("All images destroyed"))]
   [MACRO( "subModToggle@Lib:libDnD35Pathfinder" ): json.set( "{}", "tokenID", currentToken(), "setID", modToToggle, "setType", modType, "casterlevel", tcasterlvl ) ]
   [H: output = json.append(output,macro.return)]
   [H: setBarVisible("Counter",0)]
};{
   <!-- update bar "Counter" to current number of images -->
   [H: setBar("Counter",imagesRemaining/10)]
}]

[H: remainingAttacks = numAttacks - attackNum]
[H, if(remainingAttacks): output = json.append(output,strformat("The remaining %{remainingAttacks} attacks strike as normal"))]

<!-- mirror image report -->
[H: imageLink = am.tokenImageLink(currentToken())]
[H: output = json.toList(output,"<br>")]
[H: output.block = strformat('
   <table style="border-spacing:1pt;border-width:0px;border-style:solid;padding:0px;">
      <tr><td colspan=2 style="background-color:#FF99FF;border-width:1pt;border-style:solid;padding:0px 3px 0px 6px;"><b>%{token.name}</b> - Spell: Mirror Image</td></tr>
      <tr valign="top">
         <td width="34" style="padding:0px;border-width:1pt;border-style:solid;text-align:center;">%{imageLink}</td>
         <td width="250" style="background-color:#FFCCFF;border-width:1pt;border-style:solid;padding:0px 3px 0px 6px;font-weight:bold;">%{output}</td>
      </tr>
   </table>
')]
[H: broadcast(output.block)]

!!
 
edit: I did a small edit to the code and post. I decided to do the caster level setting in the macro to avoid an exploit if done through globalMod. ie, you can put level 200 and it would always give you max images. So, I get level in the macro to avoid that.

edit: After some use, I find it more convenient to just do the default single attack. In the case of "May hit Caster" if it actually misses caster because it was with in 5 AC then I decrement images and run again or if it was last attack set attacks to 0 so that the number of images is correct.
Attachments
Mirror Image.zip
(221.58 KiB) Downloaded 148 times

User avatar
lmarkus001
Great Wyrm
Posts: 1867
Joined: Sat Mar 29, 2008 12:30 am
Location: Layfayette Hill, PA

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW (11-13-15)

Post by lmarkus001 »

Nice addition! I continue to incorporate your additions into my core framework, so next update will have this.

Personally I don't worry about exploits. Anyone foolish enough to game the system while I GM shall suffer the consequences as I have ultimate control of the narrative (wow, that orc had over 300 hitpoints and never missed a single swing!).

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW (11-13-15)

Post by aliasmask »

I also updated all the normal states from 3x3 to 4x4 because of the image placement. I also updated the elevation states to be smaller (top half of upper right corner). It looks better and is less dominating of the token. The numbers are still in the same position but I changed them to mouseover. I also included a state behind the numbers so they stand out over the elevation state. I also added AirWalk to the list.

That post will soon be here.

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW (11-13-15)

Post by aliasmask »

I recently had a player create a Summoner (Synthesist). The framework doesn't handle the tracking of hitpoints very well so I wrote some code, created some health bars and states to help with that. It's very one dimensional right now only handling the case of a single class Synthesist, but I was wondering if anyone out there has a need for this. If so, I can share what I have and perhaps develop it more.

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW (11-13-15)

Post by aliasmask »

I use a simple macro for torches in my game. It uses the Torch state to show the light is on.

Code: Select all

[H: isOn = hasLightSource("D20","Torch - 20")]
[H: setLight("D20","Torch - 20",1 - isOn)]
[R: if(isOn,"Torch Removed","Torch Used")]
[H: setState("Torch",1-isOn)]

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW (11-13-15)

Post by aliasmask »

Updated pfStatBlockImporter to v2.0.6: viewtopic.php?f=53&t=24994&p=260873#p260873

Read macro notes.

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW (11-13-15)

Post by aliasmask »

Use this for generic use of changeElevation like when putting it in to a campaign window macro. Make sure Apply to Selected is checked.

Code: Select all

<!-- changeElevation(): output -->
[H: elevation.value = getProperty("Elevation")]
[H: elevation.states = getProperty("elevation.states")]
[H, if(! isNumber(elevation.value)): elevation.value = 0]
[H, if(! isNumber(elevation.states)): elevation.states = 0]

[H: abort(input("tip|<html><b><i>Either set current elevation or put adjustment value in.</i></b></html>||LABEL|SPAN=TRUE",
   strformat('elevation.input|%{elevation.value}|Current Elevation|TEXT'),
   "elevation.adjustment|0|Adjust Elevation|TEXT",
   "elevation.metric|1|Apply Upward Move Cost|CHECK",
   strformat('elevation.states|%{elevation.states}|Set Elevation States|CHECK')
))]

<!-- error checking -->
[H, if(! isNumber(elevation.input)): elevation.input = 0]
[H, if(! isNumber(elevation.adjustment)): elevation.adjustment = 0]

<!-- get new elevation -->
[H, if(elevation.input != elevation.value), code: {
   [H: elevation.new = elevation.input]
};{
   [H, if(elevation.adjustment > 0), code: {
      [H, if(elevation.metric): elevation.new = elevation.value + floor(elevation.adjustment/10) * 5; elevation.new = elevation.value + elevation.adjustment]
   };{
      [H: elevation.new = max(0,elevation.value - elevation.adjustment)]
   }]
}]

<!-- enforce 5ft increments -->
[H: elevation.new = floor(elevation.new/5)*5]

<!-- clear elevation states -->
[H, for(i,5,155,5): setState(i,0)]

<!-- set states -->
[H, if(elevation.new > 0 && elevation.states): setState(min(150,elevation.new),1)]

<!-- set elevation value -->
[H: setProperty("Elevation",elevation.new)]
[H: setProperty("elevation.states",elevation.states)]

<!-- output elevation changes -->
[H: tokenImage = getTokenImage()]

[H: output = strformat('
   <table style="border-spacing:0px;border-style:solid;border-color:black;border-width:1pt;padding:0px">
      <tr>
         <td width="34" style="padding:0px">%{tokenImage}</td>
         <td width="250" style="background-color:aqua;padding:0px 5px 2px 5px;">%{token.name} moves to Elevation %{elevation.new}.</td>
      </tr>
   </table>
')]

[H: broadcast(output)]
[H: abort(0)]
edit: removed reference to state.fly

Post Reply

Return to “D&D 3.5/Pathfinder 1e Campaign Macros”