Programatically determine firing arc?

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

Post Reply
ziltmilt
Dragon
Posts: 331
Joined: Sun Apr 29, 2007 9:28 pm
Contact:

Programatically determine firing arc?

Post by ziltmilt »

Is there functionality already built into a Maptool function that will tell you which facing or arc that a target is getting hit, depending on the source of the attack?

Example - a ship is fired upon by a target dead ahead. The function is passed the token ID of the attack's source and returns 'Front'.

User avatar
wolph42
Winter Wolph
Posts: 9999
Joined: Fri Mar 20, 2009 5:40 am
Location: Netherlands
Contact:

Re: Programatically determine firing arc?

Post by wolph42 »

im guessing not in the core of MT, but a lot has been developed on top. Im however not quite sure what you mean. Can you elaborate on what it is you want?

ziltmilt
Dragon
Posts: 331
Joined: Sun Apr 29, 2007 9:28 pm
Contact:

Re: Programatically determine firing arc?

Post by ziltmilt »

The framework I'm building requires a user to initiate an attack by selecting the attacking token, in this case, a vehicle. The attacker chooses a target and the macro resolves the attack, by passing a set of values to the defending token.

Then a second macro is launched by the defending player selecting the defending token. Vehicles in the game can have shields and can deploy the shields at different strength across 4 different sides: front, left, rear, right.

It'd be cool to have functionality that could be called by the second macro that would determine the side of the defender that is struck, so that the appropriate shield level is automatically applied. The correct side of the defender (front, left, rear, right) would simply depend on the position of the attacker relative to the defender.

Does this make sense?

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

Re: Programatically determine firing arc?

Post by aliasmask »

Facing of the targeted token matters as well. You can use the math functions to determine the angle and adjust for facing. A simpler method would be to use the slope method (rise/run or ydiff/xdiff). I have some code to determine flanking that could be of use. One thing to consider is the grid. If snapped to grid and on token layer then the x,y is in the center of token otherwise it's in the top left.

Here is the core of my flanking test. Let me know if you have any questions.
||| CODE |||

Code: Select all

@@ @isFlanking
@PROPS@ fontColor=black ; autoExecute=true ; fontSize=11pt ; sortBy=68 ; color=lime ; playerEditable=false ; applyToSelected=false ; group=AM Updates ; 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 = 0]
[H: canFlank = if(((a.y1 > t.y1 && t.y1 > f.y1) || (a.y1 < t.y1 && t.y1 < f.y1)) || ((a.x1 > t.x1 && t.x1 > f.x1) || (a.x1 < t.x1 && t.x1 < f.x1)),1,0)]
[H, if(canFlank), code: {
   [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(canFlank && ! isFlanking && a.size == 1 && f.size == 1), code: {
   [H: denominator = f.cx - a.cx]
   [H,if(denominator): slope = (f.cy - a.cy)/infinity; slope = (f.cy - a.cy)/denominator]
   [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.x1 && (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(canFlank && ! 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]

!!
@@ @getFlankers
@PROPS@ fontColor=black ; autoExecute=true ; fontSize=11pt ; sortBy=68 ; color=lime ; playerEditable=false ; applyToSelected=false ; group=AM Updates ; tooltip= ; minWidth=94 ; 
<!-- 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]

!!
@@ @slopeRangeCheck
@PROPS@ fontColor=black ; autoExecute=true ; fontSize=11pt ; sortBy=68 ; color=lime ; playerEditable=false ; applyToSelected=false ; group=AM Updates ; tooltip= ; minWidth=94 ; 
<!-- 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]

!!
@@ @defineTokenCoords.sub
@PROPS@ fontColor=black ; autoExecute=true ; fontSize=11pt ; sortBy=68 ; color=lime ; playerEditable=true ; applyToSelected=false ; group=AM Updates ; tooltip= ; minWidth=147 ; 
[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]

!!
As for your specific problem, here is some pseudo-code:

Code: Select all

getAttackerDetails()
determine slope and direction to attacker.
convert slope and direction to an angle.
add the facing to angle to determine side hit.
note: MT has an inverted y-axis for coords but not for token facing.

User avatar
wolph42
Winter Wolph
Posts: 9999
Joined: Fri Mar 20, 2009 5:40 am
Location: Netherlands
Contact:

Re: Programatically determine firing arc?

Post by wolph42 »

yes this makes sense and yes this is possible...but quite a bit of work to program...

its a LOT easier to indeed split everything up into two separate macros, launched individually by the user. (e.g. ATTACK and DEFEND or RESOLVE). the attack macro can allow 'target based selection' (search the forum) and consist of a form where you can set all the attack parameters, when done everything is stored on a lib:token.
Then the defend macro is launched, retrieves all stored values and calculates the end result and applies it. Have a look at my warhammer framework which has this (only the defend part is done automatically).

edit: ah Alias already replied...

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

Re: Programatically determine firing arc?

Post by aliasmask »

How do you handle having 2 sides exposed to the attacker? Percentage chance to hit either side or is it always a straight shot to the center of token? Corner shots when need you to chose a method of which shield to hit.

xavram
Dragon
Posts: 891
Joined: Tue Apr 20, 2010 8:22 pm

Re: Programatically determine firing arc?

Post by xavram »

Not sure if this is the same thing, but I handle this with a "vision" macro that is aligned with the "real" token, the rotated according. It has different vision types (since front/back vision "cone" is different from the left/right vision "cone") and then uses the "isVisible" function to determine if points on the target are visible or not (to account for vision blocking).

I can share code if you think it would help but its pretty specific to my application.

Post Reply

Return to “Macros”