# Line Segment Enumeration .v2.1a

Submitted by IcemanBo
This bundle is marked as approved. It works and satisfies the submission rules.
Info

Allows to enumerate widgets inside a line segment with an offset.
So basicly it will result in a rect, but which is also allowed to be rotated.

There is normal JASS, and also a GUI version.
For GUI, you should open the demo map, to see the triggers.

Code

Code (vJASS):

library LineSegmentEnumeration /* v2.1a -- hiveworkshop.com/threads/line-segment-enumeration-v1-1.286552/

Information
¯¯¯¯¯¯¯¯¯¯¯

Allows to enumerate widgets inside a line segment with an offset.
So basicly it will result in a rect, but which is also allowed to be rotated.

Mechanics
¯¯¯¯¯¯¯¯¯

(Issue:)

The problem with normal jass rects is that they aren't defined by 4 points, but only by 4 values: x/y -min/max.
The result is that a jass rect is never rotated, so it's always paralel to the x/y axis.

But when we draw a line from point A to point B we might also create a non-axix-parelel rect, and then
we can't use the normal rect natives from jass anymore to find out if a point is inside the rect.

(Solution:)

To solve this problem the system does following:

jass rect: rectangular defined by 4 values (axis paralel)
custom rect: the real rectangular that is defined by user (not axis parelel)

1. Create a big jass rect that is big enough so we can ensure to enum all widgets that are potentialy inside our custom rect. (Enum_Group)
This Enum_Group will contain all wanted units, but may also contain not wanted units.

2. We rotate the custom rect around Point B, by angle "theta", so it gets now a normal, non-rotated rect. (axis parelel)

3. We loop through Enum_Group, and rotate all widget's coordinates also by the same "theta" value around Point B.

4. Now what happened is that the custom rect was manipulated so it got axis parelel. And we also used the same maths
to manipulate the x/y coordinate of our widgets, so the relation between the custom rect and the widget is still the very same.

But what we can do now is to use native jass logics to check if the widget is within the bounds of the rotated custom rect
with only comparing it's x/y coordinates with the rect's x/y -min/max coodinates.

Note: this approach is not the most performant one, but it doesn't work with custom types. So if you already work with for example
struct rects or other polygons, and you care for trigomnomig orientation you can write straight forward functions to check if a point
is inside a quadrilateral, as standalone function.

*/

//  --- API ---
//! novjass

struct LineSegment

static method EnumUnits takes group whichgroup, real ax, real ay, real bx, real by, real offset returns nothing

static method EnumDestructables takes real ax, real ay, real bx, real by, real offset returns nothing

static integer DestructableCounter      // starts with index "0"
static destructable array Destructable

static method EnumItems takes real ax, real ay, real bx, real by, real offset returns nothing

static integer ItemCounter      // starts with index "0"
static destructable array Item

//! endnovjass
// ==== End API ====

struct LineSegment extends array

private static constant rect RECT = Rect(0, 0, 0, 0)

private static constant group GROUP = CreateGroup()

public static method EnumUnits takes group whichgroup, real ax, real ay, real bx, real by, real offset returns nothing
local real angle = Atan2(by - ay, bx - ax)
local real theta = ModuloReal(angle, bj_PI/2)
local real thetaSin
local real thetaCos
local unit u
local real x
local real y

local real x_new
local real y_new

local real maxX
local real maxY
local real minX
local real minY

local real x1
local real y1
local real x2
local real y2
local real x3
local real y3
local real x4
local real y4

// Enum all potential units
if ax > bx then
set maxX = ax + offset
set minX = bx - offset
else
set maxX = bx + offset
set minX = ax - offset
endif

if ay > by then
set maxY = ay + offset
set minY = by - offset
else
set maxY = by + offset
set minY = ay - offset
endif
call SetRect(RECT, minX, minY, maxX, maxY)
call GroupEnumUnitsInRect(GROUP, RECT, null)

// Create and rotate rect around point B, so it gets unrotated
set angle = angle - theta
set angle = angle*-1
set thetaSin = Sin(-theta)
set thetaCos = Cos(-theta)

set x_new = (ax-bx)*thetaCos - (ay-by)*thetaSin + bx
set y_new = (ax-bx)*thetaSin + (ay-by)*thetaCos + by

set x1 = x_new + offset*Cos(angle - bj_PI*0.5)
set y1 = y_new + offset*Sin(angle - bj_PI*0.5)
set x2 = x_new + offset*Cos(angle + bj_PI*0.5)
set y2 = y_new + offset*Sin(angle + bj_PI*0.5)
set x3 = bx + offset*Cos(angle - bj_PI*0.5)
set y3 = by + offset*Sin(angle - bj_PI*0.5)
set x4 = bx + offset*Cos(angle + bj_PI*0.5)
set y4 = by + offset*Sin(angle + bj_PI*0.5)

// define max/min values of the new non-rotated rect

if x1 > x2 then
set maxX = RMaxBJ(x1, x3)
set minX = RMinBJ(x2, x4)
else
set maxX = RMaxBJ(x2, x4)
set minX = RMinBJ(x1, x3)
endif

if y1 > y2 then
set maxY = RMaxBJ(y1, y3)
set minY = RMinBJ(y2, y4)
else
set maxY = RMaxBJ(y2, y4)
set minY = RMinBJ(y1, y3)
endif

// enum through all tracked units, rotate it around B, and check if it's inside bounds

call GroupClear(whichgroup)
loop
set u = FirstOfGroup(GROUP)
exitwhen u == null

set x = GetUnitX(u)
set y = GetUnitY(u)

set x_new = (x-bx)*thetaCos - (y-by)*thetaSin + bx
set y_new = (x-bx)*thetaSin + (y-by)*thetaCos + by

if x_new >= minX and x_new <= maxX and y_new >= minY and y_new <= maxY then
endif

call GroupRemoveUnit(GROUP, u)
endloop
endmethod

//! textmacro LSE_WIDGET takes TYPE, NAME
private static integer \$NAME\$Counter_p = -1
private static \$TYPE\$ array \$NAME\$_p
public static integer \$NAME\$Counter = -1
public static \$TYPE\$ array \$NAME\$

private static method on\$NAME\$Filter takes nothing returns nothing
set \$NAME\$Counter_p = \$NAME\$Counter_p + 1
set \$NAME\$_p[\$NAME\$Counter_p] = GetEnum\$NAME\$()
endmethod

public static method Enum\$NAME\$s takes real ax, real ay, real bx, real by, real offset returns nothing
local real angle = Atan2(by - ay, bx - ax)
local real theta = ModuloReal(angle, bj_PI/2)
local real thetaSin
local real thetaCos
local \$TYPE\$ t
local real x
local real y

local real x_new
local real y_new

local real maxX
local real maxY
local real minX
local real minY

local real x1
local real y1
local real x2
local real y2
local real x3
local real y3
local real x4
local real y4

// Enum all potential widgets
if ax > bx then
set maxX = ax + offset
set minX = bx - offset
else
set maxX = bx + offset
set minX = ax - offset
endif

if ay > by then
set maxY = ay + offset
set minY = by - offset
else
set maxY = by + offset
set minY = ay - offset
endif
call SetRect(RECT, minX, minY, maxX, maxY)
set \$NAME\$Counter_p = -1
call Enum\$NAME\$sInRect(RECT, null, function thistype.on\$NAME\$Filter)

// Create and rotate rect around point B, so it gets unrotated
set angle = angle - theta
set angle = angle*-1
set thetaSin = Sin(-theta)
set thetaCos = Cos(-theta)

set x_new = (ax-bx)*thetaCos - (ay-by)*thetaSin + bx
set y_new = (ax-bx)*thetaSin + (ay-by)*thetaCos + by

set x1 = x_new + offset*Cos(angle - bj_PI*0.5)
set y1 = y_new + offset*Sin(angle - bj_PI*0.5)
set x2 = x_new + offset*Cos(angle + bj_PI*0.5)
set y2 = y_new + offset*Sin(angle + bj_PI*0.5)
set x3 = bx + offset*Cos(angle - bj_PI*0.5)
set y3 = by + offset*Sin(angle - bj_PI*0.5)
set x4 = bx + offset*Cos(angle + bj_PI*0.5)
set y4 = by + offset*Sin(angle + bj_PI*0.5)

// define max/min values of the new non-rotated rect

if x1 > x2 then
set maxX = RMaxBJ(x1, x3)
set minX = RMinBJ(x2, x4)
else
set maxX = RMaxBJ(x2, x4)
set minX = RMinBJ(x1, x3)
endif

if y1 > y2 then
set maxY = RMaxBJ(y1, y3)
set minY = RMinBJ(y2, y4)
else
set maxY = RMaxBJ(y2, y4)
set minY = RMinBJ(y1, y3)
endif

// enum through all tracked widgets, rotate it around B, and check if it's inside bounds

set \$NAME\$Counter = -1
loop
exitwhen \$NAME\$Counter_p < 0
set t = \$NAME\$_p[\$NAME\$Counter_p]

set x = Get\$NAME\$X(t)
set y = Get\$NAME\$Y(t)

set x_new = (x-bx)*thetaCos - (y-by)*thetaSin + bx
set y_new = (x-bx)*thetaSin + (y-by)*thetaCos + by

if x_new >= minX and x_new <= maxX and y_new >= minY and y_new <= maxY then

set \$NAME\$Counter = \$NAME\$Counter + 1
set \$NAME\$[\$NAME\$Counter] = t
endif

set \$NAME\$_p[\$NAME\$Counter_p] = null
set \$NAME\$Counter_p = \$NAME\$Counter_p - 1
endloop
endmethod
//! endtextmacro

//! runtextmacro LSE_WIDGET("destructable", "Destructable")
//! runtextmacro LSE_WIDGET("item", "Item")

endstruct
endlibrary

Code (vJASS):

library LineSegmentEnumerationGUI uses LineSegmentEnumeration /* v2.0
*/

private struct LineSegmentGUI extends array

private static method GetUnits takes nothing returns nothing
call LineSegment.EnumUnits(udg_LSE_Group, GetLocationX(udg_LSE_Loc_1), GetLocationY(udg_LSE_Loc_1), GetLocationX(udg_LSE_Loc_2), GetLocationY(udg_LSE_Loc_2), udg_LSE_Offset)
endmethod

private static method GetDestructables takes nothing returns nothing
call LineSegment.EnumDestructables(GetLocationX(udg_LSE_Loc_1), GetLocationY(udg_LSE_Loc_1), GetLocationX(udg_LSE_Loc_2), GetLocationY(udg_LSE_Loc_2), udg_LSE_Offset)
set udg_LSE_DestructableCounter = -1
loop
exitwhen LineSegment.DestructableCounter < 0
set udg_LSE_DestructableCounter = udg_LSE_DestructableCounter + 1
set udg_LSE_Destructable[udg_LSE_DestructableCounter] = LineSegment.Destructable[LineSegment.DestructableCounter]
set LineSegment.DestructableCounter = LineSegment.DestructableCounter - 1
endloop
endmethod

private static method GetItems takes nothing returns nothing
call LineSegment.EnumItems(GetLocationX(udg_LSE_Loc_1), GetLocationY(udg_LSE_Loc_1), GetLocationX(udg_LSE_Loc_2), GetLocationY(udg_LSE_Loc_2), udg_LSE_Offset)
set udg_LSE_ItemCounter = -1
loop
exitwhen LineSegment.ItemCounter < 0
set udg_LSE_ItemCounter = udg_LSE_ItemCounter + 1
set udg_LSE_Item[udg_LSE_ItemCounter] = LineSegment.Item[LineSegment.ItemCounter]
set LineSegment.ItemCounter = LineSegment.ItemCounter - 1
endloop
endmethod

private static method onInit takes nothing returns nothing
set udg_LSE_GetUnits = CreateTrigger()
set udg_LSE_GetDestructables = CreateTrigger()
set udg_LSE_GetItems = CreateTrigger()
endmethod

endstruct

endlibrary

How to use

• LSE GUI How to Use
• Ereignisse
• Bedingungen
• Aktionen
• -------- ----------------------------------------------------------- --------
• -------- Step 1 - Define endpoints and offset tolerance of line. --------
• -------- ----------------------------------------------------------- --------
• Set LSE_Loc_1 = (Random point in (Playable map area))
• Set LSE_Loc_2 = (Random point in (Playable map area))
• Set LSE_Offset = 100.00
• -------- ----------------------------------------------------------- --------
• -------- Step 2 Enumeration --------
• -------- ----------------------------------------------------------- --------
• -------- 2.1 Units --------
• -------- ----------------------------------------------------------- --------
• Trigger - Run LSE_GetUnits (ignoring conditions)
• -------- ----------------------------------------------------------- --------
• -------- Now you can work with LSE_group. --------
• -------- ----------------------------------------------------------- --------
• Unit Group - Pick every unit in LSE_Group and do (Actions)
• Loop - Actions
• -------- ----------------------------------------------------------- --------
• -------- Do some actions here for example. --------
• -------- ----------------------------------------------------------- --------
• -------- ----------------------------------------------------------- --------
• -------- Attention -- do never destroy LSE_Group. --------
• -------- ----------------------------------------------------------- --------
• -------- 2.2 Destructables --------
• -------- ----------------------------------------------------------- --------
• Trigger - Run LSE_GetDestructables (ignoring conditions)
• -------- ----------------------------------------------------------- --------
• -------- Loop from 0 to LSE_DestructableCounter --------
• -------- ----------------------------------------------------------- --------
• For each (Integer A) from 0 to LSE_DestructableCounter, do (Actions)
• Loop - Actions
• -------- ----------------------------------------------------------- --------
• -------- Use LSE_Destructable[Integer A] to refer to current destructable. --------
• -------- ----------------------------------------------------------- --------
• -------- ----------------------------------------------------------- --------
• -------- 2.3 Items --------
• -------- ----------------------------------------------------------- --------
• Trigger - Run LSE_GetItems (ignoring conditions)
• -------- ----------------------------------------------------------- --------
• -------- Loop from 0 to LSE_ItemCounter --------
• -------- ----------------------------------------------------------- --------
• For each (Integer A) from 0 to LSE_ItemCounter, do (Actions)
• Loop - Actions
• -------- ----------------------------------------------------------- --------
• -------- Use LSE_Item[Integer A] to refer to current item. --------
• -------- ----------------------------------------------------------- --------
• -------- ----------------------------------------------------------- --------
• -------- To prevent leaks. --------
• -------- ----------------------------------------------------------- --------
• Custom script: call RemoveLocation(udg_LSE_Loc_1)
• Custom script: call RemoveLocation(udg_LSE_Loc_2)

How To Import A Spell/System
Previews
Contents

# Line Segment Enumeration .v2.1a(Map)

Reviews
A simple mechanic, yet the math behind it all is beyond my scope of knowledge! A great thank you to Ammorth and Anitarf for the small work such as this they do. We can of course cannot forget to thank the IcemanBo for applying it into a GUI-Friendly...
1. ### KILLCIDE

Joined:
Jul 22, 2015
Messages:
3,502
Resources:
20
Models:
2
Icons:
10
Spells:
7
Tutorials:
1
Resources:
20
A simple mechanic, yet the math behind it all is beyond my scope of knowledge! A great thank you to Ammorth and Anitarf for the small work such as this they do.

We can of course cannot forget to thank the IcemanBo for applying it into a GUI-Friendly and vanilla editor compatible system with extremely easy to use API. The code looks good as usual, and the demo map is superb (; Personally, I would use another global group instead of declaring
`local group g`
all the time.

Approved with a 4/5 rating. I hope to see future submissions using this library.

2. ### JAKEZINC

Joined:
Aug 13, 2013
Messages:
1,615
Resources:
9
Spells:
9
Resources:
9
Very useful system especially when accompanied for lightnings!

Well expect me to create a future spell using this stuff ^^.

3. ### Aniki

Joined:
Nov 7, 2014
Messages:
555
Resources:
6
Tools:
1
Maps:
1
Spells:
1
JASS:
3
Resources:
6
Have you tried:
• Actions
• -------- not important, just for visuals --------
• Custom script: call MoveLightningEx(udg_Lightning, true, GetUnitX(udg_Unit1), GetUnitY(udg_Unit1), 75.00, GetUnitX(udg_Unit2), GetUnitY(udg_Unit2), 75.00)
• -------- --------
• -------- IMPORTANT STUFF --------
• Set LSE_Loc_1 = (Position of Unit1)
• Set LSE_Loc_2 = (Position of Unit2)
• Set LSE_Width = 500.00

i.e setting udg_LSE_Width to 500 for example and move a knight towards a chicken, the chicken dies when the knight is 500 units or less away from it.

Another example would be if we use the
`LSE_GroupEnumUnitsInRangeOfSegment`
function to get all the units between a caster unit and a target location say 600.0 away (when casting a point target spell), what would happen is that units that are behind the caster and less than 600.0 units away would also be picked (also units that are 600.0 away from the target point in the direction that the caster is facing would also be picked).

4. ### IcemanBo

Joined:
Sep 6, 2013
Messages:
6,532
Resources:
23
Maps:
3
Spells:
11
Template:
1
Tutorials:
5
JASS:
3
Resources:
23
Definitly true. I've also chosen a wrong name. It's actually the max distance from the line. And "width" implies total width, so max wrong of 2x amount. ^^
Thanks, will fix tomorrow!

5. ### Aniki

Joined:
Nov 7, 2014
Messages:
555
Resources:
6
Tools:
1
Maps:
1
Spells:
1
JASS:
3
Resources:
6
vJass version:

Code (vJASS):

library EnumInRangeOfLineSegment

globals
private constant real max_collision_size = 48.0
endglobals

static if DEBUG_MODE then
private function panic takes string s returns nothing
call BJDebugMsg("|cffFF0000" + s + "|r")
if 1 / 0 == 1 then
endif
endfunction
endif

globals
private group gg = CreateGroup()
endglobals
function GroupEnumUnitsInRangeOfLineSegment takes group g, real ax, real ay, real bx, real by, real line_half_width returns nothing
local real abx
local real aby
local real ab_len_sq
local real t
local unit u

static if DEBUG_MODE then
if ax == bx and ay == by then
call panic("[GroupEnumUnitsInRangeOfLineSegment] error: " + "expected a line segment, got point (" + R2S(ax) + ", " + R2S(ay) + ")")
endif
endif

set abx = bx - ax
set aby = by - ay
set ab_len_sq = abx*abx + aby*aby

set radius = 0.5 * SquareRoot(abx*abx + aby*aby) + line_half_width + max_collision_size
call GroupEnumUnitsInRange(gg, ax + (0.5 * abx), ay + (0.5 * aby), radius, null)
loop
set u = FirstOfGroup(gg)
exitwhen u == null
call GroupRemoveUnit(gg, u)

set t = ((GetUnitX(u) - ax)*abx + (GetUnitY(u) - ay)*aby) / ab_len_sq
if 0.0 <= t and t <= 1.0 and IsUnitInRangeXY(u, ax + t*abx, ay + t*aby, line_half_width) then
endif
endloop
endfunction

globals
destructable array ls_destructables // 1 based array
integer ls_destructables_count = 0

private constant real tau = 2.0 * (355.0 / 113.0)
private rect rr = Rect(0.0, 0.0, 0.0, 0.0)
private real pax
private real pay
private real pbx
private real pby
private real pline_half_width
endglobals

private function enum_destructables takes nothing returns nothing
local destructable d = GetEnumDestructable()
local real dx = GetWidgetX(d)
local real dy = GetWidgetY(d)
local real abx = pbx - pax
local real aby = pby - pay
local real ab_len_sq = abx*abx + aby*aby
local real t = ((dx - pax)*abx + (dy - pay)*aby) / ab_len_sq
local real px
local real py
local real dist_sq

if 0.0 <= t and t <= 1.0 then
set px = pax + t*abx
set py = pay + t*aby
set dist_sq = (dx - px)*(dx - px) + (dy - py)*(dy - py)
if dist_sq <= pline_half_width*pline_half_width then
set ls_destructables_count = ls_destructables_count + 1
set ls_destructables[ls_destructables_count] = d
endif
endif

set d = null
endfunction

function EnumDestructablesInRangeOfLineSegment takes real ax, real ay, real bx, real by, real line_half_width returns nothing
local real hw
local real minx
local real miny
local real maxx
local real maxy
local real d
local real ang

static if DEBUG_MODE then
if ax == bx and ay == by then
call panic("[EnumDestructablesInRangeOfLineSegment] error: " + "expected a line segment, got point (" + R2S(ax) + ", " + R2S(ay) + ")")
endif
endif

loop
exitwhen ls_destructables_count <= 0
set ls_destructables[ls_destructables_count] = null
set ls_destructables_count = ls_destructables_count - 1
endloop

set hw = line_half_width
set d = Atan2(by - ay, bx - ax)

set ang = d
if ang < 0.0 then
set ang = ang + tau
endif
set quadrant = R2I(ang / (tau/4.0))

// there's probably an easier way to find the 2 points but I don't know it =)
set minx = ax + hw * Cos(d + tau/4.0)
set miny = ay + hw * Sin(d - tau/4.0)
set maxx = bx + hw * Cos(d - tau/4.0)
set maxy = by + hw * Sin(d + tau/4.0)

set minx = bx + hw * Cos(d + tau/4.0)
set miny = ay + hw * Sin(d + tau/4.0)
set maxx = ax + hw * Cos(d - tau/4.0)
set maxy = by + hw * Sin(d - tau/4.0)

set minx = bx + hw * Cos(d - tau/4.0)
set miny = by + hw * Sin(d + tau/4.0)
set maxx = ax + hw * Cos(d + tau/4.0)
set maxy = ay + hw * Sin(d - tau/4.0)

else // if quadrant == 3 then
set minx = ax + hw * Cos(d - tau/4.0)
set miny = by + hw * Sin(d - tau/4.0)
set maxx = bx + hw * Cos(d + tau/4.0)
set maxy = ay + hw * Sin(d + tau/4.0)

endif
call SetRect(rr, minx, miny, maxx, maxy)

set pax = ax
set pay = ay
set pbx = bx
set pby = by
set pline_half_width = hw
call EnumDestructablesInRect(rr, null, function enum_destructables)
endfunction

endlibrary

Edit: changed this:
Code (vJASS):

set hw = line_half_width
set minx = RMinBJ(ax, bx) - hw
set miny = RMinBJ(ay, by) - hw
set maxx = RMaxBJ(ax, bx) + hw
set maxy = RMaxBJ(ay, by) + hw
call SetRect(rr, minx, miny, maxx, maxy)

to this:
Code (vJASS):

set hw = line_half_width
set d = Atan2(by - ay, bx - ax)
set minx = ax + hw * Cos(d + tau/4.0)
set miny = ay + hw * Sin(d - tau/4.0)
set maxx = bx + hw * Cos(d - tau/4.0)
set maxy = by + hw * Sin(d + tau/4.0)
call SetRect(rr, minx, miny, maxx, maxy)

which I think is a better fit.

Edit2: now should work for all quadrants =)

Last edited: Jan 17, 2017

### Spell Reviewer

Joined:
May 9, 2014
Messages:
1,524
Resources:
9
Models:
1
Icons:
2
Maps:
2
Spells:
3
JASS:
1
Resources:
9
@IcemanBo,

Can I add a sample spell to show how the system works?

7. ### IcemanBo

Joined:
Sep 6, 2013
Messages:
6,532
Resources:
23
Maps:
3
Spells:
11
Template:
1
Tutorials:
5
JASS:
3
Resources:
23
It's a bit flawed atm, pointed out by Aniki, but sure.

### Spell Reviewer

Joined:
May 9, 2014
Messages:
1,524
Resources:
9
Models:
1
Icons:
2
Maps:
2
Spells:
3
JASS:
1
Resources:
9
Code (vJASS):

//Written in pseudo vJass
//Thanks to IcemanBo for the LSE system

//Globals section is a vjass feature, since it uses a preprocessor to generate these variables.

globals
constant hashtable SPELL_HASH = InitHashtable()
unit enum_e
endglobals

constant function Radius takes nothing returns real
return 45.
endfunction

function Timer_Move_Unit_Actions takes nothing returns nothing
local timer t = GetExpiredTimer()
local real remaining = 1 - LoadReal(SPELL_HASH, GetHandleId(t), 1)
local unit caster = LoadUnitHandle(SPELL_HASH, GetHandleId(t), 0)
local real getFacing = LoadReal(SPELL_HASH, GetHandleId(t), 2)
local real c_x = GetUnitX(caster)
local real c_y = GetUnitY(caster)
local group ggwp = CreateGroup()
local group getGroup = LoadGroupHandle(SPELL_HASH, GetHandleId(t), 3)
if remaining > 0 then
//Normally, this would return a usable global variable, but it is too late to incorporate it now.
loop
set enum_e = FirstOfGroup(ggwp)
exitwhen enum_e == null
if not IsUnitInGroup(enum_e, getGroup) and IsUnitEnemy(enum_e, GetOwningPlayer(caster)) and GetWidgetLife(enum_e) > 0.405 then
//This checks whether or not the unit was previously enumerated and included.
//The GetWidgetLife native gets the life of the unit and checks  whether or not its' health is greater than 0.405.
//If it is, it will proceed to the next line since it is alive.
call UnitDamageTarget(caster, enum_e, 75, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
//action here
//Includes the enumerated unit if it hasn't been included already.
endif
call GroupRemoveUnit(ggwp, enum_e)
//FirstOfGroup loops use up all of the units in a group, remmoving them so as not to cause an infinite loop.
endloop
call SetUnitX(caster, c_x + Radius() * Cos(getFacing))
call SetUnitY(caster, c_y + Radius() * Sin(getFacing))
//call SetUnitPositionLoc()
//Think of this as SetUnitPositionLoc without the location handle, which means that you don't need to call RemoveLocation()
//with the benefit of not needing to null the real values.
call SaveReal(SPELL_HASH, GetHandleId(t), 1, LoadReal(SPELL_HASH, GetHandleId(t), 1) + 0.03125)
//This updates the passage of time so as not to cause an infinite loop.
//Q: How would this create an infinite loop?
//A: The timer will always repeat itself, not stopping until it is manually paused.
//  This creates a problem for us, since we can't access it directly but through
//  this function only. Thus, we include this variable to account for the passage of
//  time.
//
//     Going back to the if-then statement, the statement remaining > 0 checks
//  if the time elapsed is not yet 1 since the variable in question is stated as:
//  remaining = 1 - LoadReal(SPELL_HASH, GetHandleId(t), 1)
//  If it is false, it then proceeds to the else portion of the code.
else
call FlushChildHashtable(SPELL_HASH, GetHandleId(t))
//This removes all data associated with the expiring timer
call PauseTimer(t)
call DestroyTimer(t)
//Why do we pause the timer? It could be that the timer is still running and it would create a bug if we destroyed it right away.
//As always, destroying leaks in this case, the timer, is a must. (unless you have a library that recycles them instead)
call DestroyGroup(getGroup)
//The group is no longer used at this point, which means that we have a leak. Destroying it would (minimize) remove the leak.
endif
call DestroyGroup(ggwp)
//This group is a dummy group, only used for enumeration. However, since that it would end up always being created, we
//must destroy it so as not to cause leaks.
set ggwp = null
set getGroup = null
set caster = null
set t = null
endfunction

function Trig_Move_Unit_Actions takes nothing returns boolean
local unit caster = GetTriggerUnit()
local real c_x = GetSpellTargetX()
local real c_y = GetSpellTargetY()
local timer t
if GetSpellAbilityId() == 'A000' then
//This checks whether or not our ability being cast is correct.
set t = CreateTimer()
//This creates a timer handle, which we can use to execute a loop
call SaveUnitHandle(SPELL_HASH, GetHandleId(t), 0, caster)
call SaveReal(SPELL_HASH, GetHandleId(t), 1, 0.)
call SaveReal(SPELL_HASH, GetHandleId(t), 2, Atan2(c_y - GetUnitY(caster), c_x - GetUnitX(caster)))
call SaveGroupHandle(SPELL_HASH, GetHandleId(t), 3, CreateGroup())
//Messy syntax, but this saves our data in the hashtable SPELL_HASH.
call TimerStart(t, 0.03125, true, function Timer_Move_Unit_Actions)
//This starts the timer with a loop.
//    Q: Why timers when you could just use TriggerSleepAction?
//    A: TriggerSleepAction is infamous for its inaccuracy in displaying the exact amount of time being displaced.
//    Now for longer wait times, this would become negligible, but for shorter frequencies, it become annoying
//    specially if you want to do some sort of gliding.
set t = null
endif
set caster = null
return false
endfunction

//===========================================================================
function InitTrig_Move_Unit takes nothing returns nothing
set gg_trg_Move_Unit = CreateTrigger(  )
call TriggerRegisterAnyUnitEventBJ(gg_trg_Move_Unit, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition( gg_trg_Move_Unit, function Trig_Move_Unit_Actions )
endfunction

Last edited: Jan 29, 2017
9. ### IcemanBo

Joined:
Sep 6, 2013
Messages:
6,532
Resources:
23
Maps:
3
Spells:
11
Template:
1
Tutorials:
5
JASS:
3
Resources:
23
Aniki thanks for the tips, but I tried to make an own (more naive xD) approach. It should work correctly now, though.^^

10. ### Aniki

Joined:
Nov 7, 2014
Messages:
555
Resources:
6
Tools:
1
Maps:
1
Spells:
1
JASS:
3
Resources:
6
I think it does.

There's this strange check in
`LineSegment.EnumUnits`
though:
Code (vJASS):

if GetOwningPlayer(u) != GetLocalPlayer() then // <-- o.O?
set x = GetUnitX(u)
set y = GetUnitY(u)

set x_new = (x-bx)*thetaCos - (y-by)*thetaSin + bx
set y_new = (x-bx)*thetaSin + (y-by)*thetaCos + by

if x_new >= minX and x_new <= maxX and y_new >= minY and y_new <= maxY then
endif
endif // <-- O.o?

11. ### IcemanBo

Joined:
Sep 6, 2013
Messages:
6,532
Resources:
23
Maps:
3
Spells:
11
Template:
1
Tutorials:
5
JASS:
3
Resources:
23
;D

Yes, that was from test code, to not enum my own units, thanks mate^^. Will update in an hour or so.
I figured it is a bit sad that I don't provide function to rotate a rect/or point, or just check if a point is inside one rotatet rect.
But if wanting such functions one should probably just use polygon, because they will really create/destroy data, and with this one you can just instantly enumerate without any storage. So it's maybe ok, just for this function.

12. ### IcemanBo

Joined:
Sep 6, 2013
Messages:
6,532
Resources:
23
Maps:
3
Spells:
11
Template:
1
Tutorials:
5
JASS:
3
Resources:
23
Update. EnumItems works now, too.

### Spell Reviewer

Joined:
May 9, 2014
Messages:
1,524
Resources:
9
Models:
1
Icons:
2
Maps:
2
Spells:
3
JASS:
1
Resources:
9
With enough replies, this could turn into something very useful.

14. ### IcemanBo

Joined:
Sep 6, 2013
Messages:
6,532
Resources:
23
Maps:
3
Spells:
11
Template:
1
Tutorials:
5
JASS:
3
Resources:
23

Btw, if you wanna really share a demo spell, you could also attach the demo map to your post above.

### Spell Reviewer

Joined:
May 9, 2014
Messages:
1,524
Resources:
9
Models:
1
Icons:
2
Maps:
2
Spells:
3
JASS:
1
Resources:
9
Will do, will do..
Kudos to you for making this :hand salute:

16. ### Flux

Joined:
Feb 6, 2014
Messages:
2,333
Resources:
28
Maps:
1
Spells:
19
Tutorials:
2
JASS:
6
Resources:
28
Ammorth and Anitarf?

17. ### IcemanBo

Joined:
Sep 6, 2013
Messages:
6,532
Resources:
23
Maps:
3
Spells:
11
Template:
1
Tutorials:
5
JASS:
3
Resources:
23
I stole most logics from them at first attempt + made it gui friendly and a system with all widgets. But then Aniki mentioned a bug, and logics were changed.

### Spell Reviewer

Joined:
May 9, 2014
Messages:
1,524
Resources:
9
Models:
1
Icons:
2
Maps:
2
Spells:
3
JASS:
1
Resources:
9
Code (vJASS):

set angle = angle - theta
set angle = angle*-1

Can be changed to

Code (vJASS):

set angle = theta - angle

Joined:
Sep 6, 2013
Messages:
6,532
Resources:
23
Maps:
3
Spells:
11
Template:
1
Tutorials:
5
JASS:
3
Resources:
23
Indeed. ;P