PDA

View Full Version : Custom Pointers Coding Toolkit - Over Several Posts



dulux-oz
July 26th, 2013, 18:38
Hi Kids,

There's a little known and (relatively) un-Documented (until now) feature of FG2 that allows us to create and use our own Custom Pointers inside ImageControls. Getting it right is not easy but I've created a Toolkit that should make things a whole lot easier - and here it is. Read it all (if you want to know how to do things the Toolkit doesn't cover) and have fun.

Background/Theory

The Default Pointers included with FG2 are a series of Splines. Originally a Spline was a flexible piece of metal or wood shaped and fixed at certain points along its length and then allowed to form a natural series of curves. The resulting curves had the least amount of stress on them and flowed naturally from one into the other. These curves were then copied down onto graph paper and transfer to make various physical items, such as boat hulls and aircraft wings.

Since the age of Computer Aided Design (CAD) the term Spline has come to mean a series of smooth curves defined by Mathematical Equations. The Mathematical Equations have different orders of magnitude, and consist of Linear Equations, Quadratic Equations, Cubic Equations, Quartic Equations, Quintic Equations, Sextic Equations, etc.

"Oh God" I hear you cry, "Do I have to learn all this Algebra and Polynomial Mathematics (again)?"

No, and here's why:

The Curves that make up the Splines that make up the Pointers in FG2 are B-Curves - and are in fact a particular type of B-Curve known as a Bézier Curve. In particular, they are Cubic Bézier Curves.

A Cubic Bézier Curve is defined by four points: two End Points and two Control Points. The Curve leaves an End Point and heads towards (but normally never touches) the nearest Control Point before curving away to meet the part of the curve coming from the other End Point, which itself began its journey heading towards its own Control Point. Depending upon how close a Control Point is to its End Point and compared to the distance of the other End Point/Control Point pair determines the amount of curvature of a given length of the Curve. If both Control Points are the same distance from their respective End Points then the resulting Curve will be regular (ie symmetrical around the Mid-Point of the Curve), otherwise we end up with an irregular Curve.

By its definition, for two such Curves to form a Spline they must meet and flow from one to the other smoothly. To do this not only must the End Point of one Curve be the End Point of another (ie have the same coordinates), but the End Point(s) and their respective Control Points MUST form a straight line; if they do not, then the resulting two Curves DO NOT form a Spline.

In FG2 the four points are represented as an array (table) of four rows and two columns in the form of:



aCurve = {
{ nStartPointXCoord, nStartPointYCoord },
{ nStartPointControlXCoord, nStartPointControlYCoord },
{ nEndPointControlXCoord, nEndPointControlYCoord },
{ nEndPointXCoord, nnEndPointYCoord }
};


The entire shape to be drawn is represented by an array of these aCurves and is built as follows:



table.insert(aShape,aCurve1);
table.insert(aShape,aCurve2);
etc.

dulux-oz
July 26th, 2013, 18:45
Continuing on:

Usage

To access the Custom Pointer functionality we need to add the following code to our ImageControl:



<imagecontrol>
<pointertypes>
<custom name="45Cone">
<icon>pointer_cone</icon>
<label>Draw a 45-Degree Cone</label>
</custom>
</pointertypes>
</imagecontrol>


Up to five PointerTypes can be defined. The order that they are defined is the order that the Buttons will appear on the relevant RadialMenu.

By default, the four Default Pointers which come with FG2 are defined as:



<imagecontrol>
<pointertypes>
<arrow>
<icon>pointer</icon>
<label>Draw Arrow</label>
</arrow>
<square>
<icon>pointer_square</icon>
<label>Draw Square</label>
</square>
<circle>
<icon>pointer_circle</icon>
<label>Draw Circle</label>
</circle>
<cone>
<icon>pointer_cone</icon>
<label>Draw Cone</label>
</cone>
</pointertypes>
</imagecontrol>


If these are the only Pointers being used they do not need to be defined (they are defined by default). However, if you wish to use only some of these Default Pointers (with or without one or more Custom Pointers) or any or all of these Default Pointers in addition to any custom Pointers then their individual definitions need to be included in your Imagecontrol. Default Pointers not included will NOT appear on the RadialMenu.

The four Default Pointers are always available via the Left-And-Right Mouse Button technique.

Once you define a Custom Pointer the function onBuildCustomPointer( nStartXCoord, nStartYCoord, nEndXCoord, nEndYCoord, sPointerType ) becomes avalible. The five parameters are the starting X-Coordinate, the starting Y-Coordinate, the ending X-Coordinate, the ending Y-Coordinate (all numerics) and the Custom Pointer Type (a string that matches the "name" attribute of the Custom tag). The function returns aShapeCurves (an array of Curves as defined above), aDistLabelPosition (an array of two numbers that specify where the Pointer Length Label should be drawn relative to its origin) and bDrawArrow (a boolean indicating whether to end the pointer with an arrowhead pointing in the direction of the last Curve).

"OK, so how do I know where to place my Control Points to get the curve I want?" you ask. Well, you could just enter in the coordinates for the four points and see how the curve looks, then go back and change them to try again, or you could actually learn the Mathematics involved and work it out with a calculator, but instead I've written an LUA file with all of the code you should need in it (shown in its entirety below).

To use the file add it to the ImageContol:



<imagecontrol>
<script file="Pointer_Toolkit.lua" />
</imagecontrol>


When working with the code I realised that, thanks to the pioneering work of Tero Parvinen (one of the FG2 Coders) the easiest way to draw the Pointers was to drawn then in the Negative Y-Direction (ie down the image) with an Origin (or Starting Point) of (0,0) and symmetrical around the Y-Axis, then rotating the Curves and transposing them to their correct orientation and position. When using the Toolkit this is how you should construct your Pointers.

I also determined that, fundamentally, normally all of the Pointers we would probably want can be drawn with but three basic Curves: a Line (a straight Curve), the quarter arc of an Ellipse (a quarter of an Oval) and the Arc of a Circle.

The code for a Line relies on the fact that a Line's Control Points are the Line's End Points. The Offset is used to move the entire Line up or down the Y-Axis and is normally 0 (see below for an exception):



function fpLineCurve(nStartLineXCoord,nStartLineYCoord,nEnd LineXCoord,nEndLineYCoord,nOffset)
-- Draw a Line offset in the Negative-Y direction by nOffset.
local aCurve = {{nStartLineXCoord,nStartLineYCoord-nOffset},
{nStartLineXCoord,nStartLineYCoord-nOffset},
{nEndLineXCoord,nEndLineYCoord-nOffset},
{nEndLineXCoord,nEndLineYCoord-nOffset}};
return aCurve;
end


The code for an Ellipse Curve draws a quarter Ellipse in only one quadrant of the X-Y Plane (which quadrant depends upon the sign on the two Radii). An Ellipse has two Radius values. The relative size of the two Radii determine the narrowness of the resulting Ellipse. If the two Radii are the same the resulting Curve is a quarter Circle. The value of Kappa is a constant and is ONLY valid for quarter Ellipse/circle Curves. If you try to use it for Curves less than or greater than 90 Degree Curves the resulting Curve will NOT be an accurate Ellipse/Circle. The Offset is used to move the entire Ellipse Quarter up or down the Y-Axis and is normally 0 (see below for an exception):



function fpEllipseCurve(nXRadius,nYRadius,nOffset)
-- Draw a 90-Degree Arc of an Ellipse offset in the Negative-Y direction by nOffset.
local nKappa = 4/3*(math.sqrt(2)-1);
local aCurve = {{0,nYRadius-nOffset},
{nXRadius*nKappa,nYRadius-nOffset},
{nXRadius,nYRadius*nKappa-nOffset},
{nXRadius,-nOffset}};
return aCurve;
end


Writing a generic Circle Curve function that could drawn an Arc of any Radius anywhere at any Rotation proved difficult and instead of one I ended up with two functions which both call a third to do the work.

The first ( fpAngleArcCurve() ) is used when you know the Starting Point of the Curve, its Radius/Centre and the Angle it should cover, and takes as parameters the Starting X and Y Coordinates of the Curve, the X and Y Coordinates of the Centrepoint of the Circle the Curve is a part of and the Angle that the Curve needs to cover (nArcDegrees) in Degrees. nArcDegrees must be between -180 and +180 (not inclusive) and must not be 0.

The second ( fpEndpointArcCurve() ) is used when you know the Starting and Ending Points of the Curve and its Radius/Centre, and takes as parameters the Starting X and Y Coordinates of the Curve, the X and Y Coordinates of the Centrepoint of the Circle the Curve is a part of and the Ending X and Y Coordinates of the Curve.

Both functions call the third function ( fpArcCurve() ) after first manipulating values. fpArcCurve() draws an Arc of the desired Radius in the Positive X-Direction and bisected by the X-Axis. It then rotates and transposes the Arc to the desired position and rotation as defined by the calling function.



function fpAngleArcCurve(nStartCurveXCoord,nStartCurveYCoor d,nCurveCentreXCoord,nCurveCentreYCoord,nArcDegree s)
-- Draw a Circular Arc covering nArcDegreess (-180 < nArcDegrees < 180, nArcDegrees ~= 0) given
-- the Circle Centre and the Arc Start Point.
if nArcDegrees == 0 or
nArcDegrees <= -180 or
nArcDegrees >= 180 then
return;
end
local nArcRadians = math.rad(nArcDegrees);
local nRadius = math.sqrt((nCurveCentreXCoord-nStartCurveXCoord)^2+(nCurveCentreYCoord-nStartCurveYCoord)^2);
if nRadius == 0 then
return;
end
local nAngleRadians = math.atan2(nStartCurveXCoord-nCurveCentreXCoord,nCurveCentreYCoord-nStartCurveYCoord)+nArcRadians/2;
return fpArcCurve(nAngleRadians,nArcRadians,nRadius,nCurv eCentreXCoord,nCurveCentreYCoord);
end

function fpEndpointArcCurve(nStartCurveXCoord,nStartCurveYC oord,nCurveCentreXCoord,nCurveCentreYCoord,nEndCur veXCoord,nEndCurveYCoord)
-- Draw a Circular Arc given the Circle Centre, the Arc Start Point and the Arc End Point.
local nStartAngleRadians = math.atan2(nStartCurveXCoord-nCurveCentreXCoord,nCurveCentreYCoord-nStartCurveYCoord);
local nEndAngleRadians = math.atan2(nEndCurveXCoord-nCurveCentreXCoord,nCurveCentreYCoord-nEndCurveYCoord);
local nArcRadians = math.abs(nStartAngleRadians-nEndAngleRadians)
if nArcRadians == 0 or
nArcRadians <= math.rad(-180) or
nArcRadians >= math.rad(180) then
return;
end
local nRadius = math.sqrt((nCurveCentreXCoord-nStartCurveXCoord)^2+(nCurveCentreYCoord-nStartCurveYCoord)^2);
if nRadius == 0 then
return;
end
local nAngleRadians = math.atan2(nStartCurveXCoord-nCurveCentreXCoord,nCurveCentreYCoord-nStartCurveYCoord)+nArcRadians/2;
return fpArcCurve(nAngleRadians,nArcRadians,nRadius,nCurv eCentreXCoord,nCurveCentreYCoord);
end

function fpArcCurve(nAngleRadians,nArcRadians,nRadius,nCurv eCentreXCoord,nCurveCentreYCoord)
-- Draw an Regular Arc (of a Circle) of Radius nRadius with an Origin of (0,0) and covering
-- an Arc of nArcRadians (in Radians) bisected by the Positive X-Axis and then Rotated
-- around the Origin by an angle of nAngleRadians (in Radians) and offset in both the
-- X-Direction and Y-Direction by nCurveCentreXCoord and nCurveCentreYCoord respectively.
local nX = math.cos(nArcRadians/2);
local nY = math.sin(nArcRadians/2);
local nStartX = nX*nRadius;
local nStartY = nY*nRadius;
local nControlX = nRadius*(4-nX)/3;
local nControlY = nRadius*(1-nX)*(3-nX)/(3*nY);
local aCurve = {{nStartX,nStartY},
{nControlX,nControlY},
{nControlX,-nControlY},
{nStartX,-nStartY}};
for nPointIndex,aPoint in ipairs(aCurve) do
local nXCoord = (aPoint[1]-nCurveCentreXCoord)*math.cos(nAngleRadians)-(aPoint[2]-nCurveCentreYCoord)*math.sin(nAngleRadians)+nCurve CentreXCoord;
local nYCoord = (aPoint[1]-nCurveCentreXCoord)*math.sin(nAngleRadians)+(aPoin t[2]-nCurveCentreYCoord)*math.cos(nAngleRadians)+nCurve CentreYCoord;
aCurve[nPointIndex] = {nXCoord,nYCoord};
end
return aCurve;
end

dulux-oz
July 26th, 2013, 18:49
Continuing on:

So, how do you use these Curve Definition Functions? Well, I've included some generic Pointer Definition Functions to show you how.

The first ( fpBoxPointer() ) draws a Box with a given Length and Width and is made up of four Lines. If the Length and Width are the same then we get a replica of the Default Square Pointer. The Offset is used to move the entire Box up or down the Y-Axis and is normally 0 (see below for an exception):



function fpBoxPointer(aShapeCurves,nLength,nWidth,nOffset)
-- Draw a Rectangle offset in the Negative-Y direction by nOffset.
table.insert(aShapeCurves,fpLineCurve(nWidth,nLeng th,-nWidth,nLength,nOffset));
table.insert(aShapeCurves,fpLineCurve(-nWidth,nLength,-nWidth,-nLength,nOffset));
table.insert(aShapeCurves,fpLineCurve(-nWidth,-nLength,nWidth,-nLength,nOffset));
table.insert(aShapeCurves,fpLineCurve(nWidth,-nLength,nWidth,nLength,nOffset));
end


The second ( fpCirclePointer() ) draws a replica of the default Circle Pointer with a given Radius and is made up of eight equal Arcs.



function fpCirclePointer(aShapeCurves,nRadius)
-- Draw a Circle of Radius nRadius made up of eight Regular Arcs.
local nDegreesInRadians = math.rad(45);
table.insert(aShapeCurves,fpAngleArcCurve(0,nRadiu s,0,0,45));
table.insert(aShapeCurves,fpAngleArcCurve(nRadius* math.sin(nDegreesInRadians),nRadius*math.cos(nDegr eesInRadians),0,0,45));
table.insert(aShapeCurves,fpAngleArcCurve(nRadius, 0,0,0,45));
table.insert(aShapeCurves,fpAngleArcCurve(-nRadius*math.sin(nDegreesInRadians),nRadius*math.c os(nDegreesInRadians),0,0,45));
table.insert(aShapeCurves,fpAngleArcCurve(0,-nRadius,0,0,45));
table.insert(aShapeCurves,fpAngleArcCurve(-nRadius*math.sin(nDegreesInRadians),-nRadius*math.cos(nDegreesInRadians),0,0,45));
table.insert(aShapeCurves,fpAngleArcCurve(-nRadius,0,0,0,45));
table.insert(aShapeCurves,fpAngleArcCurve(nRadius* math.sin(nDegreesInRadians),-nRadius*math.cos(nDegreesInRadians),0,0,45));
end


The third ( fpConePointer() ) draws a Cone of the given Radius and covering the given Angle and is made up of two Lines and an Arc. If nArcDegrees is set to 90 we get a replica of the Default Cone Pointer.



function fpConePointer(aShapeCurves,nRadius,nArcDegrees)
-- Draw a Cone with a Radius of nRadius and covering an Arc of nArcDegrees.
if nArcDegrees == 0 or
nArcDegrees <= -180 or
nArcDegrees >= 180 then
return;
end
local nArcRadians = math.rad(nArcDegrees);
local nXCoord = -nRadius*math.sin(nArcRadians/2);
local nYCoord = nRadius*math.cos(nArcRadians/2);
table.insert(aShapeCurves,fpLineCurve(0,0,nXCoord,-nYCoord,0));
table.insert(aShapeCurves,fpLineCurve(0,0,-nXCoord,-nYCoord,0));
table.insert(aShapeCurves,fpAngleArcCurve(-nRadius*math.cos(nArcRadians/2),nRadius*math.sin(nArcRadians/2),0,0,nArcDegrees));
end


Finally, the forth function ( fpEllipsePointer() ) draws an Ellipse of the given Radii and is made up of four Ellipse Curves (one in each quadrant). The Offset is used to move the entire Ellipse up or down the Y-Axis and is normally 0 (see below for an exception):



function fpEllipsePointer(aShapeCurves,nXRadius,nYRadius,nO ffset)
-- Draw an Ellipse offset in the Negative-Y direction by nOffset.
table.insert(aShapeCurves,fpEllipseCurve(nXRadius, nYRadius,nOffset));
table.insert(aShapeCurves,fpEllipseCurve(-nXRadius,nYRadius,nOffset));
table.insert(aShapeCurves,fpEllipseCurve(-nXRadius,-nYRadius,nOffset));
table.insert(aShapeCurves,fpEllipseCurve(nXRadius,-nYRadius,nOffset));
end


Finally, I've included a sample onBuildCustomPointer() function with eleven sample Custom Pointers to demonstrate some ways to use each of the Curve Definition Functions. Each can be used as is by setting the "name" attribute of the Custom tag in the ImageControl to the given name shown. The onBuildCustomPointer() function also does the final rotation and transposition of the given Pointer.

The first (CirclePointerAsEllipse) uses the fpEllipsePointer() function to replicate the Default Circle Pointer.

The second (CirclePointerAsArcs) uses the fpCirclePointer() function to replicate the Default Circle Pointer.

The third (HalfWidthEllipsePointerCenterOrigin) uses the fpEllipsePointer() function to draw an Ellipse which is half as wide as it is long.

The fourth (HalfWidthEllipsePointerStartPointOrigin) uses the fpEllipsePointer() function to draw an Ellipse which is half as wide as it is long and which starts at the Starting Point instead of the Starting Point being in the centre of the Pointer. I use this Pointer in my Übergame Ruleset to represent the Breath Weapon of a Green Dragon (a cloud of noxious chlorine gas).

The fifth (SquarePointer) uses the fpBoxPointer() function to replicate the Default Square Pointer.

The sixth (DoubleWidthBoxPointerCenterOrigin) uses the fpBoxPointer() function to draw a Rectangle which is twice as wide as it is long.

The seventh (DWBoxPointerStartPointOrigin) uses the fpBoxPointer() function to draw a Rectangle which is twice as wide as it is long and which starts at the Starting Point instead of the Starting Point being in the centre of the Pointer.

The eighth (ConePointer) uses the fpConePointer() function to replicate the Default Cone Pointer.

The ninth (60ConePointer) uses the fpConePointer() function to draw a 60 Degree Cone. I use this Pointer in my Übergame Ruleset to represent the Cone function of all Spells, etc.

The tenth (120ConePointer) uses the fpConePointer() function to draw a 120 Degree Cone. I use this Pointer in my Übergame Ruleset to represent the Burning Hands Spell.

Finally, "ArrowPointer" uses the fpLineCurve() function along with setting bDrawArrow to true to replicate the Default Arrow Pointer.



function onBuildCustomPointer(nStartXCoord,nStartYCoord,nEn dXCoord,nEndYCoord,sPointerType)
local nLength = math.sqrt((nEndXCoord-nStartXCoord)^2+(nEndYCoord-nStartYCoord)^2);
if nLength == 0 then
return
end
local aShapeCurves = {};
local aDistLabelPosition = {25,25};
local bDrawArrow = false;
local nAngleRadians = math.atan2(nEndXCoord-nStartXCoord,nStartYCoord-nEndYCoord);
-- Call the relevant Pointer Definition Function
-- Sample PointerTypes Shown
if sPointerType == "CirclePointerAsEllipse" then
fpEllipsePointer(aShapeCurves,nLength,nLength,0);
elseif sPointerType == "CirclePointerAsArcs" then
fpCirclePointer(aShapeCurves,nLength);
elseif sPointerType == "HalfWidthEllipsePointerCenterOrigin" then
fpEllipsePointer(aShapeCurves,nLength/2,nLength,0);
elseif sPointerType == "HalfWidthEllipsePointerStartPointOrigin" then
fpEllipsePointer(aShapeCurves,nLength/4,nLength/2,nLength/2);
elseif sPointerType == "SquarePointer" then
fpBoxPointer(aShapeCurves,nLength,nLength,0);
elseif sPointerType == "DoubleWidthBoxPointerCenterOrigin" then
fpBoxPointer(aShapeCurves,nLength,nLength*2,0);
elseif sPointerType == "DWBoxPointerStartPointOrigin" then
fpBoxPointer(aShapeCurves,nLength/2,nLength,nLength/2);
elseif sPointerType == "ConePointer" then
fpConePointer(aShapeCurves,nLength,90);
elseif sPointerType == "60ConePointer" then
fpConePointer(aShapeCurves,nLength,60);
elseif sPointerType == "120ConePointer" then
fpConePointer(aShapeCurves,nLength,120);
elseif sPointerType == "ArrowPointer" then
table.insert(aShapeCurves,fpLineCurve(0,0,0,-nLength,0));
bDrawArrow = true;
end
-- Rotate and Position the Pointer
for nIndex,aCurve in ipairs(aShapeCurves) do
for nPointIndex,aPoint in ipairs(aCurve) do
local nXCoord = aPoint[1]*math.cos(nAngleRadians)-aPoint[2]*math.sin(nAngleRadians)+nStartXCoord;
local nYCoord = aPoint[1]*math.sin(nAngleRadians)+aPoint[2]*math.cos(nAngleRadians)+nStartYCoord;
aCurve[nPointIndex] = {nXCoord,nYCoord};
end
end
return aShapeCurves,aDistLabelPosition,bDrawArrow;
end

dulux-oz
July 26th, 2013, 18:56
Finally:

The File - Pointer_Toolkit.lua

Feel free to copy and use this code as you like, but please include the copyright information found at the top.



--
-- © Copyright Matthew James BLACK 2005-13 except where explicitly stated otherwise.
-- Fantasy Grounds is Copyright © 2004-2012 SmiteWorks USA LLC.
-- Copyright to other material within this file may be held by other Individuals and/or Entities.
-- Nothing in or from this LUA file in printed, electronic and/or any other form may be used, copied,
-- transmitted or otherwise manipulated in ANY way without the explicit written consent of Matthew
-- James BLACK or, where applicable, any and all other Copyright holders.
--

function onBuildCustomPointer(nStartXCoord,nStartYCoord,nEn dXCoord,nEndYCoord,sPointerType)
local nLength = math.sqrt((nEndXCoord-nStartXCoord)^2+(nEndYCoord-nStartYCoord)^2);
if nLength == 0 then
return
end
local aShapeCurves = {};
local aDistLabelPosition = {25,25};
local bDrawArrow = false;
local nAngleRadians = math.atan2(nEndXCoord-nStartXCoord,nStartYCoord-nEndYCoord);
-- Call the relevant Pointer Definition Function
-- Sample PointerTypes Shown
if sPointerType == "CirclePointerAsEllipse" then
fpEllipsePointer(aShapeCurves,nLength,nLength,0);
elseif sPointerType == "CirclePointerAsArcs" then
fpCirclePointer(aShapeCurves,nLength);
elseif sPointerType == "HalfWidthEllipsePointerCenterOrigin" then
fpEllipsePointer(aShapeCurves,nLength/2,nLength,0);
elseif sPointerType == "HalfWidthEllipsePointerStartPointOrigin" then
fpEllipsePointer(aShapeCurves,nLength/4,nLength/2,nLength/2);
elseif sPointerType == "SquarePointer" then
fpBoxPointer(aShapeCurves,nLength,nLength,0);
elseif sPointerType == "DoubleWidthBoxPointerCenterOrigin" then
fpBoxPointer(aShapeCurves,nLength,nLength*2,0);
elseif sPointerType == "DWBoxPointerStartPointOrigin" then
fpBoxPointer(aShapeCurves,nLength/2,nLength,nLength/2);
elseif sPointerType == "ConePointer" then
fpConePointer(aShapeCurves,nLength,90);
elseif sPointerType == "60ConePointer" then
fpConePointer(aShapeCurves,nLength,60);
elseif sPointerType == "120ConePointer" then
fpConePointer(aShapeCurves,nLength,120);
elseif sPointerType == "ArrowPointer" then
table.insert(aShapeCurves,fpLineCurve(0,0,0,-nLength,0));
bDrawArrow = true;
end
-- Rotate and Position the Pointer
for nIndex,aCurve in ipairs(aShapeCurves) do
for nPointIndex,aPoint in ipairs(aCurve) do
local nXCoord = aPoint[1]*math.cos(nAngleRadians)-aPoint[2]*math.sin(nAngleRadians)+nStartXCoord;
local nYCoord = aPoint[1]*math.sin(nAngleRadians)+aPoint[2]*math.cos(nAngleRadians)+nStartYCoord;
aCurve[nPointIndex] = {nXCoord,nYCoord};
end
end
return aShapeCurves,aDistLabelPosition,bDrawArrow;
end

-- Pointer Definition Functions
function fpBoxPointer(aShapeCurves,nLength,nWidth,nOffset)
-- Draw a Rectangle offset in the Negative-Y direction by nOffset.
table.insert(aShapeCurves,fpLineCurve(nWidth,nLeng th,-nWidth,nLength,nOffset));
table.insert(aShapeCurves,fpLineCurve(-nWidth,nLength,-nWidth,-nLength,nOffset));
table.insert(aShapeCurves,fpLineCurve(-nWidth,-nLength,nWidth,-nLength,nOffset));
table.insert(aShapeCurves,fpLineCurve(nWidth,-nLength,nWidth,nLength,nOffset));
end

function fpCirclePointer(aShapeCurves,nRadius)
-- Draw a Circle of Radius nRadius made up of eight Regular Arcs.
local nDegreesInRadians = math.rad(45);
table.insert(aShapeCurves,fpAngleArcCurve(0,nRadiu s,0,0,45));
table.insert(aShapeCurves,fpAngleArcCurve(nRadius* math.sin(nDegreesInRadians),nRadius*math.cos(nDegr eesInRadians),0,0,45));
table.insert(aShapeCurves,fpAngleArcCurve(nRadius, 0,0,0,45));
table.insert(aShapeCurves,fpAngleArcCurve(-nRadius*math.sin(nDegreesInRadians),nRadius*math.c os(nDegreesInRadians),0,0,45));
table.insert(aShapeCurves,fpAngleArcCurve(0,-nRadius,0,0,45));
table.insert(aShapeCurves,fpAngleArcCurve(-nRadius*math.sin(nDegreesInRadians),-nRadius*math.cos(nDegreesInRadians),0,0,45));
table.insert(aShapeCurves,fpAngleArcCurve(-nRadius,0,0,0,45));
table.insert(aShapeCurves,fpAngleArcCurve(nRadius* math.sin(nDegreesInRadians),-nRadius*math.cos(nDegreesInRadians),0,0,45));
end

function fpConePointer(aShapeCurves,nRadius,nArcDegrees)
-- Draw a Cone with a Radius of nRadius and covering an Arc of nArcDegrees.
if nArcDegrees == 0 or
nArcDegrees <= -180 or
nArcDegrees >= 180 then
return;
end
local nArcRadians = math.rad(nArcDegrees);
local nXCoord = -nRadius*math.sin(nArcRadians/2);
local nYCoord = nRadius*math.cos(nArcRadians/2);
table.insert(aShapeCurves,fpLineCurve(0,0,nXCoord,-nYCoord,0));
table.insert(aShapeCurves,fpLineCurve(0,0,-nXCoord,-nYCoord,0));
table.insert(aShapeCurves,fpAngleArcCurve(-nRadius*math.cos(nArcRadians/2),nRadius*math.sin(nArcRadians/2),0,0,nArcDegrees));
end

function fpEllipsePointer(aShapeCurves,nXRadius,nYRadius,nO ffset)
-- Draw an Ellipse offset in the Negative-Y direction by nOffset.
table.insert(aShapeCurves,fpEllipseCurve(nXRadius, nYRadius,nOffset));
table.insert(aShapeCurves,fpEllipseCurve(-nXRadius,nYRadius,nOffset));
table.insert(aShapeCurves,fpEllipseCurve(-nXRadius,-nYRadius,nOffset));
table.insert(aShapeCurves,fpEllipseCurve(nXRadius,-nYRadius,nOffset));
end

-- Curve Definition Functions
function fpAngleArcCurve(nStartCurveXCoord,nStartCurveYCoor d,nCurveCentreXCoord,nCurveCentreYCoord,nArcDegree s)
-- Draw a Circular Arc covering nArcDegreess (-180 < nArcDegrees < 180, nArcDegrees ~= 0) given
-- the Circle Centre and the Arc Start Point.
if nArcDegrees == 0 or
nArcDegrees <= -180 or
nArcDegrees >= 180 then
return;
end
local nArcRadians = math.rad(nArcDegrees);
local nRadius = math.sqrt((nCurveCentreXCoord-nStartCurveXCoord)^2+(nCurveCentreYCoord-nStartCurveYCoord)^2);
if nRadius == 0 then
return;
end
local nAngleRadians = math.atan2(nStartCurveXCoord-nCurveCentreXCoord,nCurveCentreYCoord-nStartCurveYCoord)+nArcRadians/2;
return fpArcCurve(nAngleRadians,nArcRadians,nRadius,nCurv eCentreXCoord,nCurveCentreYCoord);
end

function fpEndpointArcCurve(nStartCurveXCoord,nStartCurveYC oord,nCurveCentreXCoord,nCurveCentreYCoord,nEndCur veXCoord,nEndCurveYCoord)
-- Draw a Circular Arc given the Circle Centre, the Arc Start Point and the Arc End Point.
local nStartAngleRadians = math.atan2(nStartCurveXCoord-nCurveCentreXCoord,nCurveCentreYCoord-nStartCurveYCoord);
local nEndAngleRadians = math.atan2(nEndCurveXCoord-nCurveCentreXCoord,nCurveCentreYCoord-nEndCurveYCoord);
local nArcRadians = math.abs(nStartAngleRadians-nEndAngleRadians)
if nArcRadians == 0 or
nArcRadians <= math.rad(-180) or
nArcRadians >= math.rad(180) then
return;
end
local nRadius = math.sqrt((nCurveCentreXCoord-nStartCurveXCoord)^2+(nCurveCentreYCoord-nStartCurveYCoord)^2);
if nRadius == 0 then
return;
end
local nAngleRadians = math.atan2(nStartCurveXCoord-nCurveCentreXCoord,nCurveCentreYCoord-nStartCurveYCoord)+nArcRadians/2;
return fpArcCurve(nAngleRadians,nArcRadians,nRadius,nCurv eCentreXCoord,nCurveCentreYCoord);
end

function fpArcCurve(nAngleRadians,nArcRadians,nRadius,nCurv eCentreXCoord,nCurveCentreYCoord)
-- Draw an Regular Arc (of a Circle) of Radius nRadius with an Origin of (0,0) and covering
-- an Arc of nArcRadians (in Radians) bisected by the Positive X-Axis and then Rotated
-- around the Origin by an angle of nAngleRadians (in Radians) and offset in both the
-- X-Direction and Y-Direction by nCurveCentreXCoord and nCurveCentreYCoord respectively.
local nX = math.cos(nArcRadians/2);
local nY = math.sin(nArcRadians/2);
local nStartX = nX*nRadius;
local nStartY = nY*nRadius;
local nControlX = nRadius*(4-nX)/3;
local nControlY = nRadius*(1-nX)*(3-nX)/(3*nY);
local aCurve = {{nStartX,nStartY},
{nControlX,nControlY},
{nControlX,-nControlY},
{nStartX,-nStartY}};
for nPointIndex,aPoint in ipairs(aCurve) do
local nXCoord = (aPoint[1]-nCurveCentreXCoord)*math.cos(nAngleRadians)-(aPoint[2]-nCurveCentreYCoord)*math.sin(nAngleRadians)+nCurve CentreXCoord;
local nYCoord = (aPoint[1]-nCurveCentreXCoord)*math.sin(nAngleRadians)+(aPoin t[2]-nCurveCentreYCoord)*math.cos(nAngleRadians)+nCurve CentreYCoord;
aCurve[nPointIndex] = {nXCoord,nYCoord};
end
return aCurve;
end

function fpEllipseCurve(nXRadius,nYRadius,nOffset)
-- Draw a 90-Degree Arc of an Ellipse offset in the Negative-Y direction by nOffset.
local nKappa = 4/3*(math.sqrt(2)-1);
local aCurve = {{0,nYRadius-nOffset},
{nXRadius*nKappa,nYRadius-nOffset},
{nXRadius,nYRadius*nKappa-nOffset},
{nXRadius,-nOffset}};
return aCurve;
end

function fpLineCurve(nStartLineXCoord,nStartLineYCoord,nEnd LineXCoord,nEndLineYCoord,nOffset)
-- Draw a Line offset in the Negative-Y direction by nOffset.
local aCurve = {{nStartLineXCoord,nStartLineYCoord-nOffset},
{nStartLineXCoord,nStartLineYCoord-nOffset},
{nEndLineXCoord,nEndLineYCoord-nOffset},
{nEndLineXCoord,nEndLineYCoord-nOffset}};
return aCurve;
end

seycyrus
July 26th, 2013, 20:17
I know what splines are and I find the mathematical information interesting, but I'm not sure exactly what this does for the user.

Edit: I'm not trying to minimize the usefulness, I honestly am not sure what what this allows.

damned
July 26th, 2013, 23:44
nothing for the user - it allows the GM to create new pointers for use within his games :)

gmkieran
July 29th, 2013, 14:33
Silly Damned! GMs are users, too! ;) Very cool, dulux! thanks for the in-depth info. will have to see if I can figure out how to make the toolkit work for me.

Cheers!
GMK

dulux-oz
July 29th, 2013, 16:18
You're welcome!

If you have any questions or what some help feel free to drop me a line.

What do people reckon - is it worth asking the FG2 Forum Moderators about making this thread "sticky"? I mean, if people think it's usesful stuff <shrug>

Or is it just my rampant ego taking over again <grin>

dr_venture
July 29th, 2013, 20:35
Hmmm... I too am unsure what the usefulness of this info is... which is an indicator of my lack of knowledge, not the usefulness of the info! :D I'm *just* getting my sea legs in FG dev and am not sure what a "pointer" is in FG terms, and how Bezier curves apply to them. Is this used for graphical output or for plotting movement paths or... ? Clearly, I have a lesson to learn here in FG dev! Thanks, dulux-oz!!!

dulux-oz
July 30th, 2013, 04:02
Hmm, OK, obviously I broke one of my own cardinal rules with this thread - "Assume No Knowledge".

A Pointer in FG2 is the Circle, Square, Cone and Arrow shapes we draw on our Images when we are gaming - for Spell Effect areas, etc. The FG2 Documentatuion calls them Pointers (under the "Maps, Images and Drawings" Heading) so I assumed that's what we all called them.

If you have any knowledge of programming you will have come across the term "pointer" before - these are a special logical construction which have NO relationship to what we are talking about here (I don't even know why I brought this up :) ).

These shapes (the Circle, Square, Cone and Arrow) are drawn on our images using mathemetatical equations which are known as Cubic Bézier Equations which, when plotted on a graph (ie drawn) form Cubic Bézier Curves. Add a series of such Curves together (following the rule in the 7th paragraph of the Background/Theory part of the 1st post) and you get a Spline - a smoothly flowing series of curves. Add several Splines together on the same drawing and, in the case of FG2, we get our Pointers.

The Toolkit simply makes drawing these Curves easier so that we can have Pointers on our Images & Maps which are not the stock-standard ones. If you are not using custom Pointers then the Toolkit will be of no use to you.

However, if you do want to use "non-standard" Pointers (as I do for my Übergame Ruleset) then instead of having to learn all the math involved and then solve the cubic equations (ie in the form of y = Ax^3 + Bx^2 + Cx + D) I've done the math for you - well, I've simplified things down so that you only have to supply a few coordinates and maybe a radius or two. I happen to be a Mathematicial as well as a senior ICT Professional, so, for me, the maths is realtively easy, but for othes it tends to be quite hard and confusing - not something that the vast majority of people know or care about.

I put the Background/Theory section in so that people could:

Do some further reaseach if they were interested.
Understand what we were actually trying to acheive.
Know the limitations of the Toolkit and how they might modify things to do things the Toolkit does not do explicitly.

My belief is that if people have as much information as possible its better than if they have little/none.

So if people find this Toolkit useful then that's great; if they don't then that's OK too. I just thought I'd help out with something that a lot of people (inside and outside the FG2 community) seem to have trouble with - God knows I've had trouble with other things in FG2 and the community has always "come to my rescue", so I thought I'd do a little "payback" as a sort of "thankyou".

Trenloe
July 30th, 2013, 19:19
Really cool - thanks for spending the time to put this detail all together.

Do you have some example screenshots of what you can do with custom pointers?

dulux-oz
July 31st, 2013, 07:10
Sceenshots... Screenshots... Not really.

I cover Maps & Images in my upcomming Tutorial Video (see the Thread "New Tutorial Videos" in the Tavern Forumn - https://www.fantasygrounds.com/forums/showthread.php?18473-New-Tutorial-Videos) and as my Übergame Ruleset uses three Custom Pointers (a 60-Degree Cone, a 120-Degree Cone and a Origin-Starting-Point Half-Wide Ellipse) we can see those ones there, but no, I don't have any (other) screenshots.

Basically, if we can draw it on paper there's a 99% chance we can draw it on-screen with the Toolkit (complex, multi-direction, multi-radii curves being the ones that may cause problems - try to break these down into simpler curves) - we've just gotta work out the starting/ending points, radii and offsets from the Origin. I suggest grabbing some graph paper and drawing your pointer on that to help work out these coordinates.

The Savage Worlds "Teardrop" Pointer/Template, for example, is simply two Lines joined by two Regular Curves - because the bottom Curve (the large one) covers more than 180-Degrees I'd draw that as two smaller Curves each starting at the Y-Axis and curving up to the left and right, and while the top Curve is less than 180-Degrees and so could be drawn "as is" by either of the Curve Functions, I'd probably draw it as two Curves as well, again, starting at the Y-Axis (actually the Origin) and curving down to the left and the right. The two LineCurves, obviously, would join these Curves.

Moon Wizard
August 1st, 2013, 01:05
The custom pointers are not often needed for ruleset development, so I think the best place for your data will be in the Library (perhaps in the Anatomy of a Ruleset).

Also, I'm hoping to get a wiki set up at some point, since there are add-ons available that can use the upgraded vBulletin login system.

Cheers,
JPG

dulux-oz
August 1st, 2013, 05:31
I was only joking JPG - well, half-joking. If you and the rest of the team want to put it in the Library or make it sticky or whatever, then thats fine with me :D

Zeus
August 4th, 2013, 10:23
dulux-oz/moon - just to clarify something. If we know the start/stop point of a pointer as well as its area coverage, it should be possible to track the image area covered by a pointer (mapped to x,y points in an image control). If this is the case then I believe this would be all that would be necessary to initiate a basic mechanism for determining Line of Sight to any targeted token (also on the same image).

I am thinking it maybe possible to attach an invisible 25-45 degree wide cone to all tokens, facing out from the current facing direction of the token. As the token rotates, so too does the LoS cone. Now to determine LoS we simply have to check to see if the x,y position of the target token falls within the LoS cone area, if it does we have LoS, if not we don't have LoS etc. etc.

dulux-oz
August 4th, 2013, 16:50
You wouldn't need to actually draw the "Invisible Cone" (although that's not a bad way to think about it) - you simple need to check if the Target coordinates were within an area defined by the coordinates defining the Cone.

The functions used to draw a Cone in the Toolkit take as arguments the Starting Coordinates of the Pointer, the Ending Coordinates of the Pointer and the Angle-Of-Arc of the Cone. This is convenient, because with these three things, plus the Coordinates of the Target, it is relatively simple to solve our problem - let me explain.

The Target lies in our Line-Of-Sight (ie within the "Invisible Cone") if:

The Target is between the two "arms" or straight-edges of our Cone, and
The distance from the Starting Point to the Target is less than or equal to the length of the Cone/Pointer.


The angle that the Pointer lies relative to an arbitrary control line is given by the math.atan2() function.
The angle that the line from the Starting Point to the Target (the TargetLine) lies relative to the arbitrary control line is also given by the math.atan2() function.
Therefore the angle that the TargetLine and the Pointer make is simple the subtraction of the two math.atan2() results.
If the absolute value (math.abs()) of the result of this subtraction is less than or equal to half of the Angle-Of-Arc then we have fulfilled Requirement 1 (half the Angle-Of-Arc because the Cone lies symmetrically on either side of the line used to define the Pointer).
If the length of the TargetLine is less than or equal to the length of the Pointer than we have fulfilled Requirement 2.
If both Requirement 1 and Requirement 2 are fulfilled (ie TRUE) the the target is within Line-Of-Sight.


Code follows (feel free to tidy-up, it's written for explanation purposes not for code/processing/memory efficiencies):


function fpLOSTrue(nStartingXCoord,nStartingYCoord,nEndingX Coord,nEndingYCoord,nTargetXCoord,nTargetYCoord,nA rcDegrees)

if nArcDegrees < 0 or


nArcDegrees > 360 then
return false;
end
local nHalfArcRadians = math.rad(nArcAngleDegrees)/2;
local nTargetLineAngleRadians = math.atan2( nTargetXCoord - nStartXCoord, nStartYCoord - nTargetYCoord );
local nPointerAngleRadians = math.atan2( nEndXCoord - nStartXCoord, nStartYCoord - nEndYCoord );
local bReq1 = ( math.abs( nPointerAngleRadians - nTargetLineAngleRadians ) <= nHalfArcRadians );
local nLengthTokenLine = math.sqrt( ( nStartingXCoord - nTargetXCoord )^2 + ( nStartingYCoord - nTargetYCoord )^2 );
local nLengthPointer = math.sqrt( ( nStartingXCoord - nEndingXCoord )^2 + ( nStartingYCoord - nEndingYCoord )^2 );
local bReq2 = ( nLengthPointer - nLengthTokenLine >= 0);
return (bReq1 and bReq2);
end

You'll note that the fpLOSTrue() function will work with any Angle-Of-Arc, includeing 0 (a Line) and 360 (a Full Circle).

Usage:
I suggest using the Target Token's Coordinates as the Target Coordinates, the "Current Token's" Coordinates as the Starting Coordinates, choose an arbitrary angle for the Angle-Of-Arc (as Übergame is built on Hex-Grids I'd personally use 60-Degrees), then the only thing to calculate is the Ending Coordinates, which would depend upon both the chosen arbitrary length of the "Invisible Cone" and the facing of the "Current Token".

Any questions please ask :)

Zeus
August 5th, 2013, 10:28
Thanks dulux-oz.

I shall have to have a play with this when I get back. Your right about not having to draw the LoS cones and thanks for the additional method, that should help get things started nicely. I'll have a think about how to best handle obstructions (walls, columns, enemies, other pc's etc. etc.), which would also need to be factored into determining LoS but thats more advanced and for later on. It could be possible to combine this functionality with the multi-layered maps functionality which could allow for a method to allow GM's to place obstruction tokens on one layer, effectively mapping the walls etc. Hmmm, intriguing possibilities arise ...

Callum
August 5th, 2013, 11:39
But even as is, this enables the drawing of a pointer shape and targeting all tokens inside it, right?

dulux-oz
August 5th, 2013, 17:39
Not quite, no.

The Pointer_Toolkit will allow us to draw a Cone Pointer based on its Starting Point and Ending Point, provided we supply the relevant function with a "hard wired" Angle-Of-Arc, which will then be drawn by FG2 for us.

The fpLOSTrue() function will allow us to determine if a Target Point lies within the area defined by a Cone with a Starting Point, Ending Point and an Angle-Of-Arc.

The Starting Points, Ending Points and Angle-Of-Arcs supplied to both Function and Toolkit COULD be the same, but there is no intruinsic relationship between the Toolkit and the fpLOSTrue() function.

The hard part is that the Starting Point, Ending Point, and Target Point need to be exposed to us by the FG2 Code (ie the FG2 Coders) so that we can use them in both the Function and/or the Toolkit - as far as the Toolkit is concerned, the Starting Point and Ending Point are exposed via the inbuilt onBuildCustomPointer() function. However, we would need to have these two Points and the Target Point exposed to us in some other function for us to be able to use them in the fpLOSTrue() function, and the to best of my knowledge that expossure function does not exist.

Until it does we cannot do automatic targeting (at least not via this menthod).

Also note that while fpLOSTrue() can be used to target along a Line, within a Cone shape of any Angle-Of-Arc (even greater than 180-Degrees) or even within a Circle, it does not and cannot work with a Square shape (the geometry formuals and logic would be different) and that the Toolkit can only draw Cones less than 180-Degrees - let me say it again - there is NO intruinsic relationship between the Toolkit and the fpLOSTrue() function.

As far as walls and other obstructions would be concerned - if the obstruction "breaks" the TargetLine then the Target would fall out of LOS. You could combine fpLOSTrue() (which returns a Boolean) with another Boolean Function that determins if the TargetLine is broken to get our result of LOS or not. If we use this idea than we would need to use more than just the CentrePoints of Tokens as, by its definition, a point has no length or width (it's a Zero-D object). You would have to have some sort of 2-D (or at least a 1-D object, like a line) breaking the TargetLine ie you would need to determin the Token's "Corners" or "Radius" (for a Square or Circular Token, respectively).

Also, you may want to consider characters/monsters (ie Tokens) as a special case, because if a party menber is standing between the Attacker and the Target but is closer to the Attacker than the Target than LOS may not exist, but if closer to the Target then LOS may occure (with a cover bonus of some sort, obviously). Also needed to be taken into accout are the relative heights of the creatures represented by the Tokens - a Halfling is not going to stop LOS on or for a Giant, for example, no matter where they stand relative to each other.

Just my $0.02 worth <WEG>

lokiare
February 7th, 2014, 11:03
Ok, so where would I put the <imagecontrol> xml stuff if I were going to create an extension with this?

Trenloe
February 7th, 2014, 11:27
Ok, so where would I put the <imagecontrol> xml stuff if I were going to create an extension with this?
If you're using a ruleset that is layered on top of CoreRPG (which 4E is) then you can add the code in an extension to the \campaign\campaign_images.xml file - the "imagewindow" windowclass contains the <imagecontrol> for maps and images.

As the ruleset modification guide mentions about extensions (https://www.fantasygrounds.com/modguide/extensions.xcp) "An extension consists of files identical to those found in a ruleset" so your quickest route would be to copy the \campaign\campaign_images.xml file from CoreRPG into your extension, add whatever is required to the <imagecontrol> entry in this XML and add this as an <includefile> entry in <base> in your extensions extension.xml file.

Or, you could work out just what you need to add specific to the XML and use the "merge" tag in a custom XML file in your extension - more info on "merge" here: https://www.fantasygrounds.com/forums/showthread.php?19530-Ruleset-layering-summary

It will probably be quicker for your development to work off a complete copy of campaign_images.xml first and get your extension working - then look to implement just the stuff you changed with the "merge" functionality - this will make it more future proof.

lokiare
February 7th, 2014, 12:23
If you're using a ruleset that is layered on top of CoreRPG (which 4E is) then you can add the code in an extension to the \campaign\campaign_images.xml file - the "imagewindow" windowclass contains the <imagecontrol> for maps and images.

As the ruleset modification guide mentions about extensions (https://www.fantasygrounds.com/modguide/extensions.xcp) "An extension consists of files identical to those found in a ruleset" so your quickest route would be to copy the \campaign\campaign_images.xml file from CoreRPG into your extension, add whatever is required to the <imagecontrol> entry in this XML and add this as an <includefile> entry in <base> in your extensions extension.xml file.

Or, you could work out just what you need to add specific to the XML and use the "merge" tag in a custom XML file in your extension - more info on "merge" here: https://www.fantasygrounds.com/forums/showthread.php?19530-Ruleset-layering-summary

It will probably be quicker for your development to work off a complete copy of campaign_images.xml first and get your extension working - then look to implement just the stuff you changed with the "merge" functionality - this will make it more future proof.

Thanks it worked, now I just need to finish implementing the code in this tutorial to make it an actual square.

lokiare
February 7th, 2014, 12:35
So uh. Don't shoot the noob, but how and where do I tie the onBuildCustomPointer function to the context menu selection I just made?

dulux-oz
February 7th, 2014, 13:34
So uh. Don't shoot the noob, but how and where do I tie the onBuildCustomPointer function to the context menu selection I just made?

As a Script entry in your Imagecontrol code (which you've already done) ie



<imagecontrol name="image">
<script file="pointer_toolkit.lua" />
<pointertypes>
<arrow>
<icon>pointer</icon>
<label>Draw Arrow</label>
</arrow>
<square>
<icon>pointer_square</icon>
<label>Draw Square</label>
</square>
<circle>
<icon>pointer_circle</icon>
<label>Draw Circle</label>
</circle>
<cone>
<icon>pointer_cone</icon>
<label>Draw Cone</label>
</cone>
<custom name="Zone">
<icon>pointer_square</icon>
<label>Draw a Zone that can have effects applied to it.</label>
</custom>
</pointertypes>
</imagecontrol>


onBuildCustomPointer is one of those functions that, if it exists in your code, will be found by FG automatically and used ("tied in" to use your term). Of course, if you've re-written the onBuildCustomPointer from the toolkit then you'll have to reference the new version in a <script> tag.

Cheers

lokiare
February 7th, 2014, 22:48
As a Script entry in your Imagecontrol code (which you've already done) ie



<imagecontrol name="image">
<script file="pointer_toolkit.lua" />
<pointertypes>
<arrow>
<icon>pointer</icon>
<label>Draw Arrow</label>
</arrow>
<square>
<icon>pointer_square</icon>
<label>Draw Square</label>
</square>
<circle>
<icon>pointer_circle</icon>
<label>Draw Circle</label>
</circle>
<cone>
<icon>pointer_cone</icon>
<label>Draw Cone</label>
</cone>
<custom name="Zone">
<icon>pointer_square</icon>
<label>Draw a Zone that can have effects applied to it.</label>
</custom>
</pointertypes>
</imagecontrol>


onBuildCustomPointer is one of those functions that, if it exists in your code, will be found by FG automatically and used ("tied in" to use your term). Of course, if you've re-written the onBuildCustomPointer from the toolkit then you'll have to reference the new version in a <script> tag.

Cheers

I've done that, but how do I select which shape it will make? How do I pass the 'box' type to the function?

dulux-oz
February 8th, 2014, 04:47
OK, so now I've got to ask: Have you actually READ the documentation and the samples code that I included with the Toolkit Posts, or did you just scan-read it and try to use the Toolkit "as is"?

The Toolkit is NOT meant to be used "as is" (unless you happen to need a Pointer that the Toolkit already defines AS AN EXAMPLE) - its meant to be used as the basis or template FOR YOUR OWN VERSION OF THE CODE. This is quite clearly explained in the original posts and is also obvious from a reading of the CODE.

I suggest you go back and have a (re-)read of the code and the documentation (in particular Part 3 and the onBuildCustomPointer function Code). ;)

Cheers

lokiare
February 8th, 2014, 04:52
OK, so now I've got to ask: Have you actually READ the documentation and the samples code that I included with the Toolkit Posts, or did you just scan-read it and try to use the Toolkit "as is"?

The Toolkit is NOT meant to be used "as is" (unless you happen to need a Pointer that the Toolkit already defines AS AN EXAMPLE) - its meant to be used as the basis or template FOR YOUR OWN VERSION OF THE CODE. This is quite clearly explained in the original posts and is also obvious from a reading of the CODE.

I suggest you go back and have a (re-)read of the code and the documentation (in particular Part 3 and the onBuildCustomPointer function Code). ;)

Cheers

Part of the problem is I'm new to coding in Lua. Some more of the problem is that I'm new to programming in Fantasy Grounds. For the most part though there isn't an easy to follow tutorial for this kind of stuff, just a lot of high level information that assumes you know Lua and the structure of how Fantasy Grounds works internally. Sorry if I'm bugging you.

lokiare
February 8th, 2014, 05:05
I reread it and somehow my sleepless addled brain missed this line: "Each can be used as is by setting the "name" attribute of the Custom tag in the ImageControl to the given name shown. The onBuildCustomPointer() function also does the final rotation and transposition of the given Pointer." Thanks for being patient.

It works great now. Ok, so now I need to read through and find out which one is the center point and which is the outside point so I can calculate what is inside it.

dulux-oz
February 8th, 2014, 05:26
You're not bugging me - its simply a fact that, as you state it one of your posts, you're a programmer, and as I am a programmer and as I Project Manager other programmers I've gotten used to people (ie programmers) acting in a certain way - one of those ways is that programmers TEND to not RTFM but instead scan-read the documentation and then try to implement things - partly this is laziness (and aren't we all a little bit lazy) and partly it is the natural desire to get "stuck in". Its not that you're bugging me, its that I have a personal bugbear about people not RTFMing.

I understand and appreciate that Lua and the XML "stuff" of how FG is put together is complex and difficult, especially for someone new to the game, but as a programmer one of the skills you should have been taught is how to read someone's code and, in effect, "reverse engineer" how the login, etc, (the underlying algorithm) fits together. As for being new to Lua, when I wrote the Toolkit so was I - I tell my students (I'm also a Tutor at the Queensland University of Technology) that writing in any given language is the easy part of coding, its writing the algorithm that's hard. If you can write the algorithm correctly than translating it into ANY coding language (from 1st generation assembly to 4th generation C-variants) is child's play.

OK, I'll climb down off my soapbox now :p

From Part 3 (emphasis added by me): :)


Finally, I've included a sample onBuildCustomPointer() function with eleven sample Custom Pointers to demonstrate some ways to use each of the Curve Definition Functions. Each can be used as is by setting the "name" attribute of the Custom tag in the ImageControl to the given name shown.

So, you have to take the SAMPLE onBuildCustomPointer() function and add it a bit to recognise your new custom-Pointer type and then call one or more of the four shape functions from the top of Part 3 (or write a new shape function) to draw your new custom-Pointer. The fact that onBuildCustomPointer() is called automatically if it exists is part of the built-in functionality of FG, as described in the Ruleset Development Documentation in the Library on the FG Website.

OK, hope that helps :)

Cheers

lokiare
February 8th, 2014, 07:07
I reread it and somehow my sleepless addled brain missed this line: "Each can be used as is by setting the "name" attribute of the Custom tag in the ImageControl to the given name shown. The onBuildCustomPointer() function also does the final rotation and transposition of the given Pointer." Thanks for being patient.

It works great now. Ok, so now I need to read through and find out which one is the center point and which is the outside point so I can calculate what is inside it.

I am running into a weird problem. I've got:



<imagecontrol name="image">
<script name="PointerToolkit" file="scripts/pointer_toolkit.lua" />
<pointertypes>
...
</pointertypes>
</script>
</imagecontrol>


The guide says:



Script packages are globally accessible generic script constructs similar to Lua standard library packages. Their operation is detailed on the reference page script.

They can be accessed from other script blocks by name, which must be defined as the "name" attribute to the <script> tag. Specifying the name is not mandatory - if it is omitted, the contents of the script can still be executed using the onInit function.


I'm trying to access global variables I added to your script file in another script file. Here is how I declared them (and this might be a failing of my understanding of Lua):


...
zones = {};
zoneCount = 0;

function onBuildCustomPointer(nStartXCoord,nStartYCoord,nEn dXCoord,nEndYCoord,sPointerType)
...

Here is how I'm trying to access them in my own Lua script file in the same extension:


if PointerToolkit[zones] then
msg.text = msg.text .. ". Found zones.";
for key, value in ipairs(PointerToolkit[zones]) do
if value then
msg.text = msg.text .. ". Found a value of zones.";
if value[curves] then
msg.text = msg.text .. value[curves];
end
end
end
end


I've also tried to access them with:


PointerToolkit.zones

I get the error:


Script Error: [string "scripts/zone_manager.lua"]:28: attempt to index global 'PointerToolkit' (a nil value)

So what am I doing wrong?

lokiare
February 8th, 2014, 07:08
I guess the easy solution would be just to migrate your code to my script file, but this is the kind of stuff that I need to learn anyway.

dulux-oz
February 8th, 2014, 07:24
I'm not sure (one of the other coders can confirm or deny this - I hope) but I think the <script> tag needs to be outside of the <imagecontrol> tags at the top level of the tag hierarchy, just below the <root> level of a file.

The Inside Point is the StartCoords and the Outside Point are the EndCoords - plus some math ie those two sets of Coordinates give the "radius" of the shape - for a Square that would be half the length of a side from the Centre to the side's Midpoint.

Oh and it should be PointerToolkit.zones

Trenloe
February 8th, 2014, 14:00
The guide says:

Script packages are globally accessible generic script constructs similar to Lua standard library packages. Their operation is detailed on the reference page script.

They can be accessed from other script blocks by name, which must be defined as the "name" attribute to the <script> tag. Specifying the name is not mandatory - if it is omitted, the contents of the script can still be executed using the onInit function.
You were very close to the right section - one sections up in "Script blocks in controls" is where you need to be looking when you use:

<imagecontrol name="image">
<script name="PointerToolkit" file="scripts/pointer_toolkit.lua" />
<pointertypes>
...
</pointertypes>
</script>
</imagecontrol>
You can use <windowname>.<controlname>.<function name> to run the function in the script file attached to a control. In your example, to access the zones tables, you might use (substituting <windowname> with the name of the window:

<windowname>.image.zones
You can also use the variable window that points directly to the window environment of the control (essentially allows you to access the <windowname> entry without knowing it). See "script block scope" in this page: https://www.fantasygrounds.com/modguide/scripting.xcp

But - this is not the way to use it- as there is already a script file attached to the "image" imagecontrol in CoreRPG (asssuming you're building this extension on top of CoreRPG):

<imagecontrol name="image">
<bounds>21,58,-27,-29</bounds>
<indicators availability="image_sent" locked="image_locked" loading="image_loading" zoom="image_zoom" shortcuts="image_shortcuts" />
<shortcut icon="image_pin" hotspot="2,21" />
<default snap="on" drawingsize="500,500" />
<marginx>48</marginx>
<marginy>87</marginy>
<script file="campaign/scripts/image.lua" />
</imagecontrol>
You can't merge script file entries, and you can't give them a name within the control to differentiate them.

So, for your extensions you'll probably need to take the CoreRPG script campaign/scripts/image.lua and put it in your extension (in the exact same location \campaign\scripts) and then add your additional code to this script. This should work OK - with the main problem being that future releases of CoreRPG that change campaign/scripts/image.lua will necessitate your extension being updated with the new code from campaign/scripts/image.lua in CoreRPG.

Or, you might be able to come up with a fancy way of referencing the data you need in a script package. Look in the CoreRPG base.xml - you can see the <script> definitions of LUA files here. e.g.:

<script name="OOBManager" file="scripts/manager_oob.lua" />
You can then reference any function in scripts/manager_oob.lua by using OOBManager.<function name>.

You can use the <script name="XXXXX"> tag in an extension base.xml too. You can't use use it within a control definition as this would not have a global context.

For example, you could have in your extension base.xml file:


<script name="ZonedExtensionManager" file="scripts/manager_zone_extension.lua" />

Then you could access anything in this LUA file by using ZonedExtensionManager.<function or variable name>. When this LUA file is setup (the onInit() function will run) the whole variable space for ZonedExtensionManager and you can store variables in the script block scope. This takes a bit of getting used to, but it does allow you to define setXXX, getXXX, registerXXX functions that are accessible outside of the script block scope - which can be used in a dynamic environment such as what you are trying to do with your zone extension. Or you could have certain functions within this ZonedExtensionManager that would take control parameters sent from a control based script and return the info needed. This would allow you to have minimal changes in your extension campaign/scripts/image.lua file - hence minimising any changes needed in the future if CoreRPG changed.

Hope the above makes some sense - it takes a while to get used to the script scope and how to make best use of things.

EDIT: As an example, take a look at the Enhanced Images extension: https://www.fantasygrounds.com/forums/showthread.php?20231-Enhanced-Images-%28layers%29-for-FG-3-0-CoreRPG-%28and-rulesets-based-on-CoreRPG%29 This takes the CoreRPG \campaign\campaign_images.xml and modifies it - and also overwrites \campaign\scripts\image.lua with \scripts\image.lua - which is stored against the image control. Both of these files are copied from the CoreRPG ruleset and then modified specifically for the extension - therefore the extension will have to be updated with changes to these files in future releases of CoreRPG.

Trenloe
February 8th, 2014, 14:03
I'm not sure (one of the other coders can confirm or deny this - I hope) but I think the <script> tag needs to be outside of the <imagecontrol> tags at the top level of the tag hierarchy, just below the <root> level of a file.
Yeah, you can't have a <script name="XXX"...> tag within a control or window class and then access it through the name XXX - it is usually defined in base.xml of the Ruleset or extension. But it could be at the root level of any XML file - doing it this way is not recommended as the definition would be hard to find.

dulux-oz
February 8th, 2014, 14:27
Yeah, that's what I thought - thanks for confirming.

lokiare
February 9th, 2014, 06:23
I'm getting weird results. If I put it in 'extension.xml' <base> then the onBuildCustomPointer is never called. In order to have it called I have to put it in campaign_images.xml inside <windowclass><imagecontrol>, but if I put it in there I have no clue the path to access it in the lua script. I've tried the names of the windowclass and imagecontrol and I get "nil" errors. I can share the code if anyone is curios.

What would be helpful is a function that I could call that would tell me what lua package I'm in when I call onBuildCustomPointer so I could use it to know how to access my variables in other scripts. I now realize I need both files separate so that it will call the onBuildCustomPointer when the zone pointer is created and I have the end of turn and start of turn callbacks in the <base> lua file that I included.

What I think is happening is it's being called in the instance of the imagewindow.image so I would need the exact instance. A work around would be to put my variables in a global table that could be accessed from anywhere, but how would I do that and what table should I use?

Edit: Ok after reading all the posts I missed in this thread I decided to put the global variables in my script file zone_manager.lua that is added in extension.xml <base>. Then I access them with my script name of ZoneManager in the Pointer_Toolkit.lua file. This seems to work.

My new problem is when I create a zone pointer the onBuildCustomPointer function gets called about 30 times. So I need to figure out what's going on with that but I think I've got it working to where I can figure things out. Thanks for all the help.

lokiare
February 9th, 2014, 07:50
My new problem is I don't know how to iterate over the aShapeCurves in the onBuildCustomPointer function. I'm using:



if zones[i].curves then
for nIndex, aCurve in ipairs(zones[i].curves) do
msg.text = msg.text .. " Curves[" .. nIndex .. "]:"
for nPointIndex, aPoint in ipairs(aCurve) do
msg.text = msg.text .. " aCurve[" .. nPointIndex .. "] x:" .. aPoint[0] .. " y:" .. aPoint[1];
end
end
else
msg.text = msg.text .. " curves not found. ";
end


Strangely enough I don't get "curves not found."

I assign the aShapeCurves to zones[i].curves here:



aZone = {curves = {aShapCurves}, effect = 'none'};

-- I've also tried:

aZone = {curves = aShapeCurves, effect = 'none'};

ZoneManager.tempZones[ZoneManager.tempZoneCount] = aZone;


The first "aZone =" line doesn't trigger the 'curves not found' message, but the second does. Am I assigning it wrong, or am I accessing it wrong?

Edit: The output is either blank or the 'curves not found' message. I use the Comm.deliverMessage to send the msg.text to the chat window.

Trenloe
February 9th, 2014, 12:10
I'm getting weird results. If I put it in 'extension.xml' <base> then the onBuildCustomPointer is never called. In order to have it called I have to put it in campaign_images.xml inside <windowclass><imagecontrol>
The thing to remember about any onXXXXX function is that the function is triggered by an event happening within Fantasy Grounds usually in an element, object or package. You need to have this code within the element or object where the event will occur. As the onBuildCustomPointer event is an event that is triggered within an <imagecontrol> element, you must have the event handler code "function onBuildCustomPointer..." within the script environment of the <imagecontrol>.

lokiare
February 11th, 2014, 00:59
The thing to remember about any onXXXXX function is that the function is triggered by an event happening within Fantasy Grounds usually in an element, object or package. You need to have this code within the element or object where the event will occur. As the onBuildCustomPointer event is an event that is triggered within an <imagecontrol> element, you must have the event handler code "function onBuildCustomPointer..." within the script environment of the <imagecontrol>.

I figured that. I worked around it by putting my global variables in my main lua file that I reference in extension.xml <base>. My real problem is understanding the structure of the aShapeCurves variable table.

dulux-oz
February 11th, 2014, 02:40
aCurves (a = Array, or Table in Lua) is a four row, two column array. Each row is one point on the image, with the first column holding the the X-Coordinates and the 2nd column holding the Y-Coordinates. Thus, each aCurve represents the four points needed to draw our B-Curve.

aShapeCurves is simply a 1-diensional array of aCurves ie a list (table) of the various B-Curves to draw to make up our shape/pointer.

Remember: a Line is just a special type of Curve.

That help?

Cheers

lokiare
February 11th, 2014, 06:00
aCurves (a = Array, or Table in Lua) is a four row, two column array. Each row is one point on the image, with the first column holding the the X-Coordinates and the 2nd column holding the Y-Coordinates. Thus, each aCurve represents the four points needed to draw our B-Curve.

aShapeCurves is simply a 1-diensional array of aCurves ie a list (table) of the various B-Curves to draw to make up our shape/pointer.

Remember: a Line is just a special type of Curve.

That help?

Cheers

So I should be able to access the first curve in it using something like:



x = aShapeCurves[0][0];
y = aShapeCurves[0][1];


I'll try it out and get back to you.

Edit: Nope, didn't work.

I assign aShapeCurves to my table via:



aZone = {curves = aShapeCurves, effect = 'none'};
ZoneManager.tempZones[ZoneManager.tempZoneCount] = aZone;


Where ZoneManager is the name of my lua script package I load in the <base> section of the extension.xml file. the last tempZone is copied to my zones table here in onDragEnd:



ZoneManager.zones[ZoneManager.zoneCount] = ZoneManager.tempZones[ZoneManager.tempZoneCount-1];


Where tempZoneCount is a count I keep each time I added to tempZones. I iterated tempZoneCount after I finished adding to tempZones, thus the -1.

So I should be able to access it using the above, but it gives me 'nil' errors.

Edit2: Ok I seem to be able to access the values, but now I need to read through this thread to figure out what they mean. The code I used to access them is below:



if zones[i].curves then
for nIndex,aCurve in ipairs(zones[i].curves) do
if aCurve then
for nPointIndex,aPoint in ipairs(aCurve) do
if aPoint then
for nCoordIndex,aCoord in ipairs(aPoint) do
-- Everyone other one of these is x and y.
end
end
end
end
end
end


Where zones[i].curves is the aShapeCurves table.

Trenloe
February 11th, 2014, 07:39
So I should be able to access the first curve in it using something like:



x = aShapeCurves[0][0];
y = aShapeCurves[0][1];


I'll try it out and get back to you.

Edit: Nope, didn't work.
LUA tables use 1 as the first entry in a table, not 0. See info here, especially the "Tables as arrays" section: https://lua-users.org/wiki/TablesTutorial

lokiare
February 11th, 2014, 09:34
LUA tables use 1 as the first entry in a table, not 0. See info here, especially the "Tables as arrays" section: https://lua-users.org/wiki/TablesTutorial

Thanks I realized that and can now tell when a token whose active turn it is, is within the zone area. I just need to work the rest of the program out and I've got what I need

ShotGun Jolly
May 2nd, 2014, 17:56
Wow, this whole thread has been an interesting read, what a boat load of info!! Thanks for taking all the time to add the details.

I have been spending considerable time reading all of these posts for the purpose of trying to create a 30 degree cone..

I am not a programmer, I do not know LUA or XML. Other then the fact it looks kinda like HTML.. and I do not know HTML either. :o
The only way I have been modifying rulesets, is finding something similar to what I want in function and then copy paste, find examples and force things into where I need them to go. And with the few things over the years I needed to add to my games I have been pretty lucky with adding stuff. But unfortunately I just cant seem to find what I am looking for here in these posts to help me figure out what I need to do.. As there is way to much info for someone on my "I just want to make a 30 degree cone to use in my games" level of understanding with coding to benefit from it.

I wanted to take the 60 degree cone and insert it, and make what ever changes I need to make, to turn it into 30 degree cone. But I cant seem to understand where all the code that is given in the above posts need to fit.
Anyone able to assist me with the basic frame work on what I need to change/add to create this elusive pointer I am trying to make?

Blackfoot
May 2nd, 2014, 19:35
It would be nice if some of this could get built into CoreRPG.. at least the basics.
Non-90 degree Cones are required for Hex Maps.. which are part of the Core FG environment.

dulux-oz
May 3rd, 2014, 00:34
It would be nice if some of this could get built into CoreRPG.. at least the basics.
Non-90 degree Cones are required for Hex Maps.. which are part of the Core FG environment.

This is built into the CoreRPG Ruleset - where do you think the <imagecontrol><pointertypes> tag and the onBuildCustomPointer() function exists? :p

If, on the other-hand, you are referring to an inbuilt 60-Degree Cone, then yeah, it would be nice, but then where do you draw the line? Moon can't put it every possible pointer shape - for starters the Radial Menu only has room for 5. An argument can be made for a 45-degree Cone, a 60-degree Cone, a Hex Pointer, a 120-degree Cone, etc, etc, etc. At least we have the ability to do it ourselves (and, hopefully, with my ToolKit it makes things just a little bit easier) :)

Cheers

Blackfoot
May 3rd, 2014, 00:48
I'm not suggesting every possible pointer... just some of the basics... particularly for HEX maps where the existing 'cone' pointer doesn't work... since HEX maps are a part of basic FG functionality. I think all I'm really suggesting is 1 alternate cone... the 1 2 3 4 5 6 one. Is that 60 degrees? Probably since there are 6 60s in 360 and 6 sides to a HEX. I have already posted this suggestion (or something like it) on MW's idea.informer... so hopefully we will see some love for HEXes at some point in the future. It's nice that your work might make it easier for him to implement for everyone.

Question: Does your thing handle the underlay as well as the lines?

dulux-oz
May 3rd, 2014, 01:19
Wow, this whole thread has been an interesting read, what a boat load of info!! Thanks for taking all the time to add the details.

I have been spending considerable time reading all of these posts for the purpose of trying to create a 30 degree cone..

I am not a programmer, I do not know LUA or XML. Other then the fact it looks kinda like HTML.. and I do not know HTML either. :o
The only way I have been modifying rulesets, is finding something similar to what I want in function and then copy paste, find examples and force things into where I need them to go. And with the few things over the years I needed to add to my games I have been pretty lucky with adding stuff. But unfortunately I just cant seem to find what I am looking for here in these posts to help me figure out what I need to do.. As there is way to much info for someone on my "I just want to make a 30 degree cone to use in my games" level of understanding with coding to benefit from it.

I wanted to take the 60 degree cone and insert it, and make what ever changes I need to make, to turn it into 30 degree cone. But I cant seem to understand where all the code that is given in the above posts need to fit.
Anyone able to assist me with the basic frame work on what I need to change/add to create this elusive pointer I am trying to make?

OK, lets take things one at a time.

First, creating or modifying a Ruleset is a BIG job. Some of the skills required are, basically, how to be a programmer and knowing XML and LUA. I know you've said you aren't, but I'll let you in on a secret - the way you "find and copy things" is how most programmers do it; its how we learn - so you are a programmer, or at least, you're on the way to becoming one.

Second, this thread got a little bit off topic after a while - the core of it is in the first 12 posts. Posts 15-19 deal with a related discussion on the possibilities of using the Toolkit to implement Line-Of-Sight Targeting and how Zeus could go about doing that. Posts 20-43 are all about one user's attempts to try to make the Toolkit work for them to do what it realy wasn't designed to do and should really be in a separate thread (is that possible to set up, Moon?).

Third, to try to answer you questions, to create a 30-Degree Cone you need to do 3 things:

Define the 30-Degree Cone in a script file (an LUA file), either inside a function called onBuildCustomPointer() or in its own function - its own function is probably best.
In the same script file write a function called onBuildCustomPointer() which calls the defined 30-Degree Cone. This ties the 30-Degree Cone to the appropriate systems within FG.
Tell FG to use the defined 30-Degree Cone. We do by using the <pointertypes> tag inside an <imagecontrol>.


OK, so lets take each one.

The fpConePointer() function from the Toolkit has done all the hard work in defining a cone of any angle from 1-Degree to 179-Degrees, so simple copy the entire function to your new script file - let's call the new script file "My_Cone.lua".

Next, if we take a look at the sample onBuildCustomPointer() function from post 3 we can see that we can call the fpConePointer() function with the following code:


elseif sPointerType == "60ConePointer" then
fpConePointer(aShapeCurves,nLength,60);

This calls the fpConePointer() function and draws a 60-Degree Cone when the shape known as "60ConePointer" is selected from the Image's Radial Menu. If you look carefully you will see that the last argument to the function in the number 60. if we change that to the number 30 we'll get a 30-Degree Cone instead (this is explained in the post). Also, let's name our 30-Degree Pointer "30ConePointer". Assuming we don't want to use any other Custom Pointers, the onBuildCustomPointer() function can be written into our "My_Cone.lua" file as follows:


function onBuildCustomPointer(nStartXCoord,nStartYCoord,nEn dXCoord,nEndYCoord,sPointerType)
local nLength = math.sqrt((nEndXCoord-nStartXCoord)^2+(nEndYCoord-nStartYCoord)^2);
if nLength == 0 then
return
end
local aShapeCurves = {};
local aDistLabelPosition = {25,25};
local bDrawArrow = false;
local nAngleRadians = math.atan2(nEndXCoord-nStartXCoord,nStartYCoord-nEndYCoord);
-- Call the relevant Pointer Definition Function
if sPointerType == "30ConePointer" then
fpConePointer(aShapeCurves,nLength,30);
end
-- Rotate and Position the Pointer
for nIndex,aCurve in ipairs(aShapeCurves) do
for nPointIndex,aPoint in ipairs(aCurve) do
local nXCoord = aPoint[1]*math.cos(nAngleRadians)-aPoint[2]*math.sin(nAngleRadians)+nStartXCoord;
local nYCoord = aPoint[1]*math.sin(nAngleRadians)+aPoint[2]*math.cos(nAngleRadians)+nStartYCoord;
aCurve[nPointIndex] = {nXCoord,nYCoord};
end
end
return aShapeCurves,aDistLabelPosition,bDrawArrow;
end


Finally, we need to tell FG to use our "30ConePointer". From the very first few lines of post 2 we know that we have to add a <pointertypes> to the <imagecontrol>. We also need to tell FG to use our script file. The code below shows how this is done (without using any of the Default Pointers - to use the Default Pointers as well, re-read Post 2):


<imagecontrol>
<script file="Pointer_Toolkit.lua" />
<pointertypes>
<custom name="30ConePointer">
<icon>pointer_cone</icon>
<label>Draw a 30-Degree Cone</label>
</custom>
</pointertypes>
-- Plus the rest of the existing imagecontrol code
</imagecontrol>


In the CoreRPG the <imagecontrol> tag can be found in the ./campaign/campaign_images.xml file (line 28) - this is the main <imagecontrol> for drawing Images and Maps. The <imagecontrol> on the PartySheet is in the ./ps/ps_order.xml file (line 40).

Any questions, just ask.

Cheers

dulux-oz
May 3rd, 2014, 01:25
I'm not suggesting every possible pointer... just some of the basics... particularly for HEX maps where the existing 'cone' pointer doesn't work... since HEX maps are a part of basic FG functionality. I think all I'm really suggesting is 1 alternate cone... the 1 2 3 4 5 6 one. Is that 60 degrees? Probably since there are 6 60s in 360 and 6 sides to a HEX. I have already posted this suggestion (or something like it) on MW's idea.informer... so hopefully we will see some love for HEXes at some point in the future. It's nice that your work might make it easier for him to implement for everyone.

Question: Does your thing handle the underlay as well as the lines?

Yes, we are talking about the 60-Degree Cone.

My point was that arguments can be made for including all sorts of different shapes as Pointers, so which ones should Moon do - no matter which ones he chooses, someone will say "But what about...?". How do we define which ones are "just the basics"? I'm sure the SW guys will insist that the Teardrop Point is "just one of the basics".

I agree I'd love to see more support how Hexes. My Übergame Ruleset is built around Hexes, not Squares, which is why I went and built the Toolkit in the first place - I needed 60- and 120-Degree Cones, and FG didn't have them.

Unfortunate, as I understand your question, no. The undelay doesn't work for Hexes at all (in FG) and the Toolkit doesn't go anywhere near that "stuff".

Cheers

ShotGun Jolly
May 3rd, 2014, 14:20
Thanks Dulux!!

That's a huge help.. ill pick away at it now and see how it goes. Thanks for the effort in the explanations!

ShotGun Jolly
May 3rd, 2014, 15:37
Ok, I got good news and bad news..

Good news, your instructions helped me, and I got my 30* Cone.
Bad news, I cant verify it cause I can only see the start point and the end point. As per the pic below.
6406

As you can all see, you can see all 4 of my pointers. The Arrow, Circle, Square, and my 30* cone (which is really just to the side of the token, you can see the "2m" at the start point. It has no lines)
So based on what I have learned. (lets see if I am right) The default cones are working fine, as they are defined by default. But as for my cone, the points are there, but I need to give the cone an outline and I would need to define that outline in the My_Cone.lua (correct?)

So, I have not had the time to look yet but I am going to assume, that it is there somewhere in this thread, I just need to see if I can find that example.

Am I on the right track?

Trenloe
May 3rd, 2014, 16:16
I think the issue is with dulux_oz's otherwise excellent instructions. He says "onBuildCustomPointer() function can be written into our "My_Cone.lua"" - this is actually not quite correct. In order for the onBuildCustomPointer() function to be triggered (it is actually an event handler that responds to the event of the user drawing a pointer) it must be in the script file that is linked to the image control. In CoreRPG this is campaign/scripts/image.lua as this is the script assigned to the image control (see line 35 of campaign\campaign_images.xml where this is defined).

If the onBuildCustomPointer() event handler is not in a script file directly linked to an image control then the event will not trigger - which I think is what you're seeing.

ShotGun Jolly
May 3rd, 2014, 16:55
kinda lost me..

Simply due to the fact that I see where it was defined, as you said it would be found in the image.xml file. But in the scripts folder there is no actual image.lua Unless I am looking for the image.lua in the wrong place.

Ill keep picking away at it for now..

dulux-oz
May 3rd, 2014, 16:59
<imagecontrol>
<script file="Pointer_Toolkit.lua" />
<pointertypes>
<custom name="30ConePointer">
<icon>pointer_cone</icon>
<label>Draw a 30-Degree Cone</label>
</custom>
</pointertypes>
-- Plus the rest of the existing imagecontrol code
</imagecontrol>



Sorry, that piece of code should have read:


<imagecontrol>
<script file="My_Cone.lua" />
<pointertypes>
<custom name="30ConePointer">
<icon>pointer_cone</icon>
<label>Draw a 30-Degree Cone</label>
</custom>
</pointertypes>
-- Plus the rest of the existing imagecontrol code
</imagecontrol>


I was in a hurry and did a straight copy-and-past and forgot to change the relevant line :o

Cheers

ShotGun Jolly
May 3rd, 2014, 17:17
No worries, I figured that when I originally read it. So your second statement is what I currently already have and I still have the same problem..

Trenloe
May 3rd, 2014, 18:11
But in the scripts folder there is no actual image.lua Unless I am looking for the image.lua in the wrong place.
campaign/scripts/image.lua in CoreRPG.

ShotGun Jolly
May 4th, 2014, 00:00
Yeah, I am trying to mod the Dark Heresy.pak.

It says its based on 3.5 and Core.. so maybe I need to look where it would be stashed in a stock 3.5 ruleset? Cause the current pak does not contain any image.lua


****EDIT*****

Oh wait, it is actually referring to the CORE Ruleset!!! I got ya now!

dulux-oz
May 4th, 2014, 15:58
If the onBuildCustomPointer() event handler is not in a script file directly linked to an image control then the event will not trigger - which I think is what you're seeing.

OK, let me qualify - if you are using a Ruleset based on another Ruleset (ie the CoreRPG), then the <imagecontrol> will/may already have an LUA script file associated with, in which case you should probably add the contents of our "My_Cone.lua" file to the (already attached) <imagecontrol> script file, as per the standard "best practice" for using the Cascading or Layered Ruleset Model.

My apologises for making the assumption that this was already understood - broke my own Rule again: "Assume No Knowledge" :)

ShotGun Jolly
May 6th, 2014, 01:29
No worries, your instructions are pretty darn good!

I only found out that the DH ruleset was linked to the Core ruleset when I left the core extension as .zip and not .pak. I am guessing, but I am assuming that the darkhearsy may be using parts of Core to run. Which if I am right, is a pretty cool discovery for me!

Sad thing is, what ever the issue is, I have not been able to make any more head way with the problem with the cone now actually showing on the map. I spent the last few days now cutting and pasting and I can not seem to get it to work.

dulux-oz
May 6th, 2014, 04:15
No worries, your instructions are pretty darn good!

I only found out that the DH ruleset was linked to the Core ruleset when I left the core extension as .zip and not .pak. I am guessing, but I am assuming that the darkhearsy may be using parts of Core to run. Which if I am right, is a pretty cool discovery for me!

Yeah, its a new system that the SmiteWorks Boys brought in in version 3 - Its called Cascading Rulesets or Layered Rulesets. The idea is that all the common "stuff" that every Ruleset uses (the Notes, the ChatBox, etc) is located in a common or "Core" Ruleset and that more detail Rulesets can use the Core Ruleset (called CoreRPG) as the foundation to build on so that they don't have to "reinvent the wheel". Rulesets can actually be layerted on top of any other Ruleset, not just the CoreRPG - an example are my Rulesets: I have a Ruleset called MJBCore which sits on top of the CoreRPG which contains all the "stuff" I want common to all my other Rulsets (things like my Locations and Alternate ColourGizmo Extensions which are actually part of the MJBCore Ruleset, plus a different set of "default" graphics). My Übergane Ruleset then sits on top of the MJBCore, as does my DragonWarriors Ruleset, as will my Shadowrun_v3 Ruleset (maybe).

You can tell if a given Ruleset is layered on another because the topmost Ruleset refers to the next one down in the layer via this XML tag: <importruleset source="CoreRPG" />, which is found in the "base.xml" file of every (relevant) Ruleset.

The issue is is that not all Rulesets follow this new Cascading/Layered Ruleset Model - a lot of the pre-v3.x Rulesets don't. The Savage Worlds Ruleset, for example, doesn't follow this model (yet), but the community author(s) of that Ruleset are currently updating it so that it does. Other Rulesets may or may not be updated as the individual authors decide.

So the only way to tell if a Ruleset is Layered or not is to look at the code, and the called Ruleset may also be Layered itself - there is no way of knowing how many Layers are in the stack without starting at the topmost and "drilling down" until you hit the CoreRPG or whichever Ruleset is at the bottom of the stack.

So... in your case, I believe that the Ruleset stack consists of the v3DnD Ruleset which is layered on top of the CoreRPG (I believe) - which means that you're going to have to check both Rulesets' <imagecontrol> to see which scripts, etc, are being used and try to work out how they are interacting with each other.

Cheers

ShotGun Jolly
May 20th, 2014, 03:19
I have been searching for a week, trying to find where that code goes. No luck.. anyone out there willing to assist me in finding where I need to insert the code to allow me to see that cone?

dulux-oz
May 20th, 2014, 04:33
I have been searching for a week, trying to find where that code goes. No luck.. anyone out there willing to assist me in finding where I need to insert the code to allow me to see that cone?

OK, to link everything together you need to put the following code into an imagecontrol:

<imagecontrol>
<pointertypes>
<custom name="45Cone">
<icon>pointer_cone</icon>
<label>Draw a 45-Degree Cone</label>
</custom>
</pointertypes>
</imagecontrol>

This is explained in the 2nd Post. The imagecontrol we (normally) use is the main one, located in the campaign_images.xml file in the CoreRPG Ruleset in the Campaign Folder (it starts at line 28). If you are using another Ruleset not derived from the CoreRPG Ruleset then the location of the file may be different - but that's where its gotta go.

We also need to tell the imagecontrol where the code for the onBuildCustomPointer() function (and the functions called by onBuildCustomPointer()) lives. The code to do this is (assuming the file containing these functions is called Pointer_Toolkit.lua):

<imagecontrol>
<script file="Pointer_Toolkit.lua" />
</imagecontrol>

This is also explained in 2nd post.

But remember, the Pointers Toolkit is a Toolkit - not a complete set of files you can "drop in" to an existing Ruleset. If it was, it would be an Extension, not a Toolkit! In other words, treat it like a piece of furniture from Ikea - some assembly required. :)

Any other questions please ask.

Cheers

dulux-oz
December 14th, 2014, 05:41
Hi Guys,

Because I've been informed that some people are having trouble using the Pointer Toolkit I put together this post to try to make things clearer - Enjoy!

IMPORTNAT POINT No 1

Pointers can be considered an "advanced" topic in FG Coding. You NEED to FULLY read and understand the Pointer Toolkit Documentation to be able to use the Toolkit consistently and successfully. Just about all of the issues people have had with getting the Toolkit to work have stemmed from the fact that they HAVEN'T FULLY read and/or understood the documentation (and/or they've been trying to use the Toolkit to do things it wasn't designed to do).
IMPORTNAT POINT No 2

The Pointer Toolkit is NOT designed to be used "as is" - it's a Toolkit - you take the individual tool or tools from the kit and use them in your project.
IMPORTNAT POINT No 3

The Pointer Toolkit includes some SAMPLE or EXAMPLE functions to show you how to achieve certain results. If the Sample functions are useful to use "as is", by all means use them - BUT they were designed as Examples, not as finalised code.
OK, now that we've understood these three very important points, lets get started.

FG has two types of Pointers: Standard Pointers and Custom Pointers.

The Standard Pointers are: Circle, Square, 90-Degree Cone and Arrow. Custom Pointers are Pointers we create.

We don't need to do anything to the FG Code to use the Standard Pointers, UNLESS we want to use one or more Custom Pointers in our Ruleset.

Any Ruleset can have a MAXIMUM of 5 Pointers. The 5 Pointers can be a mix of Standard and Custom Pointers.

Any Pointer is made up of Lines, Curves and Quarter-Ellipse Curves.

To draw a Line we use the fpLineCurve() function from the Toolkit and give it the Starting and Ending X- and Y-Coordinates, plus an Offset. I'll explain what the Offset does later. Most people will use an Offset of 0.

To draw a curve we can use either the fpAngleArcCurve() or the fpEndpointArcCurve() function and give them the Starting X- and Y-Coordinates of the Curve, the X- and Y-Coordinates of the Centrepoint of the Circle the Curve is a part of and either the Angle that the Curve needs to cover in Degrees (for the first function) or the Ending X- and Y-Coordinates of the Curve (for the second function). Both of these functions in turn call the fpArcCurve() function.

To draw a Quarter-Ellipse curve we can use the fpEllipseCurve() function and give it the X- and Y-Radius of the Quarter-Ellipse, plus an Offset. The "flatness" of the Quarter-Ellipse depends upon the ratio of the two Radii: if they are the same (ie 1:1) we end up with a Quarter-Circle.

So, if your Pointer has Lines you need to include the fpLineCurve() function, if it has Quarter-Ellipses you need to include the fpEllipseCurve() function, and if it has Curves you need to include the fpArcCurve() function and either (or both) the fpAngleArcCurve() or the fpEndpointArcCurve() function, depending upon how you draw your curves (End-Point or Angle-of-Arc).

These five functions are called Curve Definition Functions.

OK, so How do we do this?

Well, Pointers are part of imagecontrols, so we need to modify the imagecontrol used to display maps, etc. In the CoreRPG this imagecontol is called "image" and is found starting at line 28 of the campaign/campaign_images.xml file. If we look at line 35 of this file we can see that the image imagecontrol calls a script file called image.lua in the campaign/scripts folder. So we know we need to modify these two files: we need to put the relevant functions from the Pointer Toolkit into the image.lua file and make some changes to the image imagecontrol in the campaign_images.xml file. Of course, this is just one way of accomplishing our goal - and we need to be aware that if we modify these two files the next time FG is updated the two files will be reverted back to they way they were before we changed them - some people solve this problem by putting their Custom Pointers in an Extension, but this can lead to conflicts with other Extensions that deal with the image imagecontrol.

To tell FG that we want to use Custom Pointers we need to put a <pointertypes> tag inside the image imagecontrol and a pointer tag for each Custom Pointer AND one for each Standard Pointer. The SAMPLE Code below shows a <pointertypes> tag with 5 pointer tags: one for each of the Standard Pointers and one for a Custom Pointer called "45Cone".


<imagecontrol>
<pointertypes>
<custom name="45Cone">
<icon>pointer_cone</icon>
<label>Draw a 45-Degree Cone</label>
</custom>
<arrow>
<icon>pointer</icon>
<label>Draw Arrow</label>
</arrow>
<square>
<icon>pointer_square</icon>
<label>Draw Square</label>
</square>
<circle>
<icon>pointer_circle</icon>
<label>Draw Circle</label>
</circle>
<cone>
<icon>pointer_cone</icon>
<label>Draw Cone</label>
</cone>
</pointertypes>
<!-- Other imagecontrol tags -->
</imagecontrol>

Once FG sees the <pointertypes> tag and the corresponding <custom> tag it automatically calls a function called onBuildCustomPointer() with takes the Starting and Ending X- and Y-Coordinates of the Pointer, plus a string representing the Custom Pointer name (eg "45Cone"). We don't have to worry about calling onBuildCustomPointer() with the correct values: FG takes care of this for us. We do have to tell onBuildCustomPointer() what to do, such as work out the length of the Pointer, etc, plus draw the Pointer. Obviously, onBuildCustomPointer() should go into the same file that we've placed the required Curve Definition Functions in (eg image.lua or our Extension).

The best way to draw a Pointer is have onBuildCustomPointer() call a Pointer-Drawing function, one for each Custom Pointer. These Pointer-Drawing functions also go into the file with the other functions (eg image.lua).

To draw a Pointer FG expects a table of Coordinates. The Curve Definition Functions return the correctly values in the correct format, so if our Pointer-Drawing function calls the correct Curve Definition Functions with the correct values, then we've drawn our Pointer.

The Pointer Toolkit includes 11 SAMPLE Pointer-Drawing functions and a SAMPLE onBuildCustomPointer() function to show you how to do this.

Pointers are drawn centred around the Start Point (for Circles, Squares, etc) or originating at the Start Point (for Cones and Arrows) and extending out to the End Point. Remember the Offset value used in some of the Curve Definition Functions? This causes the Line/Ellipse-Curve to "slide along" the line from the Start Point to the End Point. If you look closely at the Sample Pointer-Drawing functions you can see how this works.

So, to summarise: take the functions and Sample functions from the Toolkit and place them in a file. Link the file (if it isn't already) to the imagecontol. Tell FG to use the Custom Pointers via the <pointertypes> tag.

And have fun!

I hope this has made things clearer for everyone.

Any question, please ask.

Cheers

Blackfoot
December 14th, 2014, 17:55
I think you are assuming a lot to think we can actually understand the documentation. :)
Honestly.. I don't really understand half of what you type. :D

dulux-oz
December 15th, 2014, 01:13
I think you are assuming a lot to think we can actually understand the documentation. :)

OK, fair comment. I have assumed a basic level of knowledge, which (IMNSHO) is the minimum required if someone is going to be creating Extensions and Rulesets. However, you know what they say about assuming: it makes an @ss out of you and me. Everyone is different and everyone learns/understands things in a different way, so let's see what we can do to get this sorted.

Which part (of the doco) is causing you grief or where are you getting caught up?

Trenloe
December 19th, 2014, 01:21
FYI the square pointertype should be <rectangle>, square doesn't work. M_W confirmed that rectangle should be used.

Use the following for the 4 basic (default) types:

<imagecontrol>
<pointertypes>
<arrow>
<icon>pointer</icon>
<label>Draw Arrow</label>
</arrow>
<rectangle>
<icon>pointer_square</icon>
<label>Draw Square</label>
</rectangle>
<circle>
<icon>pointer_circle</icon>
<label>Draw Circle</label>
</circle>
<cone>
<icon>pointer_cone</icon>
<label>Draw Cone</label>
</cone>
</pointertypes>
</imagecontrol>

ShotGun Jolly
December 24th, 2014, 09:46
So, just so you know. After all the help I got with the 30* cone pointer. Which is greatly appreciated. I decided to keep working on it myself to figure out why I couldn't get the instructions to work. And then it struck me like a ton of bricks. The problem I had, where the end points would show, but not the outline. It was due to the same problem we solved in other post. I had alternate extensions that were effecting the code. Once I turned them off, I was able to draw what I needed.

So, I wanted to verify that the instructions in Dulux's thread here did work, and I was able to follow them. In fact, the whole time I was looking for help to solve why it didnt work, it actually was working.. it was just conflicting with another extension. Bone head move on my part. But I just wanted to let people know that the instructions here do work.

dulux-oz
December 24th, 2014, 11:04
So, just so you know. After all the help I got with the 30* cone pointer. Which is greatly appreciated. I decided to keep working on it myself to figure out why I couldn't get the instructions to work. And then it struck me like a ton of bricks. The problem I had, where the end points would show, but not the outline. It was due to the same problem we solved in other post. I had alternate extensions that were effecting the code. Once I turned them off, I was able to draw what I needed.

So, I wanted to verify that the instructions in Dulux's thread here did work, and I was able to follow them. In fact, the whole time I was looking for help to solve why it didnt work, it actually was working.. it was just conflicting with another extension. Bone head move on my part. But I just wanted to let people know that the instructions here do work.

Thankyou for that - it takes a lot of guts to publicly admit when you're wrong, and I for one appreciate you setting the record straight.

I'm glad you got things sorted - for my own edification, which was the other Extension(s)?

Cheers

ShotGun Jolly
December 24th, 2014, 12:10
I have no problem saying I made a mistake, even more so when I do not have a sweet fracking clue on how all of this works other then what I have read here on the forums.

Now that I am finally starting to understand the bare bones of it. Some things are starting to fall into place a little easier. Don't get me wrong, your instructions are great but they are well over my head when it comes to understanding them. It took me a long while of poking around to figure some of the stuff out. But, all the answers are there, I just needed to roll a few critical successes on my Cryptography skills to get there :)

As for your question, it was just one of my own Frankenstein copy/paste concoctions I had made when I first started to mess with this in the first place.

damned
December 24th, 2014, 13:15
it was just one of my own Frankenstein copy/paste concoctions I had made when I first started to mess with this in the first place.

Doh!
I mess up my code so bad sometimes...

dulux-oz
December 25th, 2014, 05:06
Doh!
I mess up my code so bad sometimes...

Ditto - you should see some of the abject cluster-f...s that I've coded before I get something right (or even half-right) - why do you think its taking so long to get the next versions of my old (& new) extensions out? :p

Merry Christmas Y'all.

(I ate too much for Christmas lunch - now I'm lying on my bed desperately willing my stomach NOT to explode) :)

Unahim
February 15th, 2016, 13:49
Hey guys!

I've followed the instructions here to add a 5e cone (which are 53°) to my games. Works great! I get the pointer and everything, no problem there.

So long as I only use my custom pointer, no further errors occur. Yet as soon as I add the "default" ones back, I get the following errors:

"
Script Error: [string "imagewindow_toolbar:toolbar_draw"]:1: 'then' expected near '~'
Script Error: [string "toolbar_draw"]:1: 'then' expected near '~'
"

The first one occurs upon loading into the campaign, the second whenever an image is opened. The errors don't prevent anything from working as far as I can tell, all the pointers (both the custom one and the defaults) work fine, it's just that I get spammed by these errors constantly! I've tried to debug it on my own, but frankly I have no clue what I'm doing beyond the instructions provided here.

Some extra info in case it's relevant: I don't have any idea how to make extensions yet, so I'm currently just directly replacing the relevant files in the Core RPG ruleset (bad practice, I know, but it was the only way I really knew how to start testing it out ^^).

Oh, and one additional question: is it possible for a custom pointer to fill in affected squares, like the default ones do? Currently my custom one doesn't have that functionality.

dulux-oz
February 15th, 2016, 14:27
Without taking a look at the exact code you put together (ie how you implemented the Toolkit) its hard to say whats going on - BUT I suspect its because you dropped in the code directly to the CoreRPG code. If that's the case, then there is something interfering somehow.

Now the error message you're getting (its the same one both times) is to do with an if-then-else statement - or that's what the FG engine believes is happening anyway - and its happening in the file "imagewindow_toolbar" and the windowclass "toolbar_draw". The system beleives that there is a missing "then" at the end of an "if" statement and that it should come after the "~" character (which should probably actually be "~=" as in "not equal to")

So my advice is put the stuff into an Extension - grab an existing extension and unpack it and try to see what's going on, then create a new extension do use for your 5E Cone.

A PITA, but that's your best bet

And no, the shading code is not exposed to the Community Devs so we can't get it to work on Custom Pointers (I've asked - believe me, I've asked) :)

Unahim
February 15th, 2016, 16:09
A PITA, but that's your best bet


Would have had to do this sooner or later anyway, so that's alright. ^^ To give you a bit more details on how I implemented it, this is what I did to the imagecontrol in campaign_images.xml:



<imagecontrol name="image">
<script file="campaign/scripts/5e_cone.lua" />
<pointertypes>
<arrow>
<icon>pointer</icon>
<label>Draw Arrow</label>
</arrow>
<rectangle>
<icon>pointer_square</icon>
<label>Draw Square</label>
</rectangle>
<circle>
<icon>pointer_circle</icon>
<label>Draw Circle</label>
</circle>
<custom name="53ConePointer">
<icon>pointer_cone</icon>
<label>Draw a 5e Cone</label>
</custom>
</pointertypes>
[...more stuff that was already there after this]


5e_cone.lua is basically just your toolkit stripped down so that the only "if" statement in the onBuildCustomPointer is my 53° cone. That all works and I think my code in campaign_images.xml is also fine, so it really has to be not working in an extension, I guess.

Trenloe
February 15th, 2016, 18:03
"
Script Error: [string "imagewindow_toolbar:toolbar_draw"]:1: 'then' expected near '~'
Script Error: [string "toolbar_draw"]:1: 'then' expected near '~'
"
As dulux_oz mentions, this is a script error the FG XML. Look in the imagewindow_toolbar <windowclass> and the toolbar_draw control. There is a <script> section within that control's XML definition, and the error will be in the LUA code in that section. Unfortunately, as the code is within the XML definition, there is no specific line number available (errors will always show line 1 for LUA code within XML <script> sections).

Look in the CoreRPG ruleset, campaign\campaign_images.xml file for the <toolbar_30 name="toolbar_draw"> control definition. Check the LUA code in the <script> section with the base code in an unmodified CoreRPG ruleset.

GrimmSpector
February 27th, 2016, 07:13
So you mentioned that drawing the spline gets us the coordinates initially; if we were to draw a zero length or very small length, wouldn't that give us the coordinates of the token we're starting at?

dulux-oz
February 27th, 2016, 09:03
Yeah, but the pointer's coordinates are only exposed to us from a user-action - there's no-way (that I know of) that can get the system to initiate the drawing of a pointer without user involvment.

But its an interesting idea - interesting enough that I'm going to run it past Moon

GrimmSpector
February 27th, 2016, 15:48
Well we can already fake being a player, and the GM can of course draw their own pointers on the map at any point. I'm wondering if we can make an invivsible fake user, that temporarily owns all tokens, or something, that can initiate it in the background. Though I assume that the main issue here is that FG is capturing the users mouse input to figure out where to start that drawing, which of course we'd have to somehow simulate then, and know where we want to put the mouse cursor. That is if I'm understanding the functionality correctly, I haven't even touched the available script for combat/ct/images yet, so I am just guessing here.

gandhi39
August 26th, 2018, 05:38
Is it possible to make an equilateral triangle pointer?

dulux-oz
August 26th, 2018, 06:01
Is it possible to make an equilateral triangle pointer?

Yes - you just need to realise that the base of the triangle is 2*(2^0.5) times the "length" of the pointer - Think about it like a standard cone (with a 60-degree "spread") but reverse or "flip" it so that the base is where the pointer starts and the apex is where it finishes - like a huge arrow-head.

gandhi39
August 26th, 2018, 14:30
Yes - you just need to realise that the base of the triangle is 2*(2^0.5) times the "length" of the pointer - Think about it like a standard cone (with a 60-degree "spread") but reverse or "flip" it so that the base is where the pointer starts and the apex is where it finishes - like a huge arrow-head.

I wanted to make it like the standard cone pointer, but with another line instead of the arc, as an equilateral triangle. Like the template from Xanatar’s Guide to Everything.

24458

bratch9
February 6th, 2023, 19:42
Anybody know how this structure has changed for the new 'filled' pointers ?

56087

Also I noticed that only the 'aShapeCurves' part seems to be used at the moment from the 'return aShapeCurves,aDistLabelPosition,bDrawArrow;'

My guess would be some extra new parameter in the return... or some new extra bits in the curve structure.

Do we have any new information on this not so 'documented/undocumented' system ?

( Or can we have a proper page in the Developer Guide Ruleset API Reference (https://fantasygroundsunity.atlassian.net/wiki/spaces/FGCP/pages/996644535/Developer+Guide+-+Ruleset+API+Reference) )

Sorry for waking up an old thread... was thinking about custom pointers to show grid selection on my spell tokens with some horrible extra global data passing...

Getting some way to 'add the pointers to image' from lua would also be nice...

Thanks, Pete