Mini Macro Challenges (Interest)

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

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

Re: Mini Macro Challenges (Interest)

Post by wolph42 »

so this is what i made of it:

first create an object with n=2 to 100 with all their dividers (required only once):

Code: Select all

[h:tmp	= "{}"]

[h,count(98),CODE:{
	[n			= roll.count + 2]
	[divArray	= "[]"]
	[d			= 2]
	[while(d <= 0.5*n), CODE:{
		[if(!mod(n,d)): divArray = json.append(divArray, d)]
		[d		= d+1]
	}]
	[tmp	= json.set(tmp, n, json.append(divArray, n))]
}]

[h:setLibProperty("GCDb", tmp, "lib:wolf")]

done.
then this returns the GCD of an array of numbers:

Code: Select all

[h,foreach(t, var): result = json.intersection(result, json.get(GCDb,t))]
yup, thats the entire 'core' of the matter.

and in a more robust (and full) form:

Code: Select all

[h:GCDb		= getLibProperty("GCDb", "lib:wolf")]
[h:var		= arg(0)]
[h:result	= json.get(GCDb, json.get(var,0))]

<!-- in case GCD does not exist in the lib, update the lib -->
[h:n = math.arrayMax(var)]
[h,if(!json.contains(GCDB, n)), CODE:{
	[divArray	= "[]"]
	[d			= 2]
	[while(d <= 0.5*n), CODE:{
		[if(!mod(n,d)): divArray = json.append(divArray, d)]
		[d		= d+1]
	}]
	[GCDb	= json.set(GCDb, n, json.append(divArray, n))]

	[setLibProperty("GCDb", GCDb, "lib:wolf")]
}]
<!-- -->


[h,foreach(t, var): result = json.intersection(result, json.get(GCDb,t))]
[h,if(json.length(result)):macro.return	= math.arrayMax(result) ; macro.return = 0]


Glendwyr
Kobold
Posts: 16
Joined: Mon Apr 11, 2016 4:11 am

Re: Mini Macro Challenges (Interest)

Post by Glendwyr »

For two numbers a > b, the following algorithm should work:
  1. Define r = mod(a,b). Maptool doesn't seem to have a mod function, but nevermind.
  2. If r = 0, then the greatest common factor is b. Else a = b and b = r; return to step 1.
So I get this UDF -- call it getGCF:

Code: Select all

[H: x1 = arg(0)]
[H: x2 = arg(1)]

[H: a = max(x1,x2)]
[H: b = min(x1,x2)]
[H: r = a - b*floor(a/b)]
[H, While(r > 0), Code:
{	[a = b]
	[b = r]
	[r = a - b*floor(a/b)]
}]
[H: macro.return = b]
Obviously a well-written script would ensure that x1 and x2 are positive integers.

This doesn't get you to the greatest common factor of an entire array, but after making something unnecessarily complicated, this second function, getArrayGCF, should work:

Code: Select all

[H: InputArray = arg(0)]
[H: tmp = getGCF(json.get(InputArray,0),json.get(InputArray,1))]
[H, If(json.length(InputArray) == 2): 
	macro.return = tmp; 
	macro.return = getArrayGCF(json.set(json.remove(InputArray,0),0,tmp))]
It's just repeatedly using the fact that gcf(a,b,c) = gcf(gcf(a,b),c).

Again, a sane person would make sure the input made sense!

User avatar
Full Bleed
Demigod
Posts: 4736
Joined: Sun Feb 25, 2007 11:53 am
Location: FL

Re: Mini Macro Challenges (Interest)

Post by Full Bleed »

I'd like to make a function that looks at a grid coordinate (X,Y), pulls the VBL shape data, and then determines how much of the grid that VBL fills.

You can assume a square grid and a 100x100 grid size (or pull the custom grid size from getInfo) to get the pixel area of a grid.

I don't, however, understand the VBL shape data enough to work out the math.

So here is some sample VBL data:

Code: Select all

[{
  "generated": 1,
  "shape": "polygon",
  "fill": 1,
  "close": 1,
  "thickness": 0,
  "points":   [
        {
      "x": 5901.99,
      "y": 5230.97
    },
        {
      "x": 5900,
      "y": 5231.02
    },
        {
      "x": 5901.05,
      "y": 5281.04
    },
        {
      "x": 5901.05,
      "y": 5281.04
    },
        {
      "x": 5899.99,
      "y": 5281.45
    },
        {
      "x": 5900,
      "y": 5281.45
    },
        {
      "x": 5900,
      "y": 5283.6
    },
        {
      "x": 5901.09,
      "y": 5283.17
    },
        {
      "x": 5901.09,
      "y": 5283.17
    },
        {
      "x": 5901.45,
      "y": 5300
    },
        {
      "x": 5903.45,
      "y": 5300
    },
        {
      "x": 5903.08,
      "y": 5282.4
    },
        {
      "x": 5903.08,
      "y": 5282.4
    },
        {
      "x": 5994.36,
      "y": 5246.93
    },
        {
      "x": 5993.63,
      "y": 5245.06
    },
        {
      "x": 5903.03,
      "y": 5280.27
    },
        {
      "x": 5901.99,
      "y": 5230.97
    },
        {
      "x": 5901.99,
      "y": 5230.97
    }
  ]
}]

Anyone know how that data can be used to determine the area of the VBL fill?
Maptool is the Millennium Falcon of VTT's -- "She may not look like much, but she's got it where it counts."

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

Re: Mini Macro Challenges (Interest)

Post by wolph42 »

hmm thats some next level stuff youre asking. Here's an js algorithm to do that: https://www.mathopenref.com/coordpolygonarea2.html it should be possible to turn that into mt, but not sure how fast that would be.

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

Re: Mini Macro Challenges (Interest)

Post by aliasmask »

That is indeed some higher math stuff, but depending on your goal/reason maybe movedOverDrawing could be of help.

User avatar
Full Bleed
Demigod
Posts: 4736
Joined: Sun Feb 25, 2007 11:53 am
Location: FL

Re: Mini Macro Challenges (Interest)

Post by Full Bleed »

aliasmask wrote:
Sun Aug 23, 2020 9:42 pm
That is indeed some higher math stuff, but depending on your goal/reason maybe movedOverDrawing could be of help.
Won't really help with the use case.

Essentially, my custom movement system needs to be smarter about how it allows tokens to move through (and not through) some VBL. Since there is no way to look at MBL with macros I have to get creative with how it deals with standard VBL (as virtual MBL) in recognizing what the token can and can't move through.

For example, I can make a basic vision check into the target grid. If the token can't see the center of the grid it's moving to, then it's probably not something it can move into. But not necessarily. I might be using an X on some bushes that block vision but should not block movement through. There is also an issue with hard right VBL corners using just the vision method. The "corner" blocks sight to the center of the adjacent 45 degree square and a basic vision check fails to let the token take the step even though it's not moving into a grid with any VBL.

So a useful check for the toolbox would be to look into a target square and see if it has more than 50% VBL in it. If it does, then it's very likely that it is a square the token shouldn't be able to move into and not just an X or thin line that is meant to block view but not movement. It would work well with irregular walls and associated VBL tracing outside it, too.

The formula Wolph found should work given the logic it uses (I found that method described as a "shoelace method" elsewhere)... I just don't know how to convert it into MTscript from JS.
Maptool is the Millennium Falcon of VTT's -- "She may not look like much, but she's got it where it counts."

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

Re: Mini Macro Challenges (Interest)

Post by wolph42 »

that would be something like this:

Code: Select all

data = json.get(vblData, 0)
points = json.get(data,"points")
n = json.length(points)
area=0

[for(i,0,n-2), CODE:{
  point1 = json.get(points,i)
  point2 = json.get(points,i+1)
  json.toVars(point1)
  x2 = json.get(point2,"x")
  y2 = json.get(point2,"y")
  area = x+x2 * y2-y1

}]

macro.return = area/2
im not sure though whether mt fits the convention, so tinkering is probabably required. and note that the vbldta can consist out of multiple areas, so a foreach is required over the above code to deal with that.

Next to that I *think* it should be possible to do the for loop in one line, to optimise for speed, but i would do that after youve established that it works.

User avatar
bubblobill
Giant
Posts: 167
Joined: Sun Jan 24, 2010 3:07 pm

Re: Mini Macro Challenges (Interest)

Post by bubblobill »

Does getVBL() return all shapes present in the area or calculate the sections in the area.

e.g. If I have a rectangle from (0,0) to (400,100) and I get VBL in the square (0,0) to (200,200), is the VBL returned the whole rectangle or just the intersection (0,0) to (200,100)?

The VBL on my Smart Doors tokens is usually in an area 128 x 800.
The tokens themselves are only used at size Medium.
If you get their VBL is it scaled to the token size?
Bubblobill on the forum.
@Reverend on the MapTool Discord Server

Responsible for less atrocities than most... I do accept responsibility for these though: SmartDoors, Simple d20 Framework - barebones, Drop-in: All 3.5 SRD Monsters, Drop in: Simple Character Editor, Battletech Framework

User avatar
Full Bleed
Demigod
Posts: 4736
Joined: Sun Feb 25, 2007 11:53 am
Location: FL

Re: Mini Macro Challenges (Interest)

Post by Full Bleed »

wolph42 wrote:
Mon Aug 24, 2020 3:03 am
that would be something like this:
I cobbled this together and am getting an 'Argument number 2 "points" to function "json.get" must be an integer.' error. I don't use jsons much so I don't know what the issue is:

Code: Select all

[h: res = input(
	"cx|0|X:|",
	"cy|0|Y:|"
	)]
[h: abort(res)]
[h: w = json.get(json.get(getInfo("map"),"grid"),"size")]
[h: rectangle = json.set("{'shape':'rectangle'}", "x", cx*w, "y", cy*w, "w", w,"h", w,"fill",1)]
[h: vblData = getVBL(rectangle , 0)]

[h: points = json.get(vblData, "points")]
[h: n = json.length(points)]
[h: area = 0]

[h, FOR (i, 0, n-2), CODE:
	{
		[h: point1 = json.get(points, i)]
		[h: point2 = json.get(points, i+1)]
		[h: json.toVars(point1)]
		[h: x2 = json.get(point2, "x")]
		[h: y2 = json.get(point2, "y")]
		[h: area = (x+x2) * (y2-y1)]
	}
]

[r: area = area/2]
Is the getVBL json an invald format since it's bracketed?
Maptool is the Millennium Falcon of VTT's -- "She may not look like much, but she's got it where it counts."

User avatar
Full Bleed
Demigod
Posts: 4736
Joined: Sun Feb 25, 2007 11:53 am
Location: FL

Re: Mini Macro Challenges (Interest)

Post by Full Bleed »

bubblobill wrote:
Mon Aug 24, 2020 4:19 am
Does getVBL() return all shapes present in the area or calculate the sections in the area.

e.g. If I have a rectangle from (0,0) to (400,100) and I get VBL in the square (0,0) to (200,200), is the VBL returned the whole rectangle or just the intersection (0,0) to (200,100)?

The VBL on my Smart Doors tokens is usually in an area 128 x 800.
The tokens themselves are only used at size Medium.
If you get their VBL is it scaled to the token size?
Not sure I fully follow you... or if VBL on a token is "different" and I'm missing your point entirely... but when pulling VBL from a single grid loc it appears to only send the data in that single grid (the points are all located in that grid and not outside the grid even if the shape extends outside it.) Is that what you're asking?

Getting token VBL might respond differently... and might even need its own function.
Maptool is the Millennium Falcon of VTT's -- "She may not look like much, but she's got it where it counts."

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

Re: Mini Macro Challenges (Interest)

Post by wolph42 »

well, as i explained you need to add an additional foreach around the code:

Code: Select all

[h: res = input(
	"cx|0|X:|",
	"cy|0|Y:|"
	)]
[h: abort(res)]
[h: w = json.get(json.get(getInfo("map"),"grid"),"size")]
[h: rectangle = json.set("{'shape':'rectangle'}", "x", cx*w, "y", cy*w, "w", w,"h", w,"fill",1)]
[h: vblData = getVBL(rectangle , 0)]

[foreach(item, vblData), CODE:{
	[h: points = json.get(item, "points")]
	[h: n = json.length(points)]
	[h: area = 0]

	[h, FOR (i, 0, n-2), CODE:
		{
			[h: point1 = json.get(points, i)]
			[h: point2 = json.get(points, i+1)]
			[h: json.toVars(point1)]
			[h: x2 = json.get(point2, "x")]
			[h: y2 = json.get(point2, "y")]
			[h: area = (x+x2) * (y2-y1)]
		}
	]


}]

[r: area = area/2]


untested

[brackets] is a json array, {accolades} is json object, in this case you have an array of objects containing an array of objects, so you require a couple json.gets to get to the core of the matter. Array is roughly the same as a list e.g. [1,2,3,4], object is a key:value pair e.g {x:1,y:2}.

as for getvbl, is simply makes an intersection of the area and returns the shape it 'found' in the designated spot. it does not (should not) scale or anything.


User avatar
Full Bleed
Demigod
Posts: 4736
Joined: Sun Feb 25, 2007 11:53 am
Location: FL

Re: Mini Macro Challenges (Interest)

Post by Full Bleed »

wolph42 wrote:
Wed Aug 26, 2020 10:01 am
and did you get it to work?
Sort of. But it really only works with solid shapes. Once you encounter poly lines it usually goes wonky.

I had to use macro code/functions I was more familiar with over what you provided because I had to tweek to account for some other peculiarities that MT has. Just getting the X,Y cords out of the getVBL json was annoying me. ;)

Not sure if I'm done working on this. It may be that the way MT draws some shapes might just not be compatible with this method.

Here is what I have right now. I'm sure it can be optimized (like combining the FOREACH), but it's pretty much instant as is and was easier for me to see what was going on as I was running into issues.

**Note see here for working code. This has bugs.**

Code: Select all

[h: res = input(
	"xCord|0|X|",
	"yCord|0|Y|"
	)]
[h: abort(res)]
[h: gridSize = json.get(json.get(getInfo("map"),"grid"),"size")]
[h: rectangle = json.set("{'shape':'rectangle'}", "x", xCord*gridSize, "y", yCord*gridSize, "w", gridSize,"h", gridSize,"fill",1)]
[h: vblData = getVBL(rectangle , 0)]

[h: gridArea = gridSize*gridSize]

[h: strProp = json.toList(vblData)]

[h: pointsJson = json.get(strProp, "points")]

[h: xList = ""]

[h, FOREACH(point, pointsJson, ""), CODE:
	{
		[h: x = json.get(point, "x")]
		[h: x = x - (xCord*100)]		
		[h: xList = listAppend(xList, x)]
	}
]

[h: yList = ""]

[h, FOREACH(point, pointsJson, ""), CODE:
	{
		[h: y = json.get(point, "y")]
		[h: y = y - (yCord*100)]
		[h: yList = listAppend(yList, y)]
	}
]

[h: firstProduct = 0]

[h: cordCount = listCount(xList)]

[h, if(cordCount == 0): assert(0, "There is no VBL in this grid.")]

[h: productCount = cordCount - 1]

[h, COUNT(productCount), CODE:
	{
		[h: firstProduct = firstProduct + (number(listGet(xList, roll.count)) * number(listGet(yList, roll.count+1)))]
	}
]

[h: secondProduct = 0]

[h, COUNT(productCount), CODE:
	{
		[h: secondProduct = secondProduct + (number(listGet(yList, roll.count)) * number(listGet(yList, roll.count+1)))]
	}
]

[h: polygonArea = absolutevalue(firstProduct - secondProduct)]
[h: percentageOfGrid = divide(polygonArea, gridArea) * 100]
[h: broadcast("VBL at grid ("+xCord+","+yCord+") is "+percentageOfGrid+"% of the grid.")]
Not working with polyline VBL grids is a bit of a bummer though as that's probably the primary cornercase I was looking to address.
Last edited by Full Bleed on Thu Aug 27, 2020 12:28 pm, edited 1 time in total.
Maptool is the Millennium Falcon of VTT's -- "She may not look like much, but she's got it where it counts."

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

Re: Mini Macro Challenges (Interest)

Post by wolph42 »

you made a mistake here:

Code: Select all

[h: firstProduct = firstProduct + (number(listGet(xList, roll.count)) * number(listGet(yList, roll.count+1)))]

User avatar
Full Bleed
Demigod
Posts: 4736
Joined: Sun Feb 25, 2007 11:53 am
Location: FL

Re: Mini Macro Challenges (Interest)

Post by Full Bleed »

wolph42 wrote:
Thu Aug 27, 2020 5:37 am
you made a mistake here:

Code: Select all

[h: firstProduct = firstProduct + (number(listGet(xList, roll.count)) * number(listGet(yList, roll.count+1)))]
That's actually correct... but it put me on the right path. The typo was in the secondProduct which should be:

Code: Select all

[h: secondProduct = secondProduct + (number(listGet(yList, roll.count)) * number(listGet(xList, roll.count+1)))]
And I also left off dividing by two in the final step with the version I shared. Should be:

Code: Select all

[h: polygonArea = absolutevalue(firstProduct - secondProduct)/2]
Now it looks to be working! Or, at least, polyline data looks to be much more accurate instead of the crazy numbers it was spitting before.

New working PoC code:

Code: Select all

[h: res = input(
	"xCord|0|X|",
	"yCord|0|Y|"
	)]
[h: abort(res)]
[h: gridSize = json.get(json.get(getInfo("map"),"grid"),"size")]
[h: rectangle = json.set("{'shape':'rectangle'}", "x", xCord*gridSize, "y", yCord*gridSize, "w", gridSize,"h", gridSize, "fill",1)]
[h: vblData = getVBL(rectangle , 0)]

[h: gridArea = gridSize*gridSize]

[h: strProp = json.toList(vblData)]

[h: pointsJson = json.get(strProp, "points")]

[h: hasVBL = if(json.isEmpty(pointsJson) == 1, 0, 1)]
[h: assert(hasVBL, "There is no VBL in grid ("+xCord+","+yCord+").")]

[h: xList = ""]
[h: yList = ""]

[h, FOREACH(point, pointsJson, ""), CODE:
	{
		[h: x = json.get(point, "x")]
		[h: x = x - (xCord*100)]		
		[h: xList = listAppend(xList, x)]
		[h: y = json.get(point, "y")]
		[h: y = y - (yCord*100)]
		[h: yList = listAppend(yList, y)]		
	}
]

[h: firstProduct = 0]

[h: cordCount = listCount(xList)]

[h: productCount = cordCount - 1]

[h, COUNT(productCount), CODE:
	{
		[h: firstProduct = firstProduct + (number(listGet(xList, roll.count)) * number(listGet(yList, roll.count+1)))]
	}
]

[h: secondProduct = 0]

[h, COUNT(productCount), CODE:
	{
		[h: secondProduct = secondProduct + (number(listGet(yList, roll.count)) * number(listGet(xList, roll.count+1)))]
	}
]

[h: polygonArea = absolutevalue(firstProduct - secondProduct)/2]
[h: percentageOfGrid = divide(polygonArea, gridArea) * 100]
[h: broadcast("VBL at grid ("+xCord+","+yCord+") is "+percentageOfGrid+"% of the grid.")]
Now off to build some functions...

Thanks for the help. :)

*Note: It doesn't work well for grids that have non-contiguous VBL objects in them. That's not likely to be a problem for me given the way I apply VBL and it could (likely) be circumvented by looking for shapes that "close" and then loop through other possible shapes in the data. Probably best to make a separate function for breaking the VBL data into multiple shapes (if they exist). But the payoff for such an unlikely corner case for me might not be there.

Note2: After just looking at multiple VBL object grids, it looks like the first coordinate pair is used as a kind of "break" between additional shapes. So getting those shapes might not be that hard after all...
Maptool is the Millennium Falcon of VTT's -- "She may not look like much, but she's got it where it counts."

Post Reply

Return to “Macros”