I just added a couple of things that may be useful. All these changes are made to the Lib:libDnD35Pathfinder token.

First, auto-check rapid shot when doing flurry of blows with ranged weapon in pathfinder. This is so the Zen Archer or Monk throwing shuriken will have the right number of attacks with the correct modifier:

Code: Select all

`...`

[H, IF( vitalStrike ), CODE: {

[H: fullRound = 0 ]

[H: flurryRapid = 1 ]

};{}]

<!-- place this after vital strike check in "Attack" macro -->

[H, if(system == "Pathfinder"), code: {

[H, if(Flurry_of_Blows), code: {

[H: flurryRapid = -2]

[H: fullRound = 1]

};{}]

};{}]

...

Second, is a check for Point Blank shot. This requires you to select a target. I'm always forgetting to check this box, now I won't have to:

Code: Select all

`...`

[H: '<!-- Check point blank -->']

[H, if(Ranged && tPointBlankShot && ! json.isEmpty(target1ID) && ! isPointBlank), code: {

[H: distance = getDistance(target1ID,1)]

[H, if(distance <= 30): isPointBlank = 1]

};{}]

<!-- Place above code just before this line in "LibAttack" macro -->

[H: rangeMod = if( rangeIncrement == 0, if( tPointBlankShot && isPointBlank, 1, 0 ), -2 * ( rangeIncrement ) ) ]

...

Third and is far more complicated to add is a check for flanking. This also requires you to select a target when attacking and will only check immediately around target. So, if your ally has reach, the flanking checkbox will still need to be checked manually. Note that this doesn't check visibility, so an ally on the other side of VBL will still flank. This usually isn't a problem, but I'll program for this fringe case and using reach in the future:

Code: Select all

`...`

[H: rangeMod = if( rangeIncrement == 0, if( tPointBlankShot && isPointBlank, 1, 0 ), -2 * ( rangeIncrement ) ) ]

<!-- place this code just below the above code in "LibAttack" macro -->

[H: '<!-- Check flanking -->']

[H, if(! Ranged && ! json.isEmpty(target1ID) && ! flanking), code: {

[H: attacker = currentToken()]

[H: target = target1ID]

[H, if(isPC()): allies = getTokens("json",json.set("{}","pc",1,"range",json.set("{}","token",target,"distancePerCell",1,"upto",5)));

allies = getTokens("json",json.set("{}","npc",1,"range",json.set("{}","token",target,"distancePerCell",1,"upto",5)))]

[H: flankers = am.getFlankers(attacker,target,allies)]

[H, if(! json.isEmpty(flankers)): flanking = 1]

};{}]

...

For the above to work you need to add 4 new macros and edit the onCampaignLoad macro.

[spoiler=||| New Macros |||]

Code: Select all

`@@ @getFlankers`

<!-- getFlankers(attacker,target,allies): flankers -->

[H: attackerId = arg(0)]

[H: targetId = arg(1)]

[H: allyIds = arg(2)]

[H: flankers = ""]

[H, if(json.type(allyIds) == "UNKNOWN" && ! json.isEmpty(allyIds)): allyIds = json.fromList(allyIds)]

[H, if(json.contains(allyIds,attackerId)): allyIds = json.difference(allyIds,json.append("",attackerId))]

[H, if(json.contains(allyIds,targetId)): allyIds = json.difference(allyIds,json.append("",targetId))]

[H, foreach(allyId,allyIds), code: {

[H: isFlanking = am.isFlanking(attackerId,targetId,allyId)]

[H, if(isFlanking): flankers = listAppend(flankers,allyId)]

}]

[H: macro.return = flankers]

!!

@@ @isFlanking

@[email protected] fontColor=black ; autoExecute=true ; fontSize=11pt ; sortBy= ; color=default ; playerEditable=false ; applyToSelected=false ; group= ; tooltip= ; minWidth=94 ;

[H: a.id = arg(0)]

[H: t.id = arg(1)]

[H: f.id = arg(2)]

<!-- Since we are dealing with grid squares, the distance to the center of grid is 0.5 -->

[H: offset = 0.5]

[H: infinity = 1000000000]

[H: a.strProps = am.defineTokenCoords.sub(a.id,0,0,"a")]

[H: t.strProps = am.defineTokenCoords.sub(t.id,0,0,"t")]

[H: f.strProps = am.defineTokenCoords.sub(f.id,0,0,"f")]

<!-- Check vertical and horizontal -->

[H: isFlanking = if((max(a.x1,t.x1,f.x1) <= min(a.x2,t.x2,f.x2)-1) && ((a.y1 > t.y1 && t.y1 > f.y1) || (a.y1 < t.y1 && t.y1 < f.y1)),1,0)]

[H, if(! isFlanking): isFlanking = if((max(a.y1,t.y1,f.y1) <= min(a.y2,t.y2,f.y2)-1) && ((a.x1 > t.x1 && t.x1 > f.x1) || (a.x1 < t.x1 && t.x1 < f.x1)),1,0)]

<!-- if attacker and ally is size 1, just check path through target -->

[H, if(! isFlanking && a.size == 1 && f.size == 1), code: {

[H: slope = (f.cy - a.cy)/(f.cx - a.cx)]

[H: b = a.cy - (a.cx * slope)]

[H, if(slope > 1 || slope < -1), code: {

[H, if((t.y1 - b)/slope >= t.x1 && (t.y1 - b)/slope <= t.x2 && (t.y2 - b)/slope >= t.x && (t.y2 - b)/slope <= t.x2): isFlanking = 1]

};{

[H, if(slope * t.x1 + b >= t.y1 && slope * t.x1 + b <= t.y2 && slope * t.x2 + b >= t.y1 && slope * t.x2 + b <= t.y2): isFlanking = 1]

}]

};{}]

<!-- Do slope test: ULtoLR, LLtoUR, URtoLL and LRtoUL. I just redefine coords to ULtoLR to avoid duplicating formulas -->

[H, if(! isFlanking), code: {

[H, if(a.x1_ <= t.cx && a.y1_ <= t.cy && f.x2_ >= t.cx && f.y2_ >= t.cy): isFlanking = am.slopeRangeCheck(a.strProps,t.strProps,f.strProps)]

[H, if(! isFlanking && a.x1_ <= t.cx && a.y2_ >= t.cy && f.x2_ >= t.cx && f.y1_ <= t.cy): isFlanking = am.slopeRangeCheck(am.defineTokenCoords.sub(a.id,0,2*(t.cy-a.cy),"a",0),t.strProps,am.defineTokenCoords.sub(f.id,0,2*(t.cy-f.cy),"f",0))]

[H, if(! isFlanking && a.x2_ >= t.cx && a.y1_ <= t.cy && f.x1_ <= t.cx && f.y2_ >= t.cy): isFlanking = am.slopeRangeCheck(am.defineTokenCoords.sub(a.id,2*(t.cx-a.cx),0,"a",0),t.strProps,am.defineTokenCoords.sub(f.id,2*(t.cx-f.cx),0,"f",0))]

[H, if(! isFlanking && a.x2_ >= t.cx && a.y2_ >= t.cy && f.x1_ <= t.cx && f.y1_ <= t.cy): isFlanking = am.slopeRangeCheck(am.defineTokenCoords.sub(f.id,0,0,"a",0),t.strProps,am.defineTokenCoords.sub(a.id,0,0,"f",0))]

};{}]

[H: macro.return = isFlanking]

!!

@@ @defineTokenCoords.sub

[H: id.sub = arg(0)]

[H, if(argCount() >= 3), code: {

[H: offsetX.sub = arg(1)]

[H: offsetY.sub = arg(2)]

};{

[H: offsetX.sub = 0]

[H: offsetY.sub = 0]

}]

[H, if(argCount() >= 4): prefix.sub = arg(3); prefix.sub = strformat("t%{id.sub}")]

[H, if(argCount() >= 5): preDefine.sub = arg(4); preDefine.sub = 1]

[H: indent.sub = 0.5]

[H: x.sub = getTokenX(0,id.sub)]

[H: y.sub = getTokenY(0,id.sub)]

[H: size.sub = max(1,listFind("Medium,Large,Huge,Gargantuan,Empty,Colossal",getSize(id.sub))+1)]

[H: strProp.sub = strformat("%{prefix.sub}.x1=%s; %{prefix.sub}.y1=%s; %{prefix.sub}.x2=%s; %{prefix.sub}.y2=%s; %{prefix.sub}.size=%{size.sub}; %{prefix.sub}.cx=%s; %{prefix.sub}.cy=%s; %{prefix.sub}.x1_=%s; %{prefix.sub}.y1_=%s; %{prefix.sub}.x2_=%s; %{prefix.sub}.y2_=%s;", x.sub+offsetX.sub, y.sub+offsetY.sub, x.sub+size.sub+offsetX.sub, y.sub+size.sub+offsetY.sub, x.sub+(size.sub/2)+offsetX.sub, y.sub+(size.sub/2)+offsetY.sub, x.sub+offsetX.sub+indent.sub, y.sub+offsetY.sub+indent.sub, x.sub+size.sub+offsetX.sub-indent.sub, y.sub+size.sub+offsetY.sub-indent.sub)]

<!--

x1 - left x

x2 - right x

y1 - up y

y2 - down y

size - token grid unit size

cx - center x

cy - center y

x1_ - indented left x

x2_ - indented right x

y1_ - indented up y

y2_ - indented down y

-->

[H, if(preDefine.sub): varsFromStrProp(strProp.sub)]

[H: macro.return = strProp.sub]

!!

@@ @slopeRangeCheck

<!-- slopeRangeCheck(attackerProps,targetProps,allyProps): isFlanking -->

[H: varsFromStrProp(arg(0))]

[H: varsFromStrProp(arg(1))]

[H: varsFromStrProp(arg(2))]

<!-- Since we are dealing with grid squares, the distance to the center of grid is 0.5 -->

[H: offset = 0.5]

[H: infinity = 1000000000]

[H: isFlanking = 0]

<!-- get slope1, left-most slope -->

[H, if(a.x2_ >= t.x1): slope1 = infinity; slope1 = (t.y1 - a.y1_)/(t.x1 - a.x2_)]

<!-- slope offset is used to determine best point of reference. Either UL or LR depending on slope relation to slope of 1 -->

[H, if(slope1 < 1), code: {

[H: slope1.offset = t.size]

[H: slope1 = ((t.y1 + slope1.offset) - a.y1_)/((t.x1 + slope1.offset) - a.x2_)]

};{

[H: slope1.offset = 0]

}]

<!-- get slope2, right-most slope -->

[H, if(a.y2_ >= t.y1): slope2 = 0; slope2 = (t.y1 - a.y2_)/(t.x1 - a.x1_)]

[H, if(slope2 > 1), code: {

[H: slope2.offset = t.size]

[H: slope2 = ((t.y1 + slope2.offset) - a.y2_)/((t.x1 + slope2.offset) - a.x1_)]

};{

[H: slope2.offset = 0]

}]

<!-- get max left and right values -->

[H, if(slope1 == infinity): isLeft = 1; isLeft = 0]

[H, if(slope2 == 0): isRight = 1; isRight = 0]

<!-- Check left slope for left of ally and border crossover -->

[H, if(! isLeft), code: {

[H: slope1.b = t.y1 + slope1.offset - ((t.x1 + slope1.offset) * slope1)]

[H: slope1.x1 = slope1 * f.x1_ + slope1.b]

[H, if(slope1.x1 > f.y2_): isLeft = 1]

[H, if(! isLeft), code: {

<!-- also check to see if line intersects flanker wall -->

[H: slope1.x2 = slope1 * f.x2_ + slope1.b]

[H, if((slope1.x1 <= f.y2_ && slope1.x1 >= f.y1_) || (slope1.x2 <= f.y2_ && slope1.x2 >= f.y1_)): isFlanking = 1]

};{}]

[H, if(! isLeft && ! isFlanking), code: {

[H: slope1.y1 = (f.y1_ - slope1.b)/slope1]

[H: slope1.y2 = (f.y2_ - slope1.b)/slope1]

[H, if((slope1.y1 <= f.x2_ && slope1.y1 >= f.x1_) || (slope1.y2 <= f.x2_ && slope1.y2 >= f.x1_)): isFlanking = 1]

};{}]

};{}]

[H, if(! isRight && ! isFlanking), code: {

[H: slope2.b = t.y1 + slope2.offset - ((t.x1 + slope2.offset) * slope2)]

[H: slope2.y1 = (f.y1_ - slope2.b)/slope2]

[H, if(slope2.y1 > f.x2_): isRight = 1]

<!-- also check to see if line intersects flanker wall -->

[H, if(! isRight), code: {

[H: slope2.y2 = (f.y2_ - slope2.b)/slope2]

[H, if((slope2.y1 <= f.x2_ && slope2.y1 >= f.x1_) || (slope2.y2 < f.x2_ && slope2.y2 >= f.x1_)): isFlanking = 1]

};{}]

<!-- check through other flanker walls -->

[H, if(! isRight && ! isFlanking), code: {

[H: slope2.x1 = slope2 * f.x1_ + slope2.b]

[H: slope2.x2 = slope2 * f.x2_ + slope2.b]

[H, if((slope2.x1 <= f.y2_ && slope2.x1 >= f.y1_) || (slope2.x2 < f.y2_ && slope2.x2 >= f.y1_)): isFlanking = 1]

};{}]

};{}]

<!-- The 2 slopes surround the flanker which means all points of flanker are flanking -->

[H, if(isLeft && isRight): isFlanking = 1]

[H: macro.return = isFlanking]

!!

[/spoiler]

Add these lines to onCampaignLoad:

thisLib if not already defined is [H: thisLib = getMacroLocation()]. Once you are done editing onCampaignLoad, then you need to run it for the functions to be defined this session. As always, save your campaign before making these changes just in case something gets messed up. Once you're satisfied it's working, save campaign to new name.