• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

unit indexer with correct initialization

Status
Not open for further replies.
Level 10
Joined
Sep 19, 2011
Messages
527
i'm not a fan of unitindexing systems but when i tried to build one, my initialization was wrong. so today i was kinda boring and i decided to give it a try and make it right. here is the result:

JASS:
//! external ObjectMerger w3a Adef !e@$ anam "UnitIndexer" ansf "(UnitIndexer)" aart "" acat "" arac 0 

library UnitIndexer
	globals
		public constant integer UNIT_INDEXER_ABILITY = '!e@$'
		private constant boolean USE_HASHTABLE = false
	endglobals
	
	private function GlobalUnitFilter takes unit whichUnit returns boolean
		return true
	endfunction
	
	static if ( USE_HASHTABLE ) then
		globals
			private hashtable hashTable = InitHashtable()
		endglobals
	endif
	
	module UnitIndexerModule
		private static method onInit takes nothing returns nothing
			static if ( thistype.onIndex.exists ) then
				call UnitIndexer.onIndex(function thistype.onIndex)
			endif
			static if ( thistype.onDeIndex.exists ) then
				call UnitIndexer.onDeIndex(function thistype.onDeIndex)
			endif
		endmethod
	endmodule
	
	private module UnitIndexerInit
		private static method initialize takes nothing returns nothing		
			set thistype.initialized = true
			
			loop
				exitwhen integer(thistype.lastUnitIndexer) == 0
				call thistype.lastUnitIndexer.fireEvent(true)
				set thistype.lastUnitIndexer = thistype.lastUnitIndexer - 1
			endloop
			
			call DestroyTimer(GetExpiredTimer())
		endmethod
		
		private static method onInit takes nothing returns nothing
			call thistype.indexInitialUnits()
            call TimerStart(CreateTimer(), 0, false, function thistype.initialize)
		endmethod
	endmodule
	
	struct UnitIndexer
		readonly static thistype triggered = 0
		private static trigger eventTrigger = CreateTrigger()
		
		private static integer array locked
		
		private static boolean initialized = false
		private static thistype lastUnitIndexer = 0
	
		readonly unit unit
		static if ( USE_HASHTABLE ) then
			readonly integer handleId
		endif
		
		method isLocked takes nothing returns boolean
			return thistype.locked[this] > 0
		endmethod
		
		method lock takes boolean lock returns nothing
			if ( lock ) then
				set thistype.locked[this] = thistype.locked[this] + 1
			else
				set thistype.locked[this] = thistype.locked[this] - 1
			endif
		endmethod
		
		static method onIndex takes code c returns nothing
			call TriggerAddCondition(thistype.eventTrigger, Condition(c))
		endmethod
		
		static method onDeIndex takes code c returns nothing
			call TriggerAddAction(thistype.eventTrigger, c)
		endmethod
		
		private method fireEvent takes boolean indexEvent returns nothing
			local thistype prevTriggered = thistype.triggered
			set thistype.triggered = this
						
			if ( indexEvent ) then
				if ( thistype.initialized ) then
					call TriggerEvaluate(thistype.eventTrigger)
				endif
			else
				if ( thistype.initialized ) then
					call TriggerExecute(thistype.eventTrigger)
				else
					set thistype.initialized = true
				
					call this.fireEvent(true)
					call this.fireEvent(false)
					
					set thistype.initialized = false
				endif
			endif
			
			set thistype.triggered = prevTriggered
		endmethod
		
		method destroy takes nothing returns nothing
			call this.fireEvent(false)
			
			call UnitRemoveAbility(this.unit, UNIT_INDEXER_ABILITY)
		
			static if ( USE_HASHTABLE ) then
				call FlushChildHashtable(hashTable, this.handleId)
				set this.handleId = 0
			else
				call SetUnitUserData(this.unit, 0)
			endif
			
			set this.unit = null
			
			if ( not(this.isLocked()) ) then
				call this.deallocate()
			endif
		endmethod
		
		static method get takes unit whichUnit returns thistype
			static if ( USE_HASHTABLE ) then
				return LoadInteger(hashTable, GetHandleId(whichUnit), 0)
			else
				return GetUnitUserData(whichUnit)
			endif
		endmethod
		
		static method create takes unit whichUnit returns thistype
			local thistype this
			
			if ( thistype.get(whichUnit) == 0 and GlobalUnitFilter(whichUnit) ) then
				set this = thistype.allocate()
				
				if ( integer(thistype.lastUnitIndexer) < integer(this) ) then
					set thistype.lastUnitIndexer = this
				endif
				
				static if ( USE_HASHTABLE ) then
					set this.handleId = GetHandleId(whichUnit)
					call SaveInteger(hashTable, this.handleId, 0, this)
				else
					call SetUnitUserData(whichUnit, this)
				endif
				
				set this.unit = whichUnit
				
				call UnitAddAbility(whichUnit, UNIT_INDEXER_ABILITY)
				call UnitMakeAbilityPermanent(whichUnit, true, UNIT_INDEXER_ABILITY)
				
				call this.fireEvent(true)
			endif
			
			return this
		endmethod
		
		private static method onEnter takes nothing returns boolean
			call thistype.create(GetFilterUnit())
			return false
		endmethod
		
		private static method onLeave takes nothing returns boolean
			local unit leavingUnit = GetFilterUnit()
			local thistype unitIndexer = thistype.get(leavingUnit)
		
			if ( GetUnitAbilityLevel(leavingUnit, UNIT_INDEXER_ABILITY) == 0 and unitIndexer != 0 ) then
				call unitIndexer.destroy()
			endif
			
			set leavingUnit = null
			
			return false
		endmethod
		
		private static method indexInitialUnits takes nothing returns nothing
            local trigger onLeaveTrigger = CreateTrigger()
			local boolexpr onEnterCondition = Condition(function thistype.onEnter)
            local boolexpr onLeaveCondition = Condition(function thistype.onLeave)
            local region worldBoundsRegion = CreateRegion()
			local integer maxPlayers = bj_MAX_PLAYERS
            local player enumPlayer
           
            loop
                exitwhen maxPlayers < 0
               
                set enumPlayer = Player(maxPlayers)
               
                call TriggerRegisterPlayerUnitEvent(onLeaveTrigger, enumPlayer, EVENT_PLAYER_UNIT_ISSUED_ORDER, onLeaveCondition)
                call GroupEnumUnitsOfPlayer(bj_lastCreatedGroup, enumPlayer, onEnterCondition)
				call SetPlayerAbilityAvailable(enumPlayer, UNIT_INDEXER_ABILITY, false)
				
                set maxPlayers = maxPlayers - 1
            endloop
			
            call RegionAddRect(worldBoundsRegion, GetWorldBounds())
            call TriggerRegisterEnterRegion(CreateTrigger(), worldBoundsRegion, onEnterCondition)
            
            set onLeaveTrigger = null
			set onEnterCondition = null
            set onLeaveCondition = null
            set worldBoundsRegion = null
            set enumPlayer = null
		endmethod
		
		implement UnitIndexerInit
	endstruct
endlibrary

as you can see, pretty simple, nothing special but (hopefully) with correct initialization (i tested and works good).

i hope this serves to anyone to understand a little bit more how unit indexers work.
 
Status
Not open for further replies.
Top