| 09-05-2015, 11:57 AM | #1 |
Rects and regions are mostly used to create "Enter" or "Leave" events for units. The correct implementation of these events is, however, quite annoying. For this reason I wrote a simple script that to automate all the bothersome things one needs to do when working with rects. For example, rects and regions don't share the same x&y coordinates (regions are on a 32 grid, rects are not) and the maxX and maxY of regions are rounded up to the nearest 32, which is very counter-intuitive. (for a good study on the subject, see http://www.wc3c.net/showthread.php?p=1134281). This script addresses these issues and adds some useful functionality such as hysteresis between enter/leave events, counting occupants, a never-failing .containsUnit function and a very easy method to filter units by the Enter response function. It also comes with a debug method ".display()", which shows the boundaries of the rect. The library requires Table JASS:library IRect initializer Init requires Table /***************************************************************************************** * * rects are mostly used to do "on enter" and "on leave" events * the IRect provides a straightforward way to use these events. * * besides having an easy syntax, it automatically fixes some bugs * associated with rects/regions, such as the up-rounding of the * maxX / maxY, and regions being set to multiple of 32, while * rects keep the original input leading to bugs when calling * RectContainsUnit. Another function is provided to check if a * unit is in the IRect. * * the last added benefit is the addition of hysteresis. A dynamic * "outer" rect can be added, so that the leave event fires at a * different position than the enter event. * * syntax: * *******************************User variables/functions:********************************* * IRect eventIRect * * this is the IRect from which an event was fired ***************************************************************************************** * unit eventUnit * * this is the entering/leaving/filter unit. ***************************************************************************************** * integer numberOfOccupants * * this is the number of units in the IRect ***************************************************************************************** * method containsUnit(unit u) returns boolean * * returns if a unit is currently occupying the IRect * it inlines to a simple native call * ************************************Setup functions:************************************* * * IRect.create(real minX, real minY, real maxX, real maxY) returns IRect * * this function creates an IRect. The IRect automatically creates * the rect, region and triggers required for the events ***************************************************************************************** * method setOnEnter(eventCondition) returns IRect * * pass a function to this method that takes nothing and returns a boolean * this function will be called when a unit enters the IRect * if the function returns true, then the unit is added to the occupants * if the function returns false, then the unit is ignored by the system * As the entering/leaving unit, use IRect.eventUnit (a global variable) * if you want to use the IRect variable itself, use the global * IRect.eventIRect * * this method also adds all units that are currently occupying the rect * and calls their enter function. ***************************************************************************************** * method setOnLeave(eventCallBack) returns IRect * * pass a function to this method that takes and returns nothing * this function will be called when a unit leaves the IRect * for event unit/iRect, use the same as in setOnEnter ***************************************************************************************** * method setHysteresis(real left, real bottom, real right, real top) returns IRect * * sets the margins for hysteresis at the sides of the IRect. * For example, for an IRect(0,0,64,64) * with a left hysteresis of 32, an entering unit will fire an event * coming from the left when it crosses x = 0. When exiting the * IRect, a leave event is fired at -32. ***************************************************************************************** * method setHysteresisSimple(real margin) returns IRect * * sets the hysteresis margins to the same value for all ***************************************************************************************** * method display() returns IRect * * displays the rects boundaries (including hysteresis if set) * useful for debugging, but nothing else ***************************************************************************************** */ globals HandleTable rTbl location l group g endglobals private function interface eventCallback takes nothing returns nothing private function interface eventCondition takes nothing returns boolean struct IRect readonly static unit eventUnit readonly static IRect eventIRect readonly integer numberOfOccupants = 0 private rect mainRect private rect outerRect private region mainRegion private region outerRegion private group occupants private trigger enterTrig private trigger leaveTrig private eventCondition enterFunc = 0 private eventCallback leaveFunc = 0 method containsUnit takes unit u returns boolean return IsUnitInGroup(u, .occupants) endmethod // ------------------------------------------------------- // setup functions (return the type to allow 1-line coding) // ------------------------------------------------------- // set the function that is called upon entering, also adds all units that the function returns true for to the group method setOnEnter takes eventCallback enterFunction returns thistype set .enterFunc = enterFunction call GroupEnumUnitsInRect(g, .mainRect, null) loop set .eventUnit = FirstOfGroup(g) exitwhen .eventUnit == null if not IsUnitInGroup(.eventUnit, .occupants) then if .enterFunc.evaluate() then call GroupAddUnit(.occupants, .eventUnit) set .numberOfOccupants = .numberOfOccupants + 1 endif endif call GroupRemoveUnit(g,.eventUnit) endloop return this endmethod // set the function that is called upon leaving method setOnLeave takes eventCallback leaveFunction returns thistype set .leaveFunc = leaveFunction return this endmethod // sets the hysteresis margins (different for all) method setHysteresis takes real left, real bottom, real right, real top returns thistype set left = RAbsBJ(left) set top = RAbsBJ(top) set right = RAbsBJ(right) set bottom = RAbsBJ(bottom) if .outerRect != null then call RegionClearRect(.outerRegion, .outerRect) call RemoveRect(.outerRect) else call RegionClearRect(.outerRegion, .mainRect) endif set .outerRect = Rect(GetRectMinX(.mainRect)-left, GetRectMinY(.mainRect)-bottom, GetRectMaxX(.mainRect)+right, GetRectMaxY(.mainRect)+top) call RegionAddRect(.outerRegion, .outerRect) return this endmethod // sets the hysteresis margins (same for all sides) method setHysteresisSimple takes real margin returns thistype set margin = RAbsBJ(margin) if .outerRect != null then call RegionClearRect(.outerRegion, .outerRect) call RemoveRect(.outerRect) else call RegionClearRect(.outerRegion, .mainRect) endif set .outerRect = Rect(GetRectMinX(.mainRect)-margin, GetRectMinY(.mainRect)-margin, GetRectMaxX(.mainRect)+margin, GetRectMaxY(.mainRect)+margin) call RegionAddRect(.outerRegion, .outerRect) return this endmethod // for debugging purposes method display takes nothing returns thistype local real x1 = GetRectMinX(.mainRect) local real x2 = GetRectMaxX(.mainRect) local real y1 = GetRectMinY(.mainRect) local real y2 = GetRectMaxY(.mainRect) local real z1 local real z2 local real z3 local real z4 call MoveLocation(l, x1, y1) set z1 = GetLocationZ(l) call MoveLocation(l, x2, y1) set z2 = GetLocationZ(l) call MoveLocation(l, x2, y2) set z3 = GetLocationZ(l) call MoveLocation(l, x1, y2) set z4 = GetLocationZ(l) call AddLightningEx("DRAL",true, x1, y1, z1, x2, y1, z2) // left, top to bottom call AddLightningEx("DRAL",true, x2, y1, z2, x2, y2, z3) // bottom, left to right call AddLightningEx("DRAL",true, x2, y2, z3, x1, y2, z4) // right, bottom to top call AddLightningEx("DRAL",true, x1, y2, z4, x1, y1, z1) // top, right to left if .outerRect != null then set x1 = GetRectMinX(.outerRect) set x2 = GetRectMaxX(.outerRect) set y1 = GetRectMinY(.outerRect) set y2 = GetRectMaxY(.outerRect) call MoveLocation(l, x1, y1) set z1 = GetLocationZ(l) call MoveLocation(l, x2, y1) set z2 = GetLocationZ(l) call MoveLocation(l, x2, y2) set z3 = GetLocationZ(l) call MoveLocation(l, x1, y2) set z4 = GetLocationZ(l) call AddLightningEx("DRAM",true, x1, y1, z1, x2, y1, z2) // left, top to bottom call AddLightningEx("DRAM",true, x2, y1, z2, x2, y2, z3) // bottom, left to right call AddLightningEx("DRAM",true, x2, y2, z3, x1, y2, z4) // right, bottom to top call AddLightningEx("DRAM",true, x1, y2, z4, x1, y1, z1) // top, right to left endif return this endmethod // this method is called everytime a unit enters the rect private static method eventEnter takes nothing returns nothing local thistype this = rTbl[GetTriggeringTrigger()] set .eventIRect = this set .eventUnit = GetEnteringUnit() if not .containsUnit(.eventUnit) then //enter event, the unit enters for the first time // check if an enter event is configured, if so, evaluate it to see if the unit should be added to the occupants if .enterFunc == 0 or .enterFunc.evaluate() then call GroupAddUnit(.occupants, .eventUnit) set .numberOfOccupants = .numberOfOccupants + 1 endif endif endmethod // this method is called everytime a unit leaves the rect private static method eventLeave takes nothing returns nothing local thistype this = rTbl[GetTriggeringTrigger()] set .eventIRect = this set .eventUnit = GetLeavingUnit() if .containsUnit(eventUnit) then //leave event should only fire when the unit is in the occupants group call GroupRemoveUnit(.occupants, .eventUnit) set .numberOfOccupants = .numberOfOccupants - 1 // check if a leave event is configured, if so, fire it if .leaveFunc != 0 then call .leaveFunc.execute() endif endif endmethod // create method has identical parameters as Rect() static method create takes real minX, real minY, real maxX, real maxY returns thistype local thistype this = thistype.allocate() // setup the main rect // The rect must be put on a grid, because blizzard only allows multiples of 32 for region, but still stores a value that can be anything on the rect, leading to bugs. // Also, the maxX and maxY are bugged, they are rounded to +32. Therefore, a correction is in order. The Rect is 0.01 smaller than the region. set minX = I2R(R2I((minX)/32))*32 set minY = I2R(R2I((minY)/32))*32 set maxX = I2R(R2I((maxX)/32))*32-0.01 set maxY = I2R(R2I((maxY)/32))*32-0.01 set .mainRect = Rect(minX, minY, maxX, maxY) // correction for stupid blizzard bug set .outerRect = null set .mainRegion = CreateRegion() call RegionAddRect(.mainRegion, .mainRect) set .outerRegion = CreateRegion() call RegionAddRect(.outerRegion, .mainRect) // create the group of occupants set .occupants = CreateGroup() // setup the triggers set .enterTrig = CreateTrigger() set .leaveTrig = CreateTrigger() set rTbl[.enterTrig] = this set rTbl[.leaveTrig] = this call TriggerRegisterEnterRegion(.enterTrig, .mainRegion, null) call TriggerRegisterLeaveRegion(.leaveTrig, .outerRegion, null) call TriggerAddAction(.enterTrig, function thistype.eventEnter) call TriggerAddAction(.leaveTrig, function thistype.eventLeave) return this endmethod endstruct private function Init takes nothing returns nothing set rTbl = HandleTable.create() set l = Location(0,0) set g = CreateGroup() endfunction endlibrary |
