Maptool

Functionality

 * Dynamically creates an interface for the GM
 * Displays the stats and combat status of a token
 * Displays the token with the current initiative by default
 * GM can switch to other tokens at any time
 * Creates a display for player clients that shows the stats and combat status of the current initiative token, etc.

Needs

 * View Monster stats and Power stats on the fly
 * Add and Edit Powers using the Compendium for an initial source
 * Automate off-table, monotonous tasks with thorough, sensible output
 * Monster power attacks
 * &bull; Multiple attacks. E.g.
 * Better power effect mechanic
 * related: no damage/effect only consideration in the Edit Power set (currently requires entering 0d6 for damage dice at creation, power displays "takes 0 damage")
 * Some sort of recharge mechanic, incl a label for the DM to indicate whether a power is available
 * &bull; Apply damage to tokens
 * (partial) Add state changes to powers and apply state changes to tokens
 * (partial) Attack using the token with initiative
 * If PC has initiative, show dialog for applying damage to an NPC
 * This should start with an Apply Damage macro, separately, then get integrated. It may be best to shift to Objects here. The attack macro (perhaps) should be an initiator, calling a separate macro depending on PC/NPC, then calling other macros for tracking ongoing damage, temporary effects, etc.
 * Attack: StartOfTurn macro -> Attack macro -> EndOfTurn macro
 * Token State
 * Output the reasonably limited stats and states of a selected PC or NPC token
 * States, Effects, ongoing damage, ???

Links

 * List of Macro Functions
 * Details of input
 * Veggiesama's Framework (D&D 4e)

Files

 * WIP Campaign
 * Library Token

Set Stats
 [H: input( "Name |" + getName,  "HP | " + getProperty("HP"),  "TempHP | " + getProperty("TempHP"),  "SurgesUsed | " + getProperty("SurgesUsed"),  "SurgesPerDay | " + getProperty("SurgesPerDay"),  "Damage | " + getProperty("Damage"),  "Initiative | " + getProperty("Initiative"),  "Perception | " + getProperty("Perception"),  "Insight | " + getProperty("Insight"),  "AC | " + getProperty("AC"),  "Fortitude | " + getProperty("Fortitude"),  "Reflex | " + getProperty("Reflex"),  "Will | " + getProperty("Will"),  "Speed | " + getProperty("Speed"),  "Level | " + getProperty("Level") )] [h: setName(Name)] [h: setProperty("HP", HP)] [h: setProperty("TempHP", TempHP)] [h: setProperty("SurgesUsed", SurgesUsed)] [h: setProperty("SurgesPerDay", SurgesPerDay)] [h: setProperty("Damage", Damage)] [h: setProperty("Initiative", Initiative)] [h: setProperty("Perception", Perception)] [h: setProperty("Insight", Insight)] [h: setProperty("AC", AC)] [h: setProperty("Fortitude", Fortitude)] [h: setProperty("Reflex", Reflex)] [h: setProperty("Will", Will)] [h: setProperty("Speed", Speed)] [h: setProperty("Level", Level)]

Edit Power
Power Property default sting PwrName=--none-- ; PwrAttackBonus=0 ; PwrDef=AC ; PwrDamDice=1d6 ; PwrDamBonus= ; PwrDamType= ; PwrEffect= ; 
 * Source: input and other new b42 macro functions
 * Set default string if powers are blank

[H: PwrList = ""] [c(6),CODE: {  [H: PwrList = listAppend(PwrList, getStrProp(eval("Power"+roll.count), "PwrName"))] }]

[H: status = input("PwrNum | " + PwrList + " | Select power to edit | LIST")] [H: abort(status)]

[H: PwrPropName = "Power" + PwrNum] [H: PwrProps = eval(PwrPropName)]

[H: NumProps = countStrProp(eval(PwrPropName))] [H: assert(NumProps!=0, "The " + PwrPropName + " property is not set up correctly.")]

[H: status = input("blah | " + PwrNum + " | Power number | LABEL",     PwrPropName + " | " + PwrProps + " | Power properties | PROPS")] [H: abort(status)]

New properties for power #{PwrNum}: {formatStrProp(eval(PwrPropName),    " ",     " %key  %value  ",    "" )}

Show GM Notes
These put the GM notes for the selected Token into a popup or frame. Include the CSS for pasting HTML from the Compendium.

Create Dockable Frame
[R, if(isGM), frame("DM Notes","temporary=1;"):{[R: getGMNotes]};{[R:"You do not have permission to view GM Notes."]}]

Create Dialog
[R, if(isGM), dialog("DM Notes","temporary=1;"):{[R: getGMNotes]};{[R:"You do not have permission to view GM Notes."]}]

Include CSS
 body { font-family:Verdana, Arial, Helvetica, sans-serif;background: #FFFFFF; color: #000000; font-size:10px; } p { padding-left: 15px; color: #000000; font-size:95%; } table { width: 100%; } table td { vertical-align: top; padding: 0 10px 0; background: #d6d6c2; border-bottom: 1px solid #FFFFFF; } p.flavor, span.flavor, ul.flavor { display: block; padding: 2px 15px; margin: 0; background-color: #d6d6c2; font-size:100%; } p.powerstat { padding: 0px 0px 0px 15px; margin: 0; background: #FFFFFF; font-size:100%; } span.ritualstats { float:right; padding: 0 30px 0 0; } p.flavorIndent { display: block; padding: 2px 15px 2px 30px; margin: 0; background: #d6d6c2; } span.clearIndent { display: block; padding: 2px 15px 2px 30px; margin: 0; background: #FFFFFF; } p.flavor alt, span.flavor alt, td.flavor alt { background-color: #C3C6AD; } p.alt, span.alt, td.alt { background-color: #C3C6AD; } th { background: #1d3d5e; color: #FFFFFF; text-align: left; padding: 0 0 0 5px; } i, em { font-style: italic; } ul { list-style: disc; margin: 1em 0 1em 30px; } table, ul.flavor { margin-bottom: 1em; } ul li { color: #3e141e; } ul.flavor li { margin-left: 15px; } a { color: #3e141e; } blockquote { padding: 0 0 0 22px; background: #d6d6c2; } span.block { display: block; padding: 0 0 0 22px; background: #d6d6c2; } h1 { font-size:100%; line-height: 2; padding-left: 15px; margin: 0; color: #FFFFFF; background: #000; } h1.player { background: #1d3d5e; font-size:110%; } h1.dm { background: #5c1f34; } h1.trap { background: #5c1f34; height:38px; } h1.atwillpower { background: #619869; } h1.encounterpower { background: #961334; } h1.dailypower { background: #4d4d4f; } span.milevel { font-size:100%; } h1.poison { background: #000000; } h1.poison .level { padding-right: 15px; margin-top: 0; text-align: right; float: right; position:relative; top:-24px; } h1.utilitypower { background: #1c3d5f; } h1.familiar { background: #4e5c2e; } h1 .level { padding-right: 15px; margin-top: 0; text-align: right; float: right; } .rightalign { text-align: right; } /* Traps */ h1.trap .level { margin-top: 0; text-align: right; position:relative; top:-60px; } h1.trap .type, h1.trap .xp { display: block; position: relative; z-index: 99; top: -0.75em; height: 1em; font-weight: normal; font-size:100%; } .traplead { display: block; padding: 1px 15px; margin: 0; background: #ffffff; } .trapblocktitle { display: block; padding: 1px 15px; margin: 0; background: #d6d6c2; font-weight: bold; } .trapblockbody { display: block; padding: 1px 15px 1px 30px; margin: 0; background: #ffffff; } .bodytable { border: 0; margin: 0; width: 560px; background: #D1D1BC; } .bodytable td { border-bottom: none; padding-left: 15px; padding-right: 15px; } h2 { font-size:110%; padding-left: 15px; margin: 0; color: #FFFFFF; background: #4e5c2e; height:20px; font-variant: small-caps; padding-top: 5px; } /* Monster */ h1.monster { background: #4e5c2e; height:38px; } h1.monster .level { margin-top: 0; text-align: right; position:relative; top:-60px; } h1.monster .type, h1.monster .xp { display: block; position: relative; z-index: 99; top: -0.75em; height: 1em; font-weight: normal; font-size:100%; } h2.monster { font-size:100%; padding-left: 15px; margin: 0; color: #FFFFFF; background: #4e5c2e; height:20px; font-variant: small-caps; padding-top: 3px; } h3.monster { font-size:95%; font-weight: bold; color: #000000; background: #c3c6ad; padding: 0px 5px 0px 10px; margin: 0; display: block; } p.monster { display: block; padding: 2px 15px; margin: 0; background: #CADBB7; font-size:100%; display: block; } p.monstat { background: #CADBB7; } /* Magic Items */ h1.magicitem { background: #DA9722; font-size:110%; font-weight:bold; height: 17pt; margin-top: 2; line-height: 1.5em; } h1.magicitem .milevel { padding-right: 15px; margin-top: 0px; text-align: right; float: right; position:relative; } h1.mihead { background: #DA9722; font-size:110%; font-weight:bold; height: 17pt; margin-top: 2; line-height: 1.5em; } h1.mihead .milevel { padding-right: 15px; margin-top: 0px; text-align: right; float: right; position:relative; } p.miflavor { display: block; padding: 2px 15px; margin: 0; background: #EFD09F; font-size:100%; font-style: italic; color: #000000; } table.magicitem { width: 560px; margin-bottom: 0; } table.magicitem td { border: none; font-size:95%; background: #F8E9D5; } td.mic1 { padding-left: 20px; padding-right: 0px; width: 50px; } td.mic2 { width: 40px; padding: 0px; } td.mic3 { width: 100px; text-align:right; padding: 0px; } td.mic4 { width: 100px; padding: 0px; } h2.magicitem { background: #EFD09F; color: #000000; font-variant: normal; font-size:95%; line-height: 18px; padding-top: 0px; vertical-align: bottom; height: 18px; } h2.mihead { display: block; background: #EFD09F; color: #000000; font-variant: normal; font-size:95%; line-height: 18px; padding-top: 0px; vertical-align: bottom; height: 18px; } p.publishedIn { font-size:95%; margin-top: 10px; } h2.artifactHeading1 { font-size:120%; padding-left: 5px; padding-top: 5px; margin: 0px; background: #FFFFFF; color: #254950; font-variant: normal; } h2.artifactHeading2 { font-size:120%; padding-left: 5px; padding-top: 5px; margin: 0px; background: #FFFFFF; color: #556C75; font-variant: normal; } h2.ah1 { font-size:120%; padding-left: 5px; padding-top: 5px; padding-bottom: 2px; margin: 0px; background: #FFFFFF; color: #254950; font-variant: normal; } h2.ah2 { font-size:120%; padding-left: 5px; padding-top: 5px; padding-bottom: 2px; margin: 0px; background: #FFFFFF; color: #556C75; font-variant: normal; } h2.ah3 { font-size:110%; padding-left: 5px; padding-top: 5px; margin: 0px; background: #FFFFFF; color: #556C75; font-variant: normal; } p.mistat { padding: 0px 0px 0px 15px; display: block; margin: 0; background: #F8E9D5; font-size:100%; color: #000000; } p.mistatAI { padding: 0px 0px 0px 15px; margin: 0; background: #F8E9D5; font-size:100%; color: #000000; height: 36px; } span.miright { padding: 0px 5px 0px 0px; margin-top: 0px; text-align: right; float: right; position:relative; } p.indent { padding: 0px 0px 0px 30px; text-indent: -15px; } p.indent1 { padding: 0px 0px 0px 45px; text-indent: -15px; } p.indent2 { padding: 0px 0px 0px 60px; text-indent: -15px; } p.mitext { padding: 0px 0px 0px 5px; margin: 0; background: #FFFFFF; font-size:100%; color: #000000; } ul.mitext { margin-bottom: 1em; display: block; padding: 2px 5px; margin-top: 0; margin-right: 0; background: #FFFFFF; font-size:100%; color: #000000; } ul.mitext li { color: #000000; } ul.mistat { margin-bottom: 0; padding: 2px 5px 2px 30px; margin: 0; background: #F8E9D5; font-size:100%; color: #000000; } ul.mistat li { color: #000000; } h1.miset { background: #22444B; font-size:110%; font-weight:bold; height: 17pt; margin-top: 2; line-height: 1.5em; } h1.miset .milevel { padding-right: 15px; margin-top: 0px; text-align: right; float: right; position:relative; } .ignore { } th.miset { background: #22444B; color: #FFFFFF; text-align: left; padding: 0 0 0 5px; } h1.thHead { background: #45133C; height:38px; } h1.thHead .thLevel { margin-top: 0; text-align: right; position:relative; top:-60px; padding-right: 15px; float: right; } h1.thHead .thSubHead, h1.thHead .thXP { display: block; position: relative; z-index: 99; top: -0.75em; height: 1em; font-weight: normal; font-size:100%; } h2.thHead { display: block; background: #65345D; color: #FFFFFF; font-variant: small-caps; font-size:95%; line-height: 18px; padding-top: 0px; vertical-align: bottom; height: 18px; } p.thBody { padding: 0px 0px 0px 30px; text-indent: -15px; display: block; margin: 0; background: #E8F2D6; font-size:100%; color: #000000; } p.tbod { padding: 0px 0px 0px 45px; text-indent: -15px; display: block; margin: 0; background: #E8F2D6; font-size:100%; color: #000000; } p.thStat { padding: 0px 0px 0px 15px; display: block; margin: 0; background: #E8F2D6; font-size:100%; color: #000000; } span.thInit { background: #E8F2D6; font-size:100%; color: #000000; position: absolute; left: 400px; top: 53px; } p.th2 { padding: 0px 0px 0px 15px; display: block; margin: 0; background: #CADBB7; font-size:100%; color: #000000; }
 * 1) RelatedArticles h5 { width: 100px; float: left; padding-top: 10px; padding-left: 20px; color: #3e141e; font-weight: bold; }
 * 2) RelatedArticles ul.RelatedArticles { padding: 10px 0 0 0; float: right; width: 430px; margin: 0; list-style: none; }

Monster Attack
Note: non-breaking spaces do not survive pasting into the wiki, so they cannot be used for formatting.
 * 2.3
 * Fixed Math
 * Added math to confirmation output
 * 2.2.1
 * Sets Bloodied state if HP/2 > currentHP
 * Should display the damage effects properly
 * Hit-Prompt much improved
 * No check box, just OK/Cancel
 * Displays Target's Defense
 * 2.2
 * Normal and Critical damage calculation/application code is mirrored
 * Damage is applied to the Damage property. HP is untouched
 * PC checklist is part of the Attack dialog
 * Style IF for text size. GM get's small text, others get huge text.
 * Some variable cleanup
 * Some commenting
 * Changes to output formatting
 * ver 2.1
 * Multiple Targets
 * Style Variables at the top for Chat output formatting
 * Miss text flair!
 * ver 2.1.1
 * Minor formatting changes
 * Changed power count to 5 (practical limit and matches live properties)
 * Pulled damage outside FOREACH, critical damage redux
 * Power effect added (needs some work)
 * ver 2.2
 * 2.1.2:0.1 (removed)
 * Applies damage to the PC token
 * Accounts for temp HP
 * Requires PC tokens to have HP and TempHP properties
 * PwrEffect added to damage output

Adding  to the top applies the macro to the token with initiative. 

[h: CRITSTYLE="color:red;font-weight:bold;"] [h: PCNAMESTYLE="font-size:120%;font-weight:bold;"] [h: VALUESTYLE="font-size:130%;font-weight:bold;color:red;"] [h: MONSTYLE="color:blue;"] [h: MISSTEXT="stumbles, slips, trips, aims high, aims low"] [h,if(isGM): BIGTEXT="font-size:14px;"; BIGTEXT="font-size:28px;"]

[h: PwrList = ""] [h,COUNT(5): PwrList = listAppend(PwrList, getStrProp(eval("Power"+roll.count), "PwrName"))]

[h: PwrNum = getStrProp(Private, "DefaultPwr", 0)]

[h: PCnames = getPCNames]

[h:OutputString=""] [h,FOREACH(PC,PCnames):outputString=outputString+" 'target"+roll.count+"|0|"+PC+"|CHECK" + " ' " + ","] [h: OutputString = listDelete(OutputString,(listCount(OutputString)-1))]

[h:status=eval("input('PwrNum | ' + PwrList + ' | Select power to use | LIST | SELECT=' + PwrNum,		'CA | 0 | Combat Advantage | CHECK',		'MiscAtk | 0 | Misc. attack bonus',		'MiscDmg | 0 | Misc. damage bonus',		'Blah|Target?|Players|LABEL',		'"+OutputString+" ' "+")") ]	[h:abort(status)]

[h:NumEntries=listCount(PCnames)] [h:TargetList=""] [h,FOR(i,0,NumEntries):TargetList = if(eval("target"+i), listAppend(TargetList,listGet(PCnames,i)),TargetList)]

[h: varsFromStrProp(eval("Power" + PwrNum), "unsuffixed")]

[h: DamageRoll = eval(PwrDamDice)] [h: TotalDamage = eval("DamageRoll + PwrDamBonus + MiscDmg")] [h: CritDamage = eval(replace(PwrDamDice,"d","*")) + MiscDmg] [h: AttackBonus = PwrAttackBonus + if(CA, 2, 0) + MiscAtk]

[FOREACH(PCTarget, TargetList, " "), CODE:{ [h: AttackRoll = eval("1d20")] [h,if(AttackRoll==20): CRIT="1"; CRIT="0"] [h,if(AttackRoll==1): CRIT="2"] [h: TotalAttack=eval("AttackRoll+AttackBonus")] [h: defense=getProperty(PwrDef, PCTarget)]PwrDef [h,if(CRIT=="0"), CODE:{ [h:applyDamage=input("junkVar| " + TotalAttack + " vs. " + PwrDef + " (" + defense + ") | Apply " + TotalDamage + " damage to " + PCTarget + "? Type: " + PwrDamType + " Effect: " + PwrEffect + "

A:"+AttackRoll+"+"+PwrAttackBonus+"+"+if(CA, "2", "0")+"+"+MiscAtk+" D:"+DamageRoll+"+"+PwrDamBonus+"+"+MiscDmg+" |LABEL")]			[h,if(applyDamage==1): CRIT="0"; CRIT="2"]		}]



[switch(CRIT), CODE: case "0": { [r: token.name] hits [r: PCTarget] with his [r: PwrName]. [r: PCTarget] takes [r: TotalDamage] [r: PwrDamType] damage. [r: PwrEffect] [h: HP=getProperty("HP", PCTarget)] [h: currentDamage=getProperty("Damage", PCTarget)] [h: tempHP=getProperty("TempHP", PCTarget)] [h: tempHPDamage = if(TotalDamage > 0, min(TotalDamage, tempHP), 0)] [h: HPDamage = eval("TotalDamage-tempHPDamage")] [h: newDamage = eval("currentDamage+HPDamage")] [h: currentHP = eval("HP-newDamage")] [h: setProperty("Damage", newDamage, PCTarget)] [h: setProperty("TempHP",eval("tempHP-tempHPDamage"), PCTarget)] [h: setState("Bloodied", if(eval("HP/2")>currentHP, 1, 0), PCTarget)] [h: setState("Dying", if(currentHP < 0, 1, 0), PCTarget)] };			case "1": { [h: TotalDamage=CritDamage] [r: token.name] hits [r: PCTarget] with his [r: PwrName], A Critical Hit! [r: PCTarget] takes [r: TotalDamage] [r: PwrDamType] damage. [r: PwrEffect] [h: HP=getProperty("HP", PCTarget)] [h: currentDamage=getProperty("Damage", PCTarget)] [h: tempHP=getProperty("TempHP", PCTarget)] [h: tempHPDamage = if(TotalDamage > 0, min(TotalDamage, tempHP), 0)] [h: HPDamage = eval("TotalDamage-tempHPDamage")] [h: newDamage = eval("currentDamage+HPDamage")] [h: currentHP = eval("HP-newDamage")] [h: setProperty("Damage", newDamage, PCTarget)] [h: setProperty("TempHP",eval("tempHP-tempHPDamage"), PCTarget)] [h: setState("Bloodied", if(eval("HP/2")>currentHP, 1, 0), PCTarget)] [h: setState("Dead", if(currentHP < 0, 1, 0), PCTarget)] };			case "2": { [r: token.name] [r: listGet(MISSTEXT, eval("roll(1,listCount(MISSTEXT))-1"))] trying to attack [r: PCTarget] and misses.

};		] 	}]

Reset Damage and Damage states for PC Tokens
[h: PCnames = getPCNames] [h,FOREACH(PC, PCnames, " "), CODE:{ [h: setProperty("Damage", 0, PC)] [h: setProperty("TempHP", 0, PC)] [h: setState("Dead", 0, PC)] [h: setState("Bloodied", 0, PC)] [r: PC getProperty("Damage", 0, PC)] }]

List States in Chat
[h: states = getTokenStates] [h: COUNT=0]

v.1
 [h: switchToken(getInitiativeToken)]

[h: CRITSTYLE="color:red;font-weight:bold;"] [h: PCNAMESTYLE="font-size:120%;font-weight:bold;"] [h: VALUESTYLE="font-size:130%;font-weight:bold;color:red;"] [h: MONSTYLE="color:blue;"] [h: MISSTEXT="stumbles, slips, trips, aims high, aims low"] [h,if(isGM): BIGTEXT="font-size:14px;"; BIGTEXT="font-size:28px;"]

[h: token=getName(getInitiativeToken)] [h: pcNames = getPCNames] [h: npcNames = getNPCNames] [h: states = getTokenStates]

[h: tempList = states] [h: num = listCount(states)] [h,COUNT(num): tempList = listReplace(tempList, roll.count, 		listGet(tempList, roll.count) + " " + getStateImage(listGet(tempList, roll.count)))] [h: tempList = listInsert(tempList, 0, " ")] [h: stateList = tempList]

[h:outputString=""] [h: num = listCount(npcNames)] [h,COUNT(num): outputString = outputString + " 'target" + roll.count + "|0|"+listGet(npcNames,roll.count)+": " +getProperty("AC", listGet(npcNames,roll.count))+"," +getProperty("Fortitude", listGet(npcNames,roll.count))+"," +getProperty("Reflex", listGet(npcNames,roll.count))+"," +getProperty("Will", listGet(npcNames,roll.count)) +"|CHECK',"] [h: outputString = listDelete(outputString,(listCount(outputString)-1))]

[h:status=eval("input('TotalDamage | 0 | Damage',		'stateNum | ' + stateList + ' | Apply State | LIST | icon=true iconsize=20',		'defense|AC,Fort,Ref,Will|Defence|LIST|VALUE=STRING',		'Blah|Hit?|NPCs (AC,Fort,Ref,Will)|LABEL',		'"+outputString+" ' "+")") ]	[h:abort(status)]

[h:NumEntries=listCount(npcNames)] [h:TargetList=""] [h,FOR(i,0,NumEntries):TargetList = if(eval("target"+i), listAppend(TargetList,listGet(npcNames,i)),TargetList)]

[FOREACH(npcTarget, TargetList, " "), CODE:{ [h:status=input(			"junkVar| vs. " + defense + " | Apply " + TotalDamage + " damage to " + npcTarget + "? |LABEL",			"TotalDamage|"+TotalDamage+"|Apply Damage?"		)] [h:abort(status)]

<div style="width:100%;[r: BIGTEXT]"> <span style="[r:MONSTYLE]">[r: token] hits [r: npcTarget] <span style="[r:PCNAMESTYLE]">[r: npcTarget] takes <span style="[r:VALUESTYLE]">[r: TotalDamage] damage.

[h: HP=getProperty("HP", npcTarget)] [h: currentDamage=getProperty("Damage", npcTarget)] [h: tempHP=getProperty("TempHP", npcTarget)] [h: tempHPDamage = if(TotalDamage > 0, min(TotalDamage, tempHP), 0)] [h: HPDamage = eval("TotalDamage-tempHPDamage")] [h: newDamage = eval("currentDamage+HPDamage")] [h: currentHP = eval("HP-newDamage")] [h: setProperty("Damage", newDamage, npcTarget)] [h: setProperty("TempHP",eval("tempHP-tempHPDamage"), npcTarget)] [h: setState("Bloodied", if(eval("HP/2")>currentHP, 1, 0), npcTarget)] [h: setState("Dead", if(currentHP < 0, 1, 0), npcTarget)] [h: StateName = if(stateNum != 0, listGet(states, stateNum-1), "")] [h,if(StateName != ""): setState(StateName, 1, npcTarget)] }]

List PC Tokens
Sets selected PC Name to PCTarget [h: PCnames = getPCNames] [H: status = input(    "PCTarget | " + PCnames + " | Target | LIST | VALUE=STRING" )] [H: abort(status)]