AutoIndex

Scripts

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:

  • 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) Select
1
2
library AutoIndex
3
//===========================================================================
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 GetUnitId
9
// function is extremely fast because it inlines directly to a GetUnitUserData
10
// call. AutoIndex automatically assigns an ID to each unit as it enters the
11
// map, and instantly frees that ID as the unit leaves the map. Detection of
12
// leaving units is accomplished in constant time without a periodic scan.
13
//
14
//     AutoIndex uses UnitUserData by default. If something else in your map
15
// would conflict with that, you can set the UseUnitUserData configuration
16
// 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 several
20
// helpful error messages. The following issues will be detected:
21
//   -Passing a removed or decayed unit to GetUnitId
22
//   -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. This
26
// effectively allows you to notice when units enter or leave the game. Also
27
// included are the AutoData, AutoCreate, and AutoDestroy modules, which allow
28
// you to fully utilize AutoIndex's enter/leave detection capabilities in
29
// 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 macro
38
//     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 to
45
// attach data to a unit? GetUnitId will always return a number in the range of
46
// 1-8190. This means it can be used as an array index, as demonstrated below:
47
/*
48
    globals
49
        integer array IntegerData
50
        real array RealData
51
        SomeStruct array SomeStructData
52
    englobals
53
54
    function Example takes nothing returns nothing
55
        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 can
58
            //attach or retrieve data about the unit using arrays.
59
            set IntegerData[id] = 5
60
            set RealData[id] = 25.0
61
            set SomeStructData[id] = SomeStruct.create()
62
            //If you have access to the same unit in another function, you can
63
            //retrieve the data by using GetUnitId() and reading the arrays.
64
    endfunction
65
*/
66
//     The UnitFilter function in the configuration section is provided so that
67
// you can make AutoIndex completely ignore certain unit-types. Ignored units
68
// won't be indexed or fire indexed/deindexed events. You may want to filter out
69
// dummy casters or system-private units, especially ones that use UnitUserData
70
// 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 used
78
// as events must take a unit and return nothing. An example is given below:
79
/*
80
    function UnitEntersMap takes unit u returns nothing
81
        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 nothing
85
        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 nothing
89
        call OnUnitIndexed(UnitEntersMap)
90
        call OnUnitDeindexed(UnitLeavesMap)
91
    endfunction
92
*/
93
//     If you call OnUnitIndexed during map initialization, every existing
94
// unit will be considered as entering the map. This saves you from the need
95
// to manually enumerate preplaced units (or units created by initialization
96
// code that ran before OnUnitIndexed was called).
97
//
98
//     OnUnitDeindexed runs while a unit still exists, which means you can
99
// 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) -> integer
107
//   This function returns a unique ID in the range of 1-8190 for the
108
//   specified unit. Returns 0 if a null unit was passed. This function
109
//   inlines directly to GetUnitUserData or LoadInteger if debug mode
110
//   is disabled. If debug mode is enabled, this function will print
111
//   an error message when passed a decayed or filtered unit.
112
//
113
// IsUnitIndexed(unit) -> boolean
114
//   This function returns a boolean indicating whether the specified
115
//   unit has been indexed. The only time this will return false is
116
//   for units you have filtered using the UnitFilter function, or
117
//   for xe dummy units. You can use this function to easily detect
118
//   dummy units and avoid performing certain actions on them.
119
//
120
// OnUnitIndexed(IndexFunc)
121
//   This function accepts an IndexFunc, which must take a unit and
122
//   return nothing. The IndexFunc will be fired instantly whenever
123
//   a unit enters the map. You may use GetUnitId on the unit. When
124
//   you call this function during map initialization, every existing
125
//   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. When
129
//   this event runs, the unit still exists, but it will cease to exist
130
//   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 instances
137
// of the implementing struct with units, as well as iterate through all
138
// 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 unit
142
// you assign to "me" becomes the owner of that instance. You may change
143
// ownership by reassigning "me" to another unit at any time, or you may
144
// make the instance unowned by assigning "me" to null.
145
//
146
//     AutoData implements the static method operator [] in your struct
147
// 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 set
149
// 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 multiple
152
// instances? You may use 2D array syntax to access instances assigned to
153
// the same unit: local StructName s = StructName[u][n], where u is the
154
// owning unit, and n is the index beginning with 0 for each unit. You
155
// can access the size of a unit's instance list (i.e. the number of
156
// instances belonging to the unit) by using the .size instance member.
157
/*
158
    struct Example
159
        implement AutoData
160
        static method create takes unit u returns Example
161
            local Example this = allocate()
162
                set me = u //Assigning the "me" member from AutoData.
163
            return this
164
        endmethod
165
    endstruct
166
    function Test takes nothing returns nothing
167
        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 e
172
            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 belonging
175
            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 already
184
//    declared static or non-static method operator [].
185
//   -AutoData will conflict with anything named "me", "size", or
186
//    "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 restriction
189
//    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 an
193
//    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 that
198
//    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 instances
206
// of the implementing struct for units as they enter the game. AutoCreate
207
// automatically implements AutoData into your struct. Any time an instance
208
// is automatically created for a unit, that instance's "me" member will be
209
// 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 parent
214
//    type's create method takes parameters), you must declare a create
215
//    method and pass those extra parameters to allocate yourself.
216
//
217
//   AutoCreate information:
218
//   -You may optionally declare the createFilter method, which specifies
219
//    which units should recieve an instance as they enter the game. If
220
//    you do not declare it, all entering units will recieve an instance.
221
//   -You may optionally declare the onCreate method, which will run when
222
//    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 single
225
//    unit parameter (the entering unit) if you do so.
226
/*
227
    struct Example
228
        private static method createFilter takes unit u returns boolean
229
            return GetUnitTypeId(u) == 'hfoo' //Created only for Footmen.
230
        endmethod
231
        private method onCreate takes nothing returns nothing
232
            call BJDebugMsg(GetUnitName(me)+" entered the game!")
233
        endmethod
234
        implement AutoCreate
235
    endstruct
236
*/
237
//===========================================================================
238
// How to use AutoDestroy:
239
//=========================
240
// 
241
//     The AutoDestroy module allows you to automatically destroy instances
242
// of the implementing struct when their "me" unit leaves the game. AutoDestroy
243
// automatically implements AutoData into your struct. You must assign a unit
244
// 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 it
251
//    assigns the "me" unit automatically. That means you can have fully
252
//    automatic creation and destruction.
253
/*
254
    struct Example
255
        static method create takes unit u returns Example
256
            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 nothing
261
            call BJDebugMsg(GetUnitName(me)+" left the game!")
262
        endmethod
263
        implement AutoDestroy
264
    endstruct
265
*/
266
//===========================================================================
267
// Configuration:
268
//================
269
270
//! external ObjectMerger w3a Adef lvdt anam "Leave Detect" aart "" arac 0
271
//Save your map with this Object Merger call enabled, then close and reopen your
272
//map. Disable it by removing the exclamation to remove the delay while saving.
273
274
globals
275
    private constant integer LeaveDetectAbilityID = 'lvdt'
276
    //This rawcode must match the parameter after "Adef" in the
277
    //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 set
281
    //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 very
287
    //rare circumstance: creating a unit that has a default-on autocast
288
    //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 a
290
    //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 time
294
    //it is implemented. If this is set to false, all AutoDatas will share
295
    //a single hashtable, but iterating through the instances belonging to
296
    //a unit will become about 12.5% slower. Your map will break if you
297
    //use more than 255 hashtables simultaneously. Only set this to false
298
    //if you suspect you will run out of hashtable instances.
299
endglobals
300
301
private function UnitFilter takes unit u returns boolean
302
    return true
303
endfunction
304
//Make this function return false for any unit-types you want to ignore.
305
//Ignored units won't be indexed or fire OnUnitIndexed/OnUnitDeindexed
306
//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 AutoData
321
    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
        else
346
            call SaveInteger(AutoData, id, index*8190+typeid, data)
347
        endif
348
    endmethod
349
    
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
        endif
354
        loop
355
            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
    endmethod
364
    
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
        endif
369
        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
        endif
374
        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
    endmethod
378
    
379
    method operator me takes nothing returns unit
380
        return meunit
381
    endmethod
382
    
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
        endif
389
    endmethod
390
    
391
    method operator size takes nothing returns integer
392
        return listsize[id]
393
    endmethod
394
    
395
    method destroy takes nothing returns nothing
396
        call deallocate()
397
        call remove() //This makes removal automatic when an instance is destroyed.
398
    endmethod
399
    
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
            endif
407
        endif
408
    endmethod
409
endmodule
410
411
module AutoCreate
412
    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
            endif
420
            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
                endif
426
                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
                endif
430
            endif
431
    endmethod
432
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
endmodule
437
438
module AutoDestroy
439
    implement AutoData //AutoData is necessary for AutoDestroy.
440
    
441
    static method destroyer takes unit u returns nothing
442
        loop
443
            exitwhen thistype[u] == 0
444
            call thistype[u].destroy()
445
        endloop
446
    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
endmodule
452
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 AutoIndex
470
    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
        else
507
            return LoadInteger(ht, 0, GetHandleId(u))
508
        endif
509
    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
            endif
519
        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
        else
526
            call SaveInteger(ht, 0, GetHandleId(u), index)
527
        endif
528
    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
    endmethod
533
    
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
    endmethod
543
    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
        endif
550
    endmethod
551
    
552
    static method onUnitDeindexed takes IndexFunc func returns nothing
553
        set deindexfuncs_n = deindexfuncs_n + 1
554
        set deindexfuncs[deindexfuncs_n] = func
555
    endmethod
556
    
557
    static method addAutoCreate takes AutoCreator func returns nothing
558
        set creators_n = creators_n + 1
559
        set creators[creators_n] = func
560
    endmethod
561
    
562
    static method addAutoDestroy takes AutoDestroyer func returns nothing
563
        set destroyers_n = destroyers_n + 1
564
        set destroyers[destroyers_n] = func
565
    endmethod
566
    
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
                else
582
                    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
                    endif
586
                    set altered[data] = true
587
                endif
588
            endif
589
        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
            loop
597
                exitwhen n < 0
598
                set nodecay[getIndex(allowdecayunit[n])] = false
599
                set allowdecayunit[n] = null
600
                set n = n - 1
601
            endloop
602
            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
                        endif
624
                    endif
625
                else
626
                
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
                        endloop
646
                        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
                        endloop
652
                        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
                    endif
655
                endif
656
            endif
657
        set u = null
658
        return false
659
    endmethod
660
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
            endif
669
            static if LIBRARY_xebasic then
670
                if GetUnitTypeId(u) == XE_DUMMY_UNITID then
671
                    return //Don't index xe dummy units.
672
                endif
673
            endif
674
            if not UnitFilter(u) then
675
                return //Don't index units that fail the unit filter.
676
            endif
677
            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
            endloop
698
            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
            endloop
704
    endmethod
705
    
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
                endif
711
            endif
712
        return GetIssuedOrderId() == 852056 //If the order is Undefend, allow detectStatus to run.
713
    endmethod
714
    
715
    private static method initEnteringUnit takes nothing returns boolean
716
            call unitEntersMap(GetFilterUnit())
717
        return false
718
    endmethod
719
    
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
    endmethod
729
    
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
            endif
738
            loop
739
                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 unique
746
                //index. By using GroupEnumUnitsOfPlayer, even units with Locust can be detected.
747
                set i = i - 1
748
            endloop
749
            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 was
755
            //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
    endmethod
767
    
768
endstruct
769
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
    endif
780
endfunction
781
782
function IsUnitIndexed takes unit u returns boolean
783
    return AutoIndex.isUnitIndexed(u)
784
endfunction
785
786
function OnUnitIndexed takes IndexFunc func returns nothing
787
    call AutoIndex.onUnitIndexed(func)
788
endfunction
789
790
function OnUnitDeindexed takes IndexFunc func returns nothing
791
    call AutoIndex.onUnitDeindexed(func)
792
endfunction
793
794
endlibrary
795
796