14 years
edited 8 years
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:
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.
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.
1 2 library AutoIndex3 //===========================================================================4 // Information:5 //==============6 //7 // AutoIndex is a very simple script to utilize. Just call GetUnitId(unit)8 // to get get the unique value assigned to a particular unit. The GetUnitId9 // function is extremely fast because it inlines directly to a GetUnitUserData10 // call. AutoIndex automatically assigns an ID to each unit as it enters the11 // map, and instantly frees that ID as the unit leaves the map. Detection of12 // leaving units is accomplished in constant time without a periodic scan.13 //14 // AutoIndex uses UnitUserData by default. If something else in your map15 // would conflict with that, you can set the UseUnitUserData configuration16 // constant to false, and a hashtable will be used instead. Note that hash-17 // tables are about 60% slower.18 //19 // If you turn on debug mode, AutoIndex will be able to display several20 // helpful error messages. The following issues will be detected:21 // -Passing a removed or decayed unit to GetUnitId22 // -Code outside of AutoIndex has overwritten a unit's UserData value.23 // -GetUnitId was used on a filtered unit (a unit you don't want indexed).24 //25 // AutoIndex provides events upon indexing or deindexing units. This26 // effectively allows you to notice when units enter or leave the game. Also27 // included are the AutoData, AutoCreate, and AutoDestroy modules, which allow28 // you to fully utilize AutoIndex's enter/leave detection capabilities in29 // conjunction with your structs.30 //31 //===========================================================================32 // How to install AutoIndex:33 //===========================34 //35 // 1.) Copy and paste this script into your map.36 // 2.) Save it to allow the ObjectMerger macro to generate the "Leave Detect"37 // ability for you. Close and re-open the map. After that, disable the macro38 // to prevent the delay while saving.39 //40 //===========================================================================41 // How to use AutoIndex:42 //=======================43 //44 // So you can get a unique integer for each unit, but how do you use that to45 // attach data to a unit? GetUnitId will always return a number in the range of46 // 1-8190. This means it can be used as an array index, as demonstrated below:47 /*48 globals49 integer array IntegerData50 real array RealData51 SomeStruct array SomeStructData52 englobals53
54 function Example takes nothing returns nothing55 local unit u = CreateUnit(Player(0), 'hpea', 0., 0., 0.)56 local integer id = GetUnitId(u)57 //You now have a unique index for the unit, so you can58 //attach or retrieve data about the unit using arrays.59 set IntegerData[id] = 560 set RealData[id] = 25.061 set SomeStructData[id] = SomeStruct.create()62 //If you have access to the same unit in another function, you can63 //retrieve the data by using GetUnitId() and reading the arrays.64 endfunction65 */66 // The UnitFilter function in the configuration section is provided so that67 // you can make AutoIndex completely ignore certain unit-types. Ignored units68 // won't be indexed or fire indexed/deindexed events. You may want to filter out69 // dummy casters or system-private units, especially ones that use UnitUserData70 // internally. xe dummy units are automatically filtered.71 //72 //===========================================================================73 // How to use OnUnitIndexed / OnUnitDeindexed:74 //=============================================75 //76 // AutoIndex will fire the OnUnitIndexed event when a unit enters the map,77 // and the OnUnitDeindexed event when a unit leaves the map. Functions used78 // as events must take a unit and return nothing. An example is given below:79 /*80 function UnitEntersMap takes unit u returns nothing81 call BJDebugMsg(GetUnitName(u)+" with ID "+I2S(GetUnitId(u))+" entered the map.")82 endfunction //Using GetUnitId() during Indexed events works fine...83
84 function UnitLeavesMap takes unit u returns nothing85 call BJDebugMsg(GetUnitName(u)+" with ID "+I2S(GetUnitId(u))+" left the map.")86 endfunction //So does using GetUnitId() during Deindexed events.87
88 function Init takes nothing returns nothing89 call OnUnitIndexed(UnitEntersMap)90 call OnUnitDeindexed(UnitLeavesMap)91 endfunction92 */93 // If you call OnUnitIndexed during map initialization, every existing94 // unit will be considered as entering the map. This saves you from the need95 // to manually enumerate preplaced units (or units created by initialization96 // code that ran before OnUnitIndexed was called).97 //98 // OnUnitDeindexed runs while a unit still exists, which means you can99 // still do things such as destroy special effects attached to the unit.100 // The unit will cease to exist immediately after the event is over.101 //102 //===========================================================================103 // AutoIndex API:104 //================105 //106 // GetUnitId(unit) -> integer107 // This function returns a unique ID in the range of 1-8190 for the108 // specified unit. Returns 0 if a null unit was passed. This function109 // inlines directly to GetUnitUserData or LoadInteger if debug mode110 // is disabled. If debug mode is enabled, this function will print111 // an error message when passed a decayed or filtered unit.112 //113 // IsUnitIndexed(unit) -> boolean114 // This function returns a boolean indicating whether the specified115 // unit has been indexed. The only time this will return false is116 // for units you have filtered using the UnitFilter function, or117 // for xe dummy units. You can use this function to easily detect118 // dummy units and avoid performing certain actions on them.119 //120 // OnUnitIndexed(IndexFunc)121 // This function accepts an IndexFunc, which must take a unit and122 // return nothing. The IndexFunc will be fired instantly whenever123 // a unit enters the map. You may use GetUnitId on the unit. When124 // you call this function during map initialization, every existing125 // unit will be considered as entering the map.126 //127 // OnUnitDeindexed(IndexFunc)128 // Same as above, but runs whenever a unit is leaving the map. When129 // this event runs, the unit still exists, but it will cease to exist130 // as soon as the event ends. You may use GetUnitId on the unit.131 //132 //===========================================================================133 // How to use AutoData:134 //======================135 //136 // The AutoData module allows you to associate one or more instances137 // of the implementing struct with units, as well as iterate through all138 // of the instances associated with each unit.139 //140 // This association is accomplished through the "me" instance member,141 // which the module will place in the implementing struct. Whichever unit142 // you assign to "me" becomes the owner of that instance. You may change143 // ownership by reassigning "me" to another unit at any time, or you may144 // make the instance unowned by assigning "me" to null.145 //146 // AutoData implements the static method operator [] in your struct147 // to allow you to access instances from their owning units. For example,148 // you may type: local StructName s = StructName[u]. If u has been set149 // to own an instance of StructName, s will be set to that instance.150 //151 // So, what happens if you assign the same owning unit to multiple152 // instances? You may use 2D array syntax to access instances assigned to153 // the same unit: local StructName s = StructName[u][n], where u is the154 // owning unit, and n is the index beginning with 0 for each unit. You155 // can access the size of a unit's instance list (i.e. the number of156 // instances belonging to the unit) by using the .size instance member.157 /*158 struct Example159 implement AutoData160 static method create takes unit u returns Example161 local Example this = allocate()162 set me = u //Assigning the "me" member from AutoData.163 return this164 endmethod165 endstruct166 function Test takes nothing returns nothing167 local unit u = CreateUnit(Player(0), 'hpea', 0., 0., 0.)168 local Example e1 = Example.create(u)169 local Example e2 = Example.create(u)170 local Example e3 = Example.create(u)171 local Example e172 call BJDebugMsg(I2S(Example[u].size)) //Prints 3 because u owns e1, e2, and e3.173 set e = Example[u][GetRandomInt(0, Example[u].size - 1)] //Random instance belonging to u.174 set e = Example[u] //This is the fastest way to iterate the instances belonging175 loop //to a specific unit, starting with the first instance.176 exitwhen e == 0 //e will be assigned to 0 when no instances remain.177 call BJDebugMsg(I2S(e)) //Prints the values of e1, e2, e3.178 set e = e[e.index + 1] //"e.index" refers to the e's position in u's instance list.179 endloop //Thus, index + 1 is next, and index - 1 is previous.180 endfunction //This trick allows you to avoid a local counter.181 */182 // AutoData restrictions:183 // -You may not implement AutoData in any struct which has already184 // declared static or non-static method operator [].185 // -AutoData will conflict with anything named "me", "size", or186 // "index" in the implementing struct.187 // -AutoData may not be implemented in structs that extend array.188 // -You may not declare your own destroy method. (This restriction189 // can be dropped as soon as JassHelper supports module onDestroy).190 //191 // AutoData information:192 // -You do not need to null the "me" member when destroying an193 // instance. That is done for you automatically during destroy().194 // (But if you use deallocate(), you must null "me" manually.)195 // -StructName[u] and StructName[u][0] refer to the same instance,196 // which is the first instance that was associated with unit u.197 // -StructName[u][StructName[u].size - 1] refers to the instance that198 // was most recently associated with unit u.199 // -Instances keep their relative order in the list when one is removed.200 //201 //===========================================================================202 // How to use AutoCreate:203 //=======================204 //205 // The AutoCreate module allows you to automatically create instances206 // of the implementing struct for units as they enter the game. AutoCreate207 // automatically implements AutoData into your struct. Any time an instance208 // is automatically created for a unit, that instance's "me" member will be209 // assigned to the entering unit.210 //211 // AutoCreate restrictions:212 // -All of the same restrictions as AutoData.213 // -If your struct's allocate() method takes parameters (i.e. the parent214 // type's create method takes parameters), you must declare a create215 // method and pass those extra parameters to allocate yourself.216 //217 // AutoCreate information:218 // -You may optionally declare the createFilter method, which specifies219 // which units should recieve an instance as they enter the game. If220 // you do not declare it, all entering units will recieve an instance.221 // -You may optionally declare the onCreate method, which will run when222 // AutoCreate automatically creates an instance. (This is just a stand-223 // in until JassHelper supports the onCreate method.)224 // -You may declare your own create method, but it must take a single225 // unit parameter (the entering unit) if you do so.226 /*227 struct Example228 private static method createFilter takes unit u returns boolean229 return GetUnitTypeId(u) == 'hfoo' //Created only for Footmen.230 endmethod231 private method onCreate takes nothing returns nothing232 call BJDebugMsg(GetUnitName(me)+" entered the game!")233 endmethod234 implement AutoCreate235 endstruct236 */237 //===========================================================================238 // How to use AutoDestroy:239 //=========================240 // 241 // The AutoDestroy module allows you to automatically destroy instances242 // of the implementing struct when their "me" unit leaves the game. AutoDestroy243 // automatically implements AutoData into your struct. You must assign a unit244 // to the "me" member of an instance for this module to have any effect.245 //246 // AutoDestroy restrictions:247 // -All of the same restrictions as AutoData.248 //249 // AutoDestroy information:250 // -If you also implement AutoCreate in the same struct, remember that it251 // assigns the "me" unit automatically. That means you can have fully252 // automatic creation and destruction.253 /*254 struct Example255 static method create takes unit u returns Example256 local Example this = allocate()257 set me = u //You should assign a unit to "me",258 return this //otherwise AutoDestroy does nothing.259 endmethod //Not necessary if using AutoCreate.260 private method onDestroy takes nothing returns nothing261 call BJDebugMsg(GetUnitName(me)+" left the game!")262 endmethod263 implement AutoDestroy264 endstruct265 */266 //===========================================================================267 // Configuration:268 //================269 270 //! external ObjectMerger w3a Adef lvdt anam "Leave Detect" aart "" arac 0271 //Save your map with this Object Merger call enabled, then close and reopen your272 //map. Disable it by removing the exclamation to remove the delay while saving.273 274 globals275 private constant integer LeaveDetectAbilityID = 'lvdt'
276 //This rawcode must match the parameter after "Adef" in the277 //ObjectMerger macro above. You may change both if you want.278
279 private constant boolean UseUnitUserData = true
280 //If this is set to true, UnitUserData will be used. You should only set281 //this to false if something else in your map already uses UnitUserData.282 //A hashtable will be used instead, but it is about 60% slower.283
284 private constant boolean SafeMode = true
285 //This is set to true by default so that GetUnitId() will ALWAYS work.286 //If if this is set to false, GetUnitId() may fail to work in a very287 //rare circumstance: creating a unit that has a default-on autocast288 //ability, and using GetUnitId() on that unit as it enters the game,289 //within a trigger that detects any order. Set this to false for a290 //performance boost only if you think you can avoid this issue.291
292 private constant boolean AutoDataFastMode = true
293 //If this is set to true, AutoData will utilize one hashtable per time294 //it is implemented. If this is set to false, all AutoDatas will share295 //a single hashtable, but iterating through the instances belonging to296 //a unit will become about 12.5% slower. Your map will break if you297 //use more than 255 hashtables simultaneously. Only set this to false298 //if you suspect you will run out of hashtable instances.299 endglobals300 301 private function UnitFilter takes unit u returns boolean
302 return true
303 endfunction304 //Make this function return false for any unit-types you want to ignore.305 //Ignored units won't be indexed or fire OnUnitIndexed/OnUnitDeindexed306 //events. The unit parameter "u" to refers to the unit being filtered.307 //Do not filter out xe dummy units; they are automatically filtered.308 309 //===========================================================================310 // AutoData / AutoCreate / AutoDestroy modules:311 //==============================================312 313 function interface AutoCreator takes unit u returns nothing
314 function interface AutoDestroyer takes unit u returns nothing
315 316 globals 317 hashtable AutoData = null //If AutoDataFastMode is disabled, this hashtable will be
318 endglobals //initialized and shared between all AutoData implementations.
319 320 module AutoData321 private static hashtable ht
322 private static thistype array data
323 private static integer array listsize
324 private static key typeid //Good thing keys exist to identify each implementing struct.
325 private unit meunit
326 private integer id
327
328 readonly integer index //The user can avoid using a local counter because this is accessable.
329
330 static method operator [] takes unit u returns thistype
331 return data[GetUnitId(u)]
332 endmethod //This is as fast as retrieving an instance from a unit gets.
333
334 method operator [] takes integer index returns thistype
335 static if AutoDataFastMode then //If fast mode is enabled...
336 return LoadInteger(ht, id, index)
337 else //Each instance has its own hashtable to associate unit and index.
338 return LoadInteger(AutoData, id, index*8190+typeid)
339 endif //Otherwise, simulate a 3D array associating unit, struct-type ID, and index.
340 endmethod //Somehow, this version is 12.5% slower just because of the math.
341
342 private method setIndex takes integer index, thistype data returns nothing
343 static if AutoDataFastMode then //Too bad you can't have a module-private operator []=.
344 call SaveInteger(ht, id, index, data)
345 else346 call SaveInteger(AutoData, id, index*8190+typeid, data)
347 endif348 endmethod349
350 private method remove takes nothing returns nothing
351 if meunit == null then //If the struct doesn't have an owner...
352 return //Nothing needs to be done.
353 endif354 loop355 exitwhen index == listsize[id] //The last value gets overwritten by 0.
356 call setIndex(index, this[index + 1]) //Shift each element down by one.
357 set this[index].index = index //Update the shifted instance's index.
358 set index = index + 1
359 endloop 360 set listsize[id] = listsize[id] - 1
361 set data[id] = this[0] //Ensure thistype[u] returns the same value as thistype[u][0].
362 set meunit = null
363 endmethod364
365 private method add takes unit u returns nothing
366 if meunit != null then //If the struct has an owner...
367 call remove() //remove it first.
368 endif369 set meunit = u
370 set id = GetUnitId(u) //Cache GetUnitId for slight performance boost.
371 if data[id] == 0 then //If this is the first instance for this unit...
372 set data[id] = this //Update the value that thistype[u] returns.
373 endif374 set index = listsize[id] //Remember the index for removal.
375 call setIndex(index, this) //Add to the array.
376 set listsize[id] = index + 1
377 endmethod378
379 method operator me takes nothing returns unit
380 return meunit381 endmethod382
383 method operator me= takes unit u returns nothing
384 if u != null then //If assigning "me" a non-null value...
385 call add(u) //Add this instance to that unit's array.
386 else //If assigning "me" a null value...
387 call remove() //Remove this instance from that unit's array.
388 endif389 endmethod390
391 method operator size takes nothing returns integer
392 return listsize[id]
393 endmethod394
395 method destroy takes nothing returns nothing
396 call deallocate()
397 call remove() //This makes removal automatic when an instance is destroyed.
398 endmethod399
400 private static method onInit takes nothing returns nothing
401 static if AutoDataFastMode then //If fast mode is enabled...
402 set ht = InitHashtable() //Initialize one hashtable per instance.
403 else //If fast mode is disabled...
404 if AutoData == null then //If the hashtable hasn't been initialized yet...
405 set AutoData = InitHashtable() //Initialize the shared hashtable.
406 endif407 endif408 endmethod409 endmodule410 411 module AutoCreate412 implement AutoData //AutoData is necessary for AutoCreate.
413 414 private static method creator takes unit u returns nothing
415 local thistype this
416 local boolean b = true //Assume that the instance will be created.
417 static if thistype.createFilter.exists then //If createFilter exists...
418 set b = createFilter(u) //evaluate it and update b.
419 endif420 if b then //If the instance should be created...
421 static if thistype.create.exists then //If the create method exists...
422 set this = create(u) //Create the instance, passing the entering unit.
423 else //If the create method doesn't exist...
424 set this = allocate() //Just allocate the instance.
425 endif426 set me = u //Assign the instance's owner as the entering unit.
427 static if thistype.onCreate.exists then //If onCreate exists...
428 call onCreate() //Call it, because JassHelper should do this anyway.
429 endif430 endif431 endmethod432 433 private static method onInit takes nothing returns nothing
434 call AutoIndex.addAutoCreate(thistype.creator)
435 endmethod //During module initialization, pass the creator function to AutoIndex.
436 endmodule437 438 module AutoDestroy439 implement AutoData //AutoData is necessary for AutoDestroy.
440
441 static method destroyer takes unit u returns nothing
442 loop443 exitwhen thistype[u] == 0
444 call thistype[u].destroy()
445 endloop446 endmethod //Destroy each instance owned by the unit until none are left.
447 448 private static method onInit takes nothing returns nothing
449 call AutoIndex.addAutoDestroy(thistype.destroyer)
450 endmethod //During module initialization, pass the destroyer function to AutoIndex.
451 endmodule452 453 //===========================================================================454 // AutoIndex struct:455 //===================456 457 function interface IndexFunc takes unit u returns nothing
458 459 hook RemoveUnit AutoIndex.hook_RemoveUnit
460 hook ReplaceUnitBJ AutoIndex.hook_ReplaceUnitBJ
461 debug hook SetUnitUserData AutoIndex.hook_SetUnitUserData
462 463 private keyword getIndex
464 private keyword getIndexDebug
465 private keyword isUnitIndexed
466 private keyword onUnitIndexed
467 private keyword onUnitDeindexed
468 469 struct AutoIndex470 private static trigger enter = CreateTrigger()
471 private static trigger order = CreateTrigger()
472 private static trigger creepdeath = CreateTrigger()
473 private static group preplaced = CreateGroup()
474 private static timer allowdecay = CreateTimer()
475 private static hashtable ht
476 477 private static boolean array dead
478 private static boolean array summoned
479 private static boolean array animated
480 private static boolean array nodecay
481 private static boolean array removing
482
483 private static IndexFunc array indexfuncs
484 private static integer indexfuncs_n = -1
485 private static IndexFunc array deindexfuncs
486 private static integer deindexfuncs_n = -1
487 private static IndexFunc indexfunc
488
489 private static AutoCreator array creators
490 private static integer creators_n = -1
491 private static AutoDestroyer array destroyers
492 private static integer destroyers_n = -1
493
494 private static unit array allowdecayunit
495 private static integer allowdecay_n = -1
496
497 private static boolean duringinit = true
498 private static boolean array altered
499 private static unit array idunit
500
501 //===========================================================================502 503 static method getIndex takes unit u returns integer
504 static if UseUnitUserData then
505 return GetUnitUserData(u)
506 else507 return LoadInteger(ht, 0, GetHandleId(u))
508 endif509 endmethod //Resolves to an inlinable one-liner after the static if.
510
511 static method getIndexDebug takes unit u returns integer
512 if u == null then
513 return 0
514 elseif GetUnitTypeId(u) == 0 then
515 call BJDebugMsg("AutoIndex error: Removed or decayed unit passed to GetUnitId.")
516 elseif idunit[getIndex(u)] != u and GetIssuedOrderId() != 852056 then
517 call BJDebugMsg("AutoIndex error: "+GetUnitName(u)+" is a filtered unit.")
518 endif519 return getIndex(u)
520 endmethod //If debug mode is enabled, use the getIndex method that shows errors.
521
522 private static method setIndex takes unit u, integer index returns nothing
523 static if UseUnitUserData then
524 call SetUnitUserData(u, index)
525 else526 call SaveInteger(ht, 0, GetHandleId(u), index)
527 endif528 endmethod //Resolves to an inlinable one-liner after the static if.
529
530 static method isUnitIndexed takes unit u returns boolean
531 return u != null and idunit[getIndex(u)] == u
532 endmethod533
534 static method isUnitAnimateDead takes unit u returns boolean
535 return animated[getIndex(u)]
536 endmethod //Don't use this; use IsUnitAnimateDead from AutoEvents instead.
537
538 //===========================================================================539
540 private static method onUnitIndexed_sub takes nothing returns nothing
541 call indexfunc.evaluate(GetEnumUnit())
542 endmethod543 static method onUnitIndexed takes IndexFunc func returns nothing
544 set indexfuncs_n = indexfuncs_n + 1
545 set indexfuncs[indexfuncs_n] = func
546 if duringinit then //During initialization, evaluate the indexfunc for every preplaced unit.
547 set indexfunc = func
548 call ForGroup(preplaced, function AutoIndex.onUnitIndexed_sub)
549 endif550 endmethod551
552 static method onUnitDeindexed takes IndexFunc func returns nothing
553 set deindexfuncs_n = deindexfuncs_n + 1
554 set deindexfuncs[deindexfuncs_n] = func
555 endmethod556
557 static method addAutoCreate takes AutoCreator func returns nothing
558 set creators_n = creators_n + 1
559 set creators[creators_n] = func
560 endmethod561
562 static method addAutoDestroy takes AutoDestroyer func returns nothing
563 set destroyers_n = destroyers_n + 1
564 set destroyers[destroyers_n] = func
565 endmethod566
567 //===========================================================================568
569 private static method hook_RemoveUnit takes unit whichUnit returns nothing
570 set removing[getIndex(whichUnit)] = true
571 endmethod //Intercepts whenever RemoveUnit is called and sets a flag.
572 private static method hook_ReplaceUnitBJ takes unit whichUnit, integer newUnitId, integer unitStateMethod returns nothing
573 set removing[getIndex(whichUnit)] = true
574 endmethod //Intercepts whenever ReplaceUnitBJ is called and sets a flag.
575
576 private static method hook_SetUnitUserData takes unit whichUnit, integer data returns nothing
577 static if UseUnitUserData then
578 if idunit[getIndex(whichUnit)] == whichUnit then
579 if getIndex(whichUnit) == data then
580 call BJDebugMsg("AutoIndex error: Code outside AutoIndex attempted to alter "+GetUnitName(whichUnit)+"'s index.")
581 else582 call BJDebugMsg("AutoIndex error: Code outside AutoIndex altered "+GetUnitName(whichUnit)+"'s index.")
583 if idunit[data] != null then
584 call BJDebugMsg("AutoIndex error: "+GetUnitName(whichUnit)+" and "+GetUnitName(idunit[data])+" now have the same index.")
585 endif586 set altered[data] = true
587 endif588 endif589 endif //In debug mode, intercepts whenever SetUnitUserData is used on an indexed unit.
590 endmethod //Displays an error message if outside code tries to alter a unit's index.
591
592 //===========================================================================593
594 private static method allowDecay takes nothing returns nothing
595 local integer n = allowdecay_n
596 loop597 exitwhen n < 0
598 set nodecay[getIndex(allowdecayunit[n])] = false
599 set allowdecayunit[n] = null
600 set n = n - 1
601 endloop602 set allowdecay_n = -1
603 endmethod //Iterate through all the units in the stack and allow them to decay again.
604
605 private static method detectStatus takes nothing returns boolean
606 local unit u = GetTriggerUnit()
607 local integer index = getIndex(u)
608 local integer n
609
610 if idunit[index] == u then //Ignore non-indexed units.
611 if not IsUnitType(u, UNIT_TYPE_DEAD) then
612
613 if dead[index] then //The unit was dead, but now it's alive.
614 set dead[index] = false //The unit has been resurrected.
615 //! runtextmacro optional RunAutoEvent("Resurrect")616 //If AutoEvents is in the map, run the resurrection events.617
618 if IsUnitType(u, UNIT_TYPE_SUMMONED) and not summoned[index] then
619 set summoned[index] = true //If the unit gained the summoned flag,
620 set animated[index] = true //it's been raised with Animate Dead.
621 //! runtextmacro optional RunAutoEvent("AnimateDead")622 //If AutoEvents is in the map, run the Animate Dead events.623 endif624 endif625 else626
627 if not removing[index] and not dead[index] and not animated[index] then
628 set dead[index] = true //The unit was alive, but now it's dead.
629 set nodecay[index] = true //A dead unit can't decay for at least 0. seconds.
630 set allowdecay_n = allowdecay_n + 1 //Add the unit to a stack. After the timer
631 set allowdecayunit[allowdecay_n] = u //expires, allow the unit to decay again.
632 call TimerStart(allowdecay, 0., false, function AutoIndex.allowDecay)
633 //! runtextmacro optional RunAutoEvent("Death")634 //If AutoEvents is in the map, run the Death events.635
636 elseif removing[index] or (dead[index] and not nodecay[index]) or (not dead[index] and animated[index]) then
637 //If .nodecay was false and the unit is dead and was previously dead, the unit decayed.638 //If .animated was true and the unit is dead, the unit died and exploded.639 //If .removing was true, the unit is being removed or replaced.640 set n = deindexfuncs_n
641 loop //Run the OnUnitDeindexed events.
642 exitwhen n < 0
643 call deindexfuncs[n].evaluate(u)
644 set n = n - 1
645 endloop646 set n = destroyers_n
647 loop //Destroy AutoDestroy structs for the leaving unit.
648 exitwhen n < 0
649 call destroyers[n].evaluate(u)
650 set n = n - 1
651 endloop652 call AutoIndex(index).destroy() //Free the index by destroying the AutoIndex struct.
653 set idunit[index] = null //Null this unit reference to prevent a leak.
654 endif655 endif656 endif657 set u = null
658 return false
659 endmethod660 661 //===========================================================================662
663 private static method unitEntersMap takes unit u returns nothing
664 local integer index
665 local integer n = 0
666 if getIndex(u) != 0 then
667 return //Don't index a unit that already has an ID.
668 endif669 static if LIBRARY_xebasic then
670 if GetUnitTypeId(u) == XE_DUMMY_UNITID then
671 return //Don't index xe dummy units.
672 endif673 endif674 if not UnitFilter(u) then
675 return //Don't index units that fail the unit filter.
676 endif677 set index = create()
678 call setIndex(u, index) //Assign an index to the entering unit.
679
680 call UnitAddAbility(u, LeaveDetectAbilityID) //Add the leave detect ability to the entering unit.
681 call UnitMakeAbilityPermanent(u, true, LeaveDetectAbilityID) //Prevent it from disappearing on morph.
682 set dead[index] = IsUnitType(u, UNIT_TYPE_DEAD) //Reset all of the flags for the entering unit.
683 set summoned[index] = IsUnitType(u, UNIT_TYPE_SUMMONED) //Each of these flags are necessary to detect
684 set animated[index] = false //when a unit leaves the map.
685 set nodecay[index] = false
686 set removing[index] = false
687 debug set altered[index] = false //In debug mode, this flag tracks wheter a unit's index was altered.
688 set idunit[index] = u //Attach the unit that is supposed to have this index to the index.
689
690 if duringinit then //If a unit enters the map during initialization...
691 call GroupAddUnit(preplaced, u) //Add the unit to the preplaced units group. This ensures that
692 endif //all units are noticed by OnUnitIndexed during initialization.
693 loop //Create AutoCreate structs for the entering unit.
694 exitwhen n > creators_n
695 call creators[n].evaluate(u)
696 set n = n + 1
697 endloop698 set n = 0
699 loop //Run the OnUnitIndexed events.
700 exitwhen n > indexfuncs_n
701 call indexfuncs[n].evaluate(u)
702 set n = n + 1
703 endloop704 endmethod705
706 private static method onIssuedOrder takes nothing returns boolean
707 static if SafeMode then //If SafeMode is enabled, perform this extra check.
708 if getIndex(GetTriggerUnit()) == 0 then //If the unit doesn't already have
709 call unitEntersMap(GetTriggerUnit()) //an index, then assign it one.
710 endif711 endif712 return GetIssuedOrderId() == 852056 //If the order is Undefend, allow detectStatus to run.
713 endmethod714
715 private static method initEnteringUnit takes nothing returns boolean
716 call unitEntersMap(GetFilterUnit())
717 return false
718 endmethod719
720 //===========================================================================721
722 private static method afterInit takes nothing returns nothing
723 set duringinit = false //Initialization is over; set a flag.
724 call DestroyTimer(GetExpiredTimer()) //Destroy the timer.
725 call GroupClear(preplaced) //The preplaced units group is
726 call DestroyGroup(preplaced) //no longer needed, so clean it.
727 set preplaced = null
728 endmethod729
730 private static method onInit takes nothing returns nothing
731 local region maparea = CreateRegion()
732 local rect bounds = GetWorldBounds()
733 local group g = CreateGroup()
734 local integer i = 15
735 static if not UseUnitUserData then
736 set ht = InitHashtable() //Only create a hashtable if it will be used.
737 endif738 loop739 exitwhen i < 0
740 call SetPlayerAbilityAvailable(Player(i), LeaveDetectAbilityID, false)
741 //Make the LeaveDetect ability unavailable so that it doesn't show up on the command card of every unit.742 call TriggerRegisterPlayerUnitEvent(order, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
743 //Register the "EVENT_PLAYER_UNIT_ISSUED_ORDER" event for each player.744 call GroupEnumUnitsOfPlayer(g, Player(i), function AutoIndex.initEnteringUnit)
745 //Enum every non-filtered unit on the map during initialization and assign it a unique746 //index. By using GroupEnumUnitsOfPlayer, even units with Locust can be detected.747 set i = i - 1
748 endloop749 call TriggerAddCondition(order, And(function AutoIndex.onIssuedOrder, function AutoIndex.detectStatus))
750 //The detectStatus method will fire every time a non-filtered unit recieves an undefend order.751 //And() is used here to avoid using a trigger action, which starts a new thread and is slower.752 call TriggerRegisterPlayerUnitEvent(creepdeath, Player(12), EVENT_PLAYER_UNIT_DEATH, null)
753 call TriggerAddCondition(creepdeath, function AutoIndex.detectStatus)
754 //The detectStatus method must also fire when a neutral hostile creep dies, in case it was755 //sleeping. Sleeping creeps don't fire undefend orders on non-damaging deaths.756 call RegionAddRect(maparea, bounds) //GetWorldBounds() includes the shaded boundry areas.
757 call TriggerRegisterEnterRegion(enter, maparea, function AutoIndex.initEnteringUnit)
758 //The filter function of an EnterRegion trigger runs instantly when a unit is created.759 call TimerStart(CreateTimer(), 0., false, function AutoIndex.afterInit)
760 //After any time elapses, perform after-initialization actions.761 call GroupClear(g)
762 call DestroyGroup(g)
763 call RemoveRect(bounds)
764 set g = null
765 set bounds = null
766 endmethod767
768 endstruct769 770 //===========================================================================771 // User functions:772 //=================773 774 function GetUnitId takes unit u returns integer
775 static if DEBUG_MODE then //If debug mode is enabled...
776 return AutoIndex.getIndexDebug(u) //call the debug version of GetUnitId.
777 else //If debug mode is disabled...
778 return AutoIndex.getIndex(u) //call the normal, inlinable version.
779 endif780 endfunction781 782 function IsUnitIndexed takes unit u returns boolean
783 return AutoIndex.isUnitIndexed(u)
784 endfunction785 786 function OnUnitIndexed takes IndexFunc func returns nothing
787 call AutoIndex.onUnitIndexed(func)
788 endfunction789 790 function OnUnitDeindexed takes IndexFunc func returns nothing
791 call AutoIndex.onUnitDeindexed(func)
792 endfunction793 794 endlibrary795 796