Warcraft 3 documentation

vJASS & Zinc Documentation
For the latest documentation about how it works vJASS and Zinc language layers for Warcraft III, please follow these links:
Jasshelper documentation - Zinc documentation - WC3 Optimizer documentation

AutoIndex

moyack · 664

0 Members and 1 Guest are viewing this topic.

Rating

Average Score - 5 / 5

« Created: December 25, 2017, 09:45:32 PM by moyack »

AutoIndex
on: February 18, 2012, 05:59:47 AM
Category: Units
Language: vJASS

AutoIndex is a very simple script to utilize. Just call GetUnitId(unit) to get get the unique value assigned to a particular unit. The GetUnitId function is extremely fast because it inlines directly to a GetUnitUserData call. AutoIndex automatically assigns an ID to each unit as it enters the map, and instantly frees that ID as the unit leaves the map. Detection of leaving units is accomplished in constant time without a periodic scan.

AutoIndex uses UnitUserData by default. If something else in your map would conflict with that, you can set the UseUnitUserData configuration constant to false, and a hashtable will be used instead. Note that hashtables are about 60% slower.

If you turn on debug mode, AutoIndex will be able to display several helpful error messages. The following issues will be detected:
  • Passing a removed or decayed unit to GetUnitId
  • Code outside of AutoIndex has overwritten a unit's UserData value.
  • GetUnitId was used on a filtered unit (a unit you don't want indexed).

AutoIndex provides events upon indexing or deindexing units. This effectively allows you to notice when units enter or leave the game. Also included are the AutoData, AutoCreate, and AutoDestroy modules, which allow you to fully utilize AutoIndex's enter/leave detection capabilities in conjunction with your structs.

Code: jass
  1. library AutoIndex
  2. //===========================================================================
  3. // Information:
  4. //==============
  5. //
  6. //     AutoIndex is a very simple script to utilize. Just call GetUnitId(unit)
  7. // to get get the unique value assigned to a particular unit. The GetUnitId
  8. // function is extremely fast because it inlines directly to a GetUnitUserData
  9. // call. AutoIndex automatically assigns an ID to each unit as it enters the
  10. // map, and instantly frees that ID as the unit leaves the map. Detection of
  11. // leaving units is accomplished in constant time without a periodic scan.
  12. //
  13. //     AutoIndex uses UnitUserData by default. If something else in your map
  14. // would conflict with that, you can set the UseUnitUserData configuration
  15. // constant to false, and a hashtable will be used instead. Note that hash-
  16. // tables are about 60% slower.
  17. //
  18. //     If you turn on debug mode, AutoIndex will be able to display several
  19. // helpful error messages. The following issues will be detected:
  20. //   -Passing a removed or decayed unit to GetUnitId
  21. //   -Code outside of AutoIndex has overwritten a unit's UserData value.
  22. //   -GetUnitId was used on a filtered unit (a unit you don't want indexed).
  23. //
  24. //     AutoIndex provides events upon indexing or deindexing units. This
  25. // effectively allows you to notice when units enter or leave the game. Also
  26. // included are the AutoData, AutoCreate, and AutoDestroy modules, which allow
  27. // you to fully utilize AutoIndex's enter/leave detection capabilities in
  28. // conjunction with your structs.
  29. //
  30. //===========================================================================
  31. // How to install AutoIndex:
  32. //===========================
  33. //
  34. // 1.) Copy and paste this script into your map.
  35. // 2.) Save it to allow the ObjectMerger macro to generate the "Leave Detect"
  36. //     ability for you. Close and re-open the map. After that, disable the macro
  37. //     to prevent the delay while saving.
  38. //
  39. //===========================================================================
  40. // How to use AutoIndex:
  41. //=======================
  42. //
  43. //     So you can get a unique integer for each unit, but how do you use that to
  44. // attach data to a unit? GetUnitId will always return a number in the range of
  45. // 1-8190. This means it can be used as an array index, as demonstrated below:
  46. /*
  47.     globals
  48.         integer array IntegerData
  49.         real array RealData
  50.         SomeStruct array SomeStructData
  51.     englobals
  52.  
  53.     function Example takes nothing returns nothing
  54.         local unit u = CreateUnit(Player(0), 'hpea', 0., 0., 0.)
  55.         local integer id = GetUnitId(u)
  56.             //You now have a unique index for the unit, so you can
  57.             //attach or retrieve data about the unit using arrays.
  58.             set IntegerData[id] = 5
  59.             set RealData[id] = 25.0
  60.             set SomeStructData[id] = SomeStruct.create()
  61.             //If you have access to the same unit in another function, you can
  62.             //retrieve the data by using GetUnitId() and reading the arrays.
  63.     endfunction
  64. */
  65. //     The UnitFilter function in the configuration section is provided so that
  66. // you can make AutoIndex completely ignore certain unit-types. Ignored units
  67. // won't be indexed or fire indexed/deindexed events. You may want to filter out
  68. // dummy casters or system-private units, especially ones that use UnitUserData
  69. // internally. xe dummy units are automatically filtered.
  70. //
  71. //===========================================================================
  72. // How to use OnUnitIndexed / OnUnitDeindexed:
  73. //=============================================
  74. //
  75. //     AutoIndex will fire the OnUnitIndexed event when a unit enters the map,
  76. // and the OnUnitDeindexed event when a unit leaves the map. Functions used
  77. // as events must take a unit and return nothing. An example is given below:
  78. /*
  79.     function UnitEntersMap takes unit u returns nothing
  80.         call BJDebugMsg(GetUnitName(u)+" with ID "+I2S(GetUnitId(u))+" entered the map.")
  81.     endfunction //Using GetUnitId() during Indexed events works fine...
  82.  
  83.     function UnitLeavesMap takes unit u returns nothing
  84.         call BJDebugMsg(GetUnitName(u)+" with ID "+I2S(GetUnitId(u))+" left the map.")
  85.     endfunction  //So does using GetUnitId() during Deindexed events.
  86.  
  87.     function Init takes nothing returns nothing
  88.         call OnUnitIndexed(UnitEntersMap)
  89.         call OnUnitDeindexed(UnitLeavesMap)
  90.     endfunction
  91. */
  92. //     If you call OnUnitIndexed during map initialization, every existing
  93. // unit will be considered as entering the map. This saves you from the need
  94. // to manually enumerate preplaced units (or units created by initialization
  95. // code that ran before OnUnitIndexed was called).
  96. //
  97. //     OnUnitDeindexed runs while a unit still exists, which means you can
  98. // still do things such as destroy special effects attached to the unit.
  99. // The unit will cease to exist immediately after the event is over.
  100. //
  101. //===========================================================================
  102. // AutoIndex API:
  103. //================
  104. //
  105. // GetUnitId(unit) -> integer
  106. //   This function returns a unique ID in the range of 1-8190 for the
  107. //   specified unit. Returns 0 if a null unit was passed. This function
  108. //   inlines directly to GetUnitUserData or LoadInteger if debug mode
  109. //   is disabled. If debug mode is enabled, this function will print
  110. //   an error message when passed a decayed or filtered unit.
  111. //
  112. // IsUnitIndexed(unit) -> boolean
  113. //   This function returns a boolean indicating whether the specified
  114. //   unit has been indexed. The only time this will return false is
  115. //   for units you have filtered using the UnitFilter function, or
  116. //   for xe dummy units. You can use this function to easily detect
  117. //   dummy units and avoid performing certain actions on them.
  118. //
  119. // OnUnitIndexed(IndexFunc)
  120. //   This function accepts an IndexFunc, which must take a unit and
  121. //   return nothing. The IndexFunc will be fired instantly whenever
  122. //   a unit enters the map. You may use GetUnitId on the unit. When
  123. //   you call this function during map initialization, every existing
  124. //   unit will be considered as entering the map.
  125. //
  126. // OnUnitDeindexed(IndexFunc)
  127. //   Same as above, but runs whenever a unit is leaving the map. When
  128. //   this event runs, the unit still exists, but it will cease to exist
  129. //   as soon as the event ends. You may use GetUnitId on the unit.
  130. //
  131. //===========================================================================
  132. // How to use AutoData:
  133. //======================
  134. //
  135. //     The AutoData module allows you to associate one or more instances
  136. // of the implementing struct with units, as well as iterate through all
  137. // of the instances associated with each unit.
  138. //
  139. //     This association is accomplished through the "me" instance member,
  140. // which the module will place in the implementing struct. Whichever unit
  141. // you assign to "me" becomes the owner of that instance. You may change
  142. // ownership by reassigning "me" to another unit at any time, or you may
  143. // make the instance unowned by assigning "me" to null.
  144. //
  145. //     AutoData implements the static method operator [] in your struct
  146. // to allow you to access instances from their owning units. For example,
  147. // you may type: local StructName s = StructName[u]. If u has been set
  148. // to own an instance of StructName, s will be set to that instance.
  149. //
  150. //     So, what happens if you assign the same owning unit to multiple
  151. // instances? You may use 2D array syntax to access instances assigned to
  152. // the same unit: local StructName s = StructName[u][n], where u is the
  153. // owning unit, and n is the index beginning with 0 for each unit. You
  154. // can access the size of a unit's instance list (i.e. the number of
  155. // instances belonging to the unit) by using the .size instance member.
  156. /*
  157.     struct Example
  158.         implement AutoData
  159.         static method create takes unit u returns Example
  160.             local Example this = allocate()
  161.                 set me = u //Assigning the "me" member from AutoData.
  162.             return this
  163.         endmethod
  164.     endstruct
  165.     function Test takes nothing returns nothing
  166.         local unit u = CreateUnit(Player(0), 'hpea', 0., 0., 0.)
  167.         local Example e1 = Example.create(u)
  168.         local Example e2 = Example.create(u)
  169.         local Example e3 = Example.create(u)
  170.         local Example e
  171.             call BJDebugMsg(I2S(Example[u].size)) //Prints 3 because u owns e1, e2, and e3.
  172.             set e = Example[u][GetRandomInt(0, Example[u].size - 1)] //Random instance belonging to u.
  173.             set e = Example[u]  //This is the fastest way to iterate the instances belonging
  174.             loop                //to a specific unit, starting with the first instance.
  175.                 exitwhen e == 0 //e will be assigned to 0 when no instances remain.
  176.                 call BJDebugMsg(I2S(e)) //Prints the values of e1, e2, e3.
  177.                 set e = e[e.index + 1] //"e.index" refers to the e's position in u's instance list.
  178.             endloop                    //Thus, index + 1 is next, and index - 1 is previous.
  179.     endfunction                        //This trick allows you to avoid a local counter.
  180. */
  181. //   AutoData restrictions:
  182. //   -You may not implement AutoData in any struct which has already
  183. //    declared static or non-static method operator [].
  184. //   -AutoData will conflict with anything named "me", "size", or
  185. //    "index" in the implementing struct.
  186. //   -AutoData may not be implemented in structs that extend array.
  187. //   -You may not declare your own destroy method. (This restriction
  188. //    can be dropped as soon as JassHelper supports module onDestroy).
  189. //
  190. //   AutoData information:
  191. //   -You do not need to null the "me" member when destroying an
  192. //    instance. That is done for you automatically during destroy().
  193. //    (But if you use deallocate(), you must null "me" manually.)
  194. //   -StructName[u] and StructName[u][0] refer to the same instance,
  195. //    which is the first instance that was associated with unit u.
  196. //   -StructName[u][StructName[u].size - 1] refers to the instance that
  197. //    was most recently associated with unit u.
  198. //   -Instances keep their relative order in the list when one is removed.
  199. //
  200. //===========================================================================
  201. // How to use AutoCreate:
  202. //=======================
  203. //
  204. //     The AutoCreate module allows you to automatically create instances
  205. // of the implementing struct for units as they enter the game. AutoCreate
  206. // automatically implements AutoData into your struct. Any time an instance
  207. // is automatically created for a unit, that instance's "me" member will be
  208. // assigned to the entering unit.
  209. //
  210. //   AutoCreate restrictions:
  211. //   -All of the same restrictions as AutoData.
  212. //   -If your struct's allocate() method takes parameters (i.e. the parent
  213. //    type's create method takes parameters), you must declare a create
  214. //    method and pass those extra parameters to allocate yourself.
  215. //
  216. //   AutoCreate information:
  217. //   -You may optionally declare the createFilter method, which specifies
  218. //    which units should recieve an instance as they enter the game. If
  219. //    you do not declare it, all entering units will recieve an instance.
  220. //   -You may optionally declare the onCreate method, which will run when
  221. //    AutoCreate automatically creates an instance. (This is just a stand-
  222. //    in until JassHelper supports the onCreate method.)
  223. //   -You may declare your own create method, but it must take a single
  224. //    unit parameter (the entering unit) if you do so.
  225. /*
  226.     struct Example
  227.         private static method createFilter takes unit u returns boolean
  228.             return GetUnitTypeId(u) == 'hfoo' //Created only for Footmen.
  229.         endmethod
  230.         private method onCreate takes nothing returns nothing
  231.             call BJDebugMsg(GetUnitName(me)+" entered the game!")
  232.         endmethod
  233.         implement AutoCreate
  234.     endstruct
  235. */
  236. //===========================================================================
  237. // How to use AutoDestroy:
  238. //=========================
  239. //
  240. //     The AutoDestroy module allows you to automatically destroy instances
  241. // of the implementing struct when their "me" unit leaves the game. AutoDestroy
  242. // automatically implements AutoData into your struct. You must assign a unit
  243. // to the "me" member of an instance for this module to have any effect.
  244. //
  245. //   AutoDestroy restrictions:
  246. //   -All of the same restrictions as AutoData.
  247. //
  248. //   AutoDestroy information:
  249. //   -If you also implement AutoCreate in the same struct, remember that it
  250. //    assigns the "me" unit automatically. That means you can have fully
  251. //    automatic creation and destruction.
  252. /*
  253.     struct Example
  254.         static method create takes unit u returns Example
  255.             local Example this = allocate()
  256.                 set me = u //You should assign a unit to "me",
  257.             return this    //otherwise AutoDestroy does nothing.
  258.         endmethod          //Not necessary if using AutoCreate.
  259.         private method onDestroy takes nothing returns nothing
  260.             call BJDebugMsg(GetUnitName(me)+" left the game!")
  261.         endmethod
  262.         implement AutoDestroy
  263.     endstruct
  264. */
  265. //===========================================================================
  266. // Configuration:
  267. //================
  268.  
  269. //! external ObjectMerger w3a Adef lvdt anam "Leave Detect" aart "" arac 0
  270. //Save your map with this Object Merger call enabled, then close and reopen your
  271. //map. Disable it by removing the exclamation to remove the delay while saving.
  272.  
  273. globals
  274.     private constant integer LeaveDetectAbilityID = 'lvdt'
  275.     //This rawcode must match the parameter after "Adef" in the
  276.     //ObjectMerger macro above. You may change both if you want.
  277.    
  278.     private constant boolean UseUnitUserData = true
  279.     //If this is set to true, UnitUserData will be used. You should only set
  280.     //this to false if something else in your map already uses UnitUserData.
  281.     //A hashtable will be used instead, but it is about 60% slower.
  282.    
  283.     private constant boolean SafeMode = true
  284.     //This is set to true by default so that GetUnitId() will ALWAYS work.
  285.     //If if this is set to false, GetUnitId() may fail to work in a very
  286.     //rare circumstance: creating a unit that has a default-on autocast
  287.     //ability, and using GetUnitId() on that unit as it enters the game,
  288.     //within a trigger that detects any order. Set this to false for a
  289.     //performance boost only if you think you can avoid this issue.
  290.    
  291.     private constant boolean AutoDataFastMode = true
  292.     //If this is set to true, AutoData will utilize one hashtable per time
  293.     //it is implemented. If this is set to false, all AutoDatas will share
  294.     //a single hashtable, but iterating through the instances belonging to
  295.     //a unit will become about 12.5% slower. Your map will break if you
  296.     //use more than 255 hashtables simultaneously. Only set this to false
  297.     //if you suspect you will run out of hashtable instances.
  298. endglobals
  299.  
  300. private function UnitFilter takes unit u returns boolean
  301.     return true
  302. endfunction
  303. //Make this function return false for any unit-types you want to ignore.
  304. //Ignored units won't be indexed or fire OnUnitIndexed/OnUnitDeindexed
  305. //events. The unit parameter "u" to refers to the unit being filtered.
  306. //Do not filter out xe dummy units; they are automatically filtered.
  307.  
  308. //===========================================================================
  309. // AutoData / AutoCreate / AutoDestroy modules:
  310. //==============================================
  311.  
  312. function interface AutoCreator takes unit u returns nothing
  313. function interface AutoDestroyer takes unit u returns nothing
  314.  
  315. globals                
  316.     hashtable AutoData = null //If AutoDataFastMode is disabled, this hashtable will be
  317. endglobals                    //initialized and shared between all AutoData implementations.
  318.  
  319. module AutoData
  320.     private static hashtable ht
  321.     private static thistype array data
  322.     private static integer array listsize
  323.     private static key typeid //Good thing keys exist to identify each implementing struct.
  324.     private unit meunit
  325.     private integer id
  326.    
  327.     readonly integer index //The user can avoid using a local counter because this is accessable.
  328.    
  329.     static method operator [] takes unit u returns thistype
  330.         return data[GetUnitId(u)]
  331.     endmethod //This is as fast as retrieving an instance from a unit gets.
  332.    
  333.     method operator [] takes integer index returns thistype
  334.         static if AutoDataFastMode then //If fast mode is enabled...
  335.             return LoadInteger(ht, id, index)
  336.         else //Each instance has its own hashtable to associate unit and index.
  337.             return LoadInteger(AutoData, id, index*8190+typeid)
  338.         endif //Otherwise, simulate a 3D array associating unit, struct-type ID, and index.
  339.     endmethod //Somehow, this version is 12.5% slower just because of the math.
  340.    
  341.     private method setIndex takes integer index, thistype data returns nothing
  342.         static if AutoDataFastMode then //Too bad you can't have a module-private operator []=.
  343.             call SaveInteger(ht, id, index, data)
  344.         else
  345.             call SaveInteger(AutoData, id, index*8190+typeid, data)
  346.         endif
  347.     endmethod
  348.    
  349.     private method remove takes nothing returns nothing
  350.         if meunit == null then //If the struct doesn't have an owner...
  351.             return             //Nothing needs to be done.
  352.         endif
  353.         loop
  354.             exitwhen index == listsize[id]        //The last value gets overwritten by 0.
  355.             call setIndex(index, this[index + 1]) //Shift each element down by one.
  356.             set this[index].index = index         //Update the shifted instance's index.
  357.             set index = index + 1
  358.         endloop
  359.         set listsize[id] = listsize[id] - 1
  360.         set data[id] = this[0] //Ensure thistype[u] returns the same value as thistype[u][0].
  361.         set meunit = null
  362.     endmethod
  363.    
  364.     private method add takes unit u returns nothing
  365.         if meunit != null then     //If the struct has an owner...
  366.             call remove()          //remove it first.
  367.         endif
  368.         set meunit = u
  369.         set id = GetUnitId(u)      //Cache GetUnitId for slight performance boost.
  370.         if data[id] == 0 then      //If this is the first instance for this unit...
  371.             set data[id] = this    //Update the value that thistype[u] returns.
  372.         endif
  373.         set index = listsize[id]   //Remember the index for removal.
  374.         call setIndex(index, this) //Add to the array.
  375.         set listsize[id] = index + 1
  376.     endmethod
  377.    
  378.     method operator me takes nothing returns unit
  379.         return meunit
  380.     endmethod
  381.    
  382.     method operator me= takes unit u returns nothing
  383.         if u != null then //If assigning "me" a non-null value...
  384.             call add(u)   //Add this instance to that unit's array.
  385.         else              //If assigning "me" a null value...
  386.             call remove() //Remove this instance from that unit's array.
  387.         endif
  388.     endmethod
  389.    
  390.     method operator size takes nothing returns integer
  391.         return listsize[id]
  392.     endmethod
  393.    
  394.     method destroy takes nothing returns nothing
  395.         call deallocate()
  396.         call remove() //This makes removal automatic when an instance is destroyed.
  397.     endmethod
  398.    
  399.     private static method onInit takes nothing returns nothing
  400.         static if AutoDataFastMode then        //If fast mode is enabled...
  401.             set ht = InitHashtable()           //Initialize one hashtable per instance.
  402.         else                                   //If fast mode is disabled...
  403.             if AutoData == null then           //If the hashtable hasn't been initialized yet...
  404.                 set AutoData = InitHashtable() //Initialize the shared hashtable.
  405.             endif
  406.         endif
  407.     endmethod
  408. endmodule
  409.  
  410. module AutoCreate
  411.     implement AutoData //AutoData is necessary for AutoCreate.
  412.  
  413.     private static method creator takes unit u returns nothing
  414.         local thistype this
  415.         local boolean b = true                          //Assume that the instance will be created.
  416.             static if thistype.createFilter.exists then //If createFilter exists...
  417.                 set b = createFilter(u)                 //evaluate it and update b.
  418.             endif
  419.             if b then                                   //If the instance should be created...
  420.                 static if thistype.create.exists then   //If the create method exists...
  421.                     set this = create(u)                //Create the instance, passing the entering unit.
  422.                 else                                    //If the create method doesn't exist...
  423.                     set this = allocate()               //Just allocate the instance.
  424.                 endif
  425.                 set me = u                              //Assign the instance's owner as the entering unit.
  426.                 static if thistype.onCreate.exists then //If onCreate exists...
  427.                     call onCreate()                     //Call it, because JassHelper should do this anyway.
  428.                 endif
  429.             endif
  430.     endmethod
  431.  
  432.     private static method onInit takes nothing returns nothing
  433.         call AutoIndex.addAutoCreate(thistype.creator)
  434.     endmethod //During module initialization, pass the creator function to AutoIndex.
  435. endmodule
  436.  
  437. module AutoDestroy
  438.     implement AutoData //AutoData is necessary for AutoDestroy.
  439.    
  440.     static method destroyer takes unit u returns nothing
  441.         loop
  442.             exitwhen thistype[u] == 0
  443.             call thistype[u].destroy()
  444.         endloop
  445.     endmethod //Destroy each instance owned by the unit until none are left.
  446.  
  447.     private static method onInit takes nothing returns nothing
  448.         call AutoIndex.addAutoDestroy(thistype.destroyer)
  449.     endmethod //During module initialization, pass the destroyer function to AutoIndex.
  450. endmodule
  451.  
  452. //===========================================================================
  453. // AutoIndex struct:
  454. //===================
  455.  
  456. function interface IndexFunc takes unit u returns nothing
  457.  
  458. hook RemoveUnit AutoIndex.hook_RemoveUnit
  459. hook ReplaceUnitBJ AutoIndex.hook_ReplaceUnitBJ
  460. debug hook SetUnitUserData AutoIndex.hook_SetUnitUserData
  461.  
  462. private keyword getIndex
  463. private keyword getIndexDebug
  464. private keyword isUnitIndexed
  465. private keyword onUnitIndexed
  466. private keyword onUnitDeindexed
  467.  
  468. struct AutoIndex
  469.     private static trigger   enter      = CreateTrigger()
  470.     private static trigger   order      = CreateTrigger()
  471.     private static trigger   creepdeath = CreateTrigger()
  472.     private static group     preplaced  = CreateGroup()
  473.     private static timer     allowdecay = CreateTimer()
  474.     private static hashtable ht
  475.  
  476.     private static boolean array dead
  477.     private static boolean array summoned
  478.     private static boolean array animated
  479.     private static boolean array nodecay
  480.     private static boolean array removing
  481.    
  482.     private static IndexFunc array indexfuncs
  483.     private static integer indexfuncs_n = -1
  484.     private static IndexFunc array deindexfuncs
  485.     private static integer deindexfuncs_n = -1
  486.     private static IndexFunc indexfunc
  487.    
  488.     private static AutoCreator array creators
  489.     private static integer creators_n = -1
  490.     private static AutoDestroyer array destroyers
  491.     private static integer destroyers_n = -1
  492.    
  493.     private static unit array allowdecayunit
  494.     private static integer allowdecay_n = -1
  495.    
  496.     private static boolean duringinit = true
  497.     private static boolean array altered
  498.     private static unit array idunit
  499.    
  500.     //===========================================================================
  501.  
  502.     static method getIndex takes unit u returns integer
  503.         static if UseUnitUserData then
  504.             return GetUnitUserData(u)
  505.         else
  506.             return LoadInteger(ht, 0, GetHandleId(u))
  507.         endif
  508.     endmethod //Resolves to an inlinable one-liner after the static if.
  509.    
  510.     static method getIndexDebug takes unit u returns integer
  511.             if u == null then
  512.                 return 0
  513.             elseif GetUnitTypeId(u) == 0 then
  514.                 call BJDebugMsg("AutoIndex error: Removed or decayed unit passed to GetUnitId.")
  515.             elseif idunit[getIndex(u)] != u and GetIssuedOrderId() != 852056 then
  516.                 call BJDebugMsg("AutoIndex error: "+GetUnitName(u)+" is a filtered unit.")
  517.             endif
  518.         return getIndex(u)
  519.     endmethod //If debug mode is enabled, use the getIndex method that shows errors.
  520.    
  521.     private static method setIndex takes unit u, integer index returns nothing
  522.         static if UseUnitUserData then
  523.             call SetUnitUserData(u, index)
  524.         else
  525.             call SaveInteger(ht, 0, GetHandleId(u), index)
  526.         endif
  527.     endmethod //Resolves to an inlinable one-liner after the static if.
  528.    
  529.     static method isUnitIndexed takes unit u returns boolean
  530.         return u != null and idunit[getIndex(u)] == u
  531.     endmethod
  532.    
  533.     static method isUnitAnimateDead takes unit u returns boolean
  534.         return animated[getIndex(u)]
  535.     endmethod //Don't use this; use IsUnitAnimateDead from AutoEvents instead.
  536.    
  537.     //===========================================================================
  538.    
  539.     private static method onUnitIndexed_sub takes nothing returns nothing
  540.         call indexfunc.evaluate(GetEnumUnit())
  541.     endmethod
  542.     static method onUnitIndexed takes IndexFunc func returns nothing
  543.         set indexfuncs_n = indexfuncs_n + 1
  544.         set indexfuncs[indexfuncs_n] = func
  545.         if duringinit then //During initialization, evaluate the indexfunc for every preplaced unit.
  546.             set indexfunc = func
  547.             call ForGroup(preplaced, function AutoIndex.onUnitIndexed_sub)
  548.         endif
  549.     endmethod
  550.    
  551.     static method onUnitDeindexed takes IndexFunc func returns nothing
  552.         set deindexfuncs_n = deindexfuncs_n + 1
  553.         set deindexfuncs[deindexfuncs_n] = func
  554.     endmethod
  555.    
  556.     static method addAutoCreate takes AutoCreator func returns nothing
  557.         set creators_n = creators_n + 1
  558.         set creators[creators_n] = func
  559.     endmethod
  560.    
  561.     static method addAutoDestroy takes AutoDestroyer func returns nothing
  562.         set destroyers_n = destroyers_n + 1
  563.         set destroyers[destroyers_n] = func
  564.     endmethod
  565.    
  566.     //===========================================================================
  567.    
  568.     private static method hook_RemoveUnit takes unit whichUnit returns nothing
  569.         set removing[getIndex(whichUnit)] = true
  570.     endmethod //Intercepts whenever RemoveUnit is called and sets a flag.
  571.     private static method hook_ReplaceUnitBJ takes unit whichUnit, integer newUnitId, integer unitStateMethod returns nothing
  572.         set removing[getIndex(whichUnit)] = true
  573.     endmethod //Intercepts whenever ReplaceUnitBJ is called and sets a flag.
  574.    
  575.     private static method hook_SetUnitUserData takes unit whichUnit, integer data returns nothing
  576.         static if UseUnitUserData then
  577.             if idunit[getIndex(whichUnit)] == whichUnit then
  578.                 if getIndex(whichUnit) == data then
  579.                     call BJDebugMsg("AutoIndex error: Code outside AutoIndex attempted to alter "+GetUnitName(whichUnit)+"'s index.")
  580.                 else
  581.                     call BJDebugMsg("AutoIndex error: Code outside AutoIndex altered "+GetUnitName(whichUnit)+"'s index.")
  582.                     if idunit[data] != null then
  583.                         call BJDebugMsg("AutoIndex error: "+GetUnitName(whichUnit)+" and "+GetUnitName(idunit[data])+" now have the same index.")
  584.                     endif
  585.                     set altered[data] = true
  586.                 endif
  587.             endif
  588.         endif //In debug mode, intercepts whenever SetUnitUserData is used on an indexed unit.
  589.     endmethod //Displays an error message if outside code tries to alter a unit's index.
  590.    
  591.     //===========================================================================
  592.    
  593.     private static method allowDecay takes nothing returns nothing
  594.         local integer n = allowdecay_n
  595.             loop
  596.                 exitwhen n < 0
  597.                 set nodecay[getIndex(allowdecayunit[n])] = false
  598.                 set allowdecayunit[n] = null
  599.                 set n = n - 1
  600.             endloop
  601.             set allowdecay_n = -1
  602.     endmethod //Iterate through all the units in the stack and allow them to decay again.
  603.    
  604.     private static method detectStatus takes nothing returns boolean
  605.         local unit u = GetTriggerUnit()
  606.         local integer index = getIndex(u)
  607.         local integer n
  608.            
  609.             if idunit[index] == u then //Ignore non-indexed units.
  610.                 if not IsUnitType(u, UNIT_TYPE_DEAD) then
  611.                
  612.                     if dead[index] then         //The unit was dead, but now it's alive.
  613.                         set dead[index] = false //The unit has been resurrected.
  614.                         //! runtextmacro optional RunAutoEvent("Resurrect")
  615.                         //If AutoEvents is in the map, run the resurrection events.
  616.                        
  617.                         if IsUnitType(u, UNIT_TYPE_SUMMONED) and not summoned[index] then
  618.                             set summoned[index] = true //If the unit gained the summoned flag,
  619.                             set animated[index] = true //it's been raised with Animate Dead.
  620.                             //! runtextmacro optional RunAutoEvent("AnimateDead")
  621.                             //If AutoEvents is in the map, run the Animate Dead events.
  622.                         endif
  623.                     endif
  624.                 else
  625.                
  626.                     if not removing[index] and not dead[index] and not animated[index] then
  627.                         set dead[index] = true               //The unit was alive, but now it's dead.
  628.                         set nodecay[index] = true            //A dead unit can't decay for at least 0. seconds.
  629.                         set allowdecay_n = allowdecay_n + 1  //Add the unit to a stack. After the timer
  630.                         set allowdecayunit[allowdecay_n] = u //expires, allow the unit to decay again.
  631.                         call TimerStart(allowdecay, 0., false, function AutoIndex.allowDecay)
  632.                         //! runtextmacro optional RunAutoEvent("Death")
  633.                         //If AutoEvents is in the map, run the Death events.
  634.                        
  635.                     elseif removing[index] or (dead[index] and not nodecay[index]) or (not dead[index] and animated[index]) then
  636.                         //If .nodecay was false and the unit is dead and was previously dead, the unit decayed.
  637.                         //If .animated was true and the unit is dead, the unit died and exploded.
  638.                         //If .removing was true, the unit is being removed or replaced.
  639.                         set n = deindexfuncs_n
  640.                         loop //Run the OnUnitDeindexed events.
  641.                             exitwhen n < 0
  642.                             call deindexfuncs[n].evaluate(u)
  643.                             set n = n - 1
  644.                         endloop
  645.                         set n = destroyers_n
  646.                         loop //Destroy AutoDestroy structs for the leaving unit.
  647.                             exitwhen n < 0
  648.                             call destroyers[n].evaluate(u)
  649.                             set n = n - 1
  650.                         endloop
  651.                         call AutoIndex(index).destroy() //Free the index by destroying the AutoIndex struct.
  652.                         set idunit[index] = null        //Null this unit reference to prevent a leak.
  653.                     endif
  654.                 endif
  655.             endif
  656.         set u = null
  657.         return false
  658.     endmethod
  659.  
  660.     //===========================================================================
  661.    
  662.     private static method unitEntersMap takes unit u returns nothing
  663.         local integer index
  664.         local integer n = 0
  665.             if getIndex(u) != 0 then
  666.                 return //Don't index a unit that already has an ID.
  667.             endif
  668.             static if LIBRARY_xebasic then
  669.                 if GetUnitTypeId(u) == XE_DUMMY_UNITID then
  670.                     return //Don't index xe dummy units.
  671.                 endif
  672.             endif
  673.             if not UnitFilter(u) then
  674.                 return //Don't index units that fail the unit filter.
  675.             endif
  676.             set index = create()
  677.             call setIndex(u, index) //Assign an index to the entering unit.
  678.            
  679.             call UnitAddAbility(u, LeaveDetectAbilityID)                 //Add the leave detect ability to the entering unit.
  680.             call UnitMakeAbilityPermanent(u, true, LeaveDetectAbilityID) //Prevent it from disappearing on morph.
  681.             set dead[index] = IsUnitType(u, UNIT_TYPE_DEAD)              //Reset all of the flags for the entering unit.
  682.             set summoned[index] = IsUnitType(u, UNIT_TYPE_SUMMONED)      //Each of these flags are necessary to detect
  683.             set animated[index] = false                                  //when a unit leaves the map.
  684.             set nodecay[index] = false
  685.             set removing[index] = false
  686.             debug set altered[index] = false    //In debug mode, this flag tracks wheter a unit's index was altered.
  687.             set idunit[index] = u               //Attach the unit that is supposed to have this index to the index.
  688.            
  689.             if duringinit then                  //If a unit enters the map during initialization...
  690.                 call GroupAddUnit(preplaced, u) //Add the unit to the preplaced units group. This ensures that
  691.             endif                               //all units are noticed by OnUnitIndexed during initialization.
  692.             loop //Create AutoCreate structs for the entering unit.
  693.                 exitwhen n > creators_n
  694.                 call creators[n].evaluate(u)
  695.                 set n = n + 1
  696.             endloop
  697.             set n = 0
  698.             loop //Run the OnUnitIndexed events.
  699.                 exitwhen n > indexfuncs_n
  700.                 call indexfuncs[n].evaluate(u)
  701.                 set n = n + 1
  702.             endloop
  703.     endmethod
  704.    
  705.     private static method onIssuedOrder takes nothing returns boolean
  706.             static if SafeMode then     //If SafeMode is enabled, perform this extra check.
  707.                 if getIndex(GetTriggerUnit()) == 0 then  //If the unit doesn't already have
  708.                     call unitEntersMap(GetTriggerUnit()) //an index, then assign it one.
  709.                 endif
  710.             endif
  711.         return GetIssuedOrderId() == 852056 //If the order is Undefend, allow detectStatus to run.
  712.     endmethod
  713.    
  714.     private static method initEnteringUnit takes nothing returns boolean
  715.             call unitEntersMap(GetFilterUnit())
  716.         return false
  717.     endmethod
  718.    
  719.     //===========================================================================
  720.    
  721.     private static method afterInit takes nothing returns nothing
  722.         set duringinit = false               //Initialization is over; set a flag.
  723.         call DestroyTimer(GetExpiredTimer()) //Destroy the timer.
  724.         call GroupClear(preplaced)           //The preplaced units group is
  725.         call DestroyGroup(preplaced)         //no longer needed, so clean it.
  726.         set preplaced = null
  727.     endmethod
  728.    
  729.     private static method onInit takes nothing returns nothing
  730.         local region maparea = CreateRegion()
  731.         local rect bounds = GetWorldBounds()
  732.         local group g = CreateGroup()
  733.         local integer i = 15
  734.             static if not UseUnitUserData then
  735.                 set ht = InitHashtable() //Only create a hashtable if it will be used.
  736.             endif
  737.             loop
  738.                 exitwhen i < 0
  739.                 call SetPlayerAbilityAvailable(Player(i), LeaveDetectAbilityID, false)
  740.                 //Make the LeaveDetect ability unavailable so that it doesn't show up on the command card of every unit.
  741.                 call TriggerRegisterPlayerUnitEvent(order, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
  742.                 //Register the "EVENT_PLAYER_UNIT_ISSUED_ORDER" event for each player.
  743.                 call GroupEnumUnitsOfPlayer(g, Player(i), function AutoIndex.initEnteringUnit)
  744.                 //Enum every non-filtered unit on the map during initialization and assign it a unique
  745.                 //index. By using GroupEnumUnitsOfPlayer, even units with Locust can be detected.
  746.                 set i = i - 1
  747.             endloop
  748.             call TriggerAddCondition(order, And(function AutoIndex.onIssuedOrder, function AutoIndex.detectStatus))
  749.             //The detectStatus method will fire every time a non-filtered unit recieves an undefend order.
  750.             //And() is used here to avoid using a trigger action, which starts a new thread and is slower.
  751.             call TriggerRegisterPlayerUnitEvent(creepdeath, Player(12), EVENT_PLAYER_UNIT_DEATH, null)
  752.             call TriggerAddCondition(creepdeath, function AutoIndex.detectStatus)
  753.             //The detectStatus method must also fire when a neutral hostile creep dies, in case it was
  754.             //sleeping. Sleeping creeps don't fire undefend orders on non-damaging deaths.
  755.             call RegionAddRect(maparea, bounds) //GetWorldBounds() includes the shaded boundry areas.
  756.             call TriggerRegisterEnterRegion(enter, maparea, function AutoIndex.initEnteringUnit)
  757.             //The filter function of an EnterRegion trigger runs instantly when a unit is created.
  758.             call TimerStart(CreateTimer(), 0., false, function AutoIndex.afterInit)
  759.             //After any time elapses, perform after-initialization actions.
  760.         call GroupClear(g)
  761.         call DestroyGroup(g)
  762.         call RemoveRect(bounds)
  763.         set g = null
  764.         set bounds = null
  765.     endmethod
  766.    
  767. endstruct
  768.  
  769. //===========================================================================
  770. // User functions:
  771. //=================
  772.  
  773. function GetUnitId takes unit u returns integer
  774.     static if DEBUG_MODE then             //If debug mode is enabled...
  775.         return AutoIndex.getIndexDebug(u) //call the debug version of GetUnitId.
  776.     else                                  //If debug mode is disabled...
  777.         return AutoIndex.getIndex(u)      //call the normal, inlinable version.
  778.     endif
  779. endfunction
  780.  
  781. function IsUnitIndexed takes unit u returns boolean
  782.     return AutoIndex.isUnitIndexed(u)
  783. endfunction
  784.  
  785. function OnUnitIndexed takes IndexFunc func returns nothing
  786.     call AutoIndex.onUnitIndexed(func)
  787. endfunction
  788.  
  789. function OnUnitDeindexed takes IndexFunc func returns nothing
  790.     call AutoIndex.onUnitDeindexed(func)
  791. endfunction
  792.  
  793. endlibrary
  794.  
  795.  
« Last Edit: December 25, 2017, 09:51:08 PM by moyack »



 

Vivir aprendiendo.co - A place for learning stuff, in Spanish   Chaos Realm - The world of Game modders and wc3 addicts   Diplo, a gaming community   WC3JASS.com - The JASS Vault + vJASS and Zinc   Jetcraft - A Starcraft II mod   WormTastic Clan (wTc)