• 🏆 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!

List Unit Abilities

Status
Not open for further replies.
To loop over abilities a unit currenly has.

JASS:
library UnitAbilityListDemo uses UnitAbilityList, CheatsJ
    private struct Demo extends array
        private static method onSelect takes nothing returns nothing
            local integer currentIndex = 0
            local string objedtIdString
            local unit u = GetTriggerUnit()
       
            call UnitAbilityList.Get(u)
       
            call ClearTextMessages()
            call BJDebugMsg("|c00FFD700" + GetUnitName(u) + "|r currently has:" )
            loop
                exitwhen ( currentIndex > UnitAbilityList.MaxIndex )
                set objedtIdString = ObjectId2String(UnitAbilityList.AbilityId[currentIndex])
                call BJDebugMsg("- " + GetObjectName(UnitAbilityList.AbilityId[currentIndex]) + " ('" + objedtIdString + "')" )
                set currentIndex = currentIndex + 1
            endloop
            set u = null
        endmethod
   
        public static method onAbilityUnitListEnabled takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterPlayerUnitEvent(t, GetLocalPlayer(), EVENT_PLAYER_UNIT_SELECTED, null)
            call TriggerAddAction(t, function thistype.onSelect)
            call BJDebugMsg("|c00FFD700UnitAbilityList|r Registered " + I2S(UnitAbilityList.MaxIndex_Total + 1) + " abilities\n
                            Select a unit to list its abilities")
        endmethod
   
        private static method onInit takes nothing returns nothing
            call UnitAbilityList.register(function thistype.onAbilityUnitListEnabled)
        endmethod
    endstruct
endlibrary

JASS:
library UnitAbilityList uses ObjectIdLoop
    struct UnitAbilityList extends array
        readonly static integer array AbilityId
        readonly static integer array AbilityListElements
        readonly static integer AbilityListMaxIndex
        readonly static integer AbilityCount
        private static boolean enabled = false
        private static timer clock = CreateTimer()
        private static constant real TIMEOUT = 0.0
        private static trigger eventHandler = CreateTrigger()
        private static integer currentIndex
        private static integer MaxIndex
        private static unit Dummy = null
       
        public static method Get takes unit u returns nothing
            local integer currentIndex

            if ( not enabled ) then
                debug call BJDebugMsg("|c00FFD700" + SCOPE_PREFIX + "|r System is not enabled, yet")
                return
            endif
           
            set AbilityCount = 0
            set currentIndex = 0
            loop
                exitwhen ( currentIndex > AbilityListMaxIndex )
                if ( GetUnitAbilityLevel(u, AbilityListElements[currentIndex]) > 0 ) then
                    set AbilityId[AbilityCount] = AbilityListElements[currentIndex]
                    set AbilityCount = AbilityCount + 1
                endif
                set currentIndex = currentIndex + 1
            endloop
        endmethod
       
        public static method register takes code c returns nothing
            call TriggerAddCondition(eventHandler, Condition(c))
        endmethod
       
        private static method CleanUp takes nothing returns nothing
            call PauseTimer(clock)
            call DestroyTimer(clock)
            call RemoveUnit(Dummy)
            call DestroyTrigger(eventHandler)
            set eventHandler = null
            set clock = null
            set Dummy = null
        endmethod
       
        private static method FireEvents takes nothing returns nothing
            set enabled = true
            call TriggerEvaluate(eventHandler)
        endmethod
       
        private static method VarifyAbiliy takes integer abilitycode returns boolean
            return UnitAddAbility(Dummy, abilitycode) or UnitRemoveAbility(Dummy, abilitycode)
        endmethod
       
        private static method AddAbilityToList takes integer abilitycode returns nothing
            set AbilityListElements[AbilityListMaxIndex] = abilitycode
            set AbilityListMaxIndex = AbilityListMaxIndex + 1
        endmethod
       
        private static method VarifyAbilities takes nothing returns nothing
            local integer stopIndex = currentIndex + 2000
       
            loop
                exitwhen ( currentIndex > MaxIndex or currentIndex > stopIndex)
                call AddAbilityToList(ObjectIdLoop.ObjectId[currentIndex])
                set currentIndex = currentIndex + 1
            endloop
           
            if ( currentIndex > MaxIndex ) then
                call FireEvents()
                call CleanUp()
            endif
        endmethod
       
        private static method onObjectLoopFinish takes nothing returns nothing
            set currentIndex = 0
            set AbilityListMaxIndex = 0
            set MaxIndex = ObjectIdLoop.MaxIndex
            call TimerStart(clock, TIMEOUT, true, function thistype.VarifyAbilities)
        endmethod

        private static method onInit takes nothing returns nothing
            set Dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), 'Hpal', 0, 0, 0)
            call UnitAddAbility(Dummy, 'Aloc')
            call ShowUnit(Dummy,false)
            call ObjectIdLoop.register(function thistype.onObjectLoopFinish)
        endmethod
    endstruct
endlibrary

JASS:
library ObjectIdLoop uses NextPrevObjectId // https://www.hiveworkshop.com/threads/nextprev-objectid.313328/
    struct ObjectIdLoop extends array
        readonly static integer array ObjectId
        readonly static integer MaxIndex = -1

        private static constant integer RECURSION_COUNTER_MAX = 20000
        private static constant real    THREAD_TIMEOUT        = 0.00
       
        private static integer RunCounter = 0
        private static integer FirstObjectId
        private static integer LastObjectId
         
        private static integer recursionCounter
        private static integer currentObjectId
        private static timer clock = CreateTimer()
        private static trigger eventHandler = CreateTrigger()
       
        private static method StartNextRun takes integer first, integer last returns nothing
            set FirstObjectId = first
            set LastObjectId = last
            set RunCounter = RunCounter + 1
            set recursionCounter = 0
            set currentObjectId = first
            call PauseTimer(clock)
            call TimerStart(clock, THREAD_TIMEOUT, true, function thistype.fillArray)
        endmethod
       
        private static method CleanUp takes nothing returns nothing
            call PauseTimer(clock)
            call DestroyTimer(clock)
            call DestroyTrigger(eventHandler)
            set clock = null
            set eventHandler = null
        endmethod
       
        private static method FireEvent takes nothing returns nothing
            call TriggerEvaluate(eventHandler)
        endmethod
     
        private static method fillArray takes nothing returns nothing
            local string objectName
            loop
                exitwhen ( recursionCounter > RECURSION_COUNTER_MAX or currentObjectId > LastObjectId )
           
                set objectName = GetObjectName(currentObjectId)
                if ( objectName != "Default string" and objectName != null ) then
                    set MaxIndex = MaxIndex + 1
                    set ObjectId[MaxIndex] = currentObjectId
                endif

                set currentObjectId = GetNextObjectId(currentObjectId)
                set recursionCounter = recursionCounter + 1
            endloop
           
            set recursionCounter = 0
           
            if ( currentObjectId > LastObjectId ) then
                if ( RunCounter == 1 ) then
                    call StartNextRun('S000', 'Sczz')
                else
                    call FireEvent()
                    call CleanUp()
                endif
            endif
        endmethod

        private static method onInit takes nothing returns nothing
            call StartNextRun('A000', 'Azzz')
        endmethod
   
        public static method register takes code c returns nothing
            call TriggerAddCondition(eventHandler, Condition(c))
        endmethod
    endstruct
endlibrary
 

Attachments

  • ListUnitAbilities.w3x
    21.9 KB · Views: 67
Last edited:
Level 13
Joined
Nov 7, 2014
Messages
571
I don't know why Jass lacks any useful ability-handle related functions.

Look at this:
JASS:
type ability extends agent
constant native GetSpellAbility takes nothing returns ability
native SaveAbilityHandle takes hashtable table, integer parentKey, integer childKey, ability whichAbility returns boolean
native LoadAbilityHandle takes hashtable table, integer parentKey, integer childKey returns ability
Nice API =)...

Instead the useful ability related natives use ability-ids/integers: 'Amls', 'ANcl', etc.

JASS:
// strange natives that work with ability-ids/strings o.O?
//
constant native AbilityId takes string abilityIdString returns integer
native GetAbilityEffect takes string abilityString, effecttype t, integer index returns string
native GetAbilitySound takes string abilityString, soundtype t returns string

// old ability related natives
//
constant native GetSpellAbilityId takes nothing returns integer
constant native GetSpellAbility takes nothing returns ability
native GetAbilityEffectById takes integer abilityId, effecttype t, integer index returns string
native GetAbilitySoundById takes integer abilityId, soundtype t returns string
constant native AbilityId2String takes integer abilityId returns string
native GetUnitAbilityLevel takes unit whichUnit, integer abilcode returns integer
native DecUnitAbilityLevel takes unit whichUnit, integer abilcode returns integer
native IncUnitAbilityLevel takes unit whichUnit, integer abilcode returns integer
native SetUnitAbilityLevel takes unit whichUnit, integer abilcode, integer level returns integer
native UnitAddAbility takes unit whichUnit, integer abilityId returns boolean
native UnitRemoveAbility takes unit whichUnit, integer abilityId returns boolean
native UnitMakeAbilityPermanent takes unit whichUnit, boolean permanent, integer abilityId returns boolean
native SetPlayerAbilityAvailable takes player whichPlayer, integer abilid, boolean avail returns nothing

// newish ability related natives
//
native BlzSetAbilityTooltip takes integer abilCode, string tooltip, integer level returns nothing
native BlzSetAbilityActivatedTooltip takes integer abilCode, string tooltip, integer level returns nothing
native BlzSetAbilityExtendedTooltip takes integer abilCode, string extendedTooltip, integer level returns nothing
native BlzSetAbilityActivatedExtendedTooltip takes integer abilCode, string extendedTooltip, integer level returns nothing
native BlzSetAbilityResearchTooltip takes integer abilCode, string researchTooltip, integer level returns nothing
native BlzSetAbilityResearchExtendedTooltip takes integer abilCode, string researchExtendedTooltip, integer level returns nothing
native BlzGetAbilityTooltip takes integer abilCode, integer level returns string
native BlzGetAbilityActivatedTooltip takes integer abilCode, integer level returns string
native BlzGetAbilityExtendedTooltip takes integer abilCode, integer level returns string
native BlzGetAbilityActivatedExtendedTooltip takes integer abilCode, integer level returns string
native BlzGetAbilityResearchTooltip takes integer abilCode, integer level returns string
native BlzGetAbilityResearchExtendedTooltip takes integer abilCode, integer level returns string
native BlzSetAbilityIcon takes integer abilCode, string iconPath returns nothing
native BlzGetAbilityIcon takes integer abilCode returns string
native BlzSetAbilityActivatedIcon takes integer abilCode, string iconPath returns nothing
native BlzGetAbilityActivatedIcon takes integer abilCode returns string
native BlzGetAbilityPosX takes integer abilCode returns integer
native BlzGetAbilityPosY takes integer abilCode returns integer
native BlzSetAbilityPosX takes integer abilCode, integer x returns nothing
native BlzSetAbilityPosY takes integer abilCode, integer y returns nothing
native BlzGetAbilityActivatedPosX takes integer abilCode returns integer
native BlzGetAbilityActivatedPosY takes integer abilCode returns integer
native BlzSetAbilityActivatedPosX takes integer abilCode, integer x returns nothing
native BlzSetAbilityActivatedPosY takes integer abilCode, integer y returns nothing
native BlzUnitHideAbility takes unit whichUnit, integer abilId, boolean flag returns nothing
native BlzUnitDisableAbility takes unit whichUnit, integer abilId, boolean flag, boolean hideUI returns nothing
native BlzGetAbilityManaCost takes integer abilId, integer level returns integer
native BlzGetAbilityCooldown takes integer abilId, integer level returns real
native BlzSetUnitAbilityCooldown takes unit whichUnit, integer abilId, integer level, real cooldown returns nothing
native BlzGetUnitAbilityCooldown takes unit whichUnit, integer abilId, integer level returns real
native BlzGetUnitAbilityCooldownRemaining takes unit whichUnit, integer abilId returns real
native BlzEndUnitAbilityCooldown takes unit whichUnit, integer abilCode returns nothing
native BlzGetUnitAbilityManaCost takes unit whichUnit, integer abilId, integer level returns integer
native BlzSetUnitAbilityManaCost takes unit whichUnit, integer abilId, integer level, integer manaCost returns nothing
It seems to me that most of the above could work with ability-handles instead of integers.

Ability-handles seem more "powerful" than ability-ids/integers:
JASS:
// "cool" but non-existent ability-handle related natives
//
native BlzWidgetAbilityListFirst takes widget w returns ability
native BlzAbilityListNext takes ability a returns ability
native BlzGetAbilityId take ability a returns integer

// misc
native BlzStringObjectId takes integer objectId returns string // 'hfoo' => "hfoo"

...
local ability a = BlzWidgetAbilityListFirst(some_unit or some_item)
loop
    exitwhen a == null

    call BJDebugMsg(BlzStringObjectId(BlzGetAbilityId(a)))

    set a = BlzAbilityListNext(a)
endloop
...
 
Could improve the GetNextObjectId function, but it's still too much operations at once to make it lagfree. Though, operations can now be timely splitted using:
JASS:
private static constant integer RECURSION_COUNTER_MAX = 20000
private static constant real    THREAD_TIMEOUT        = 0.00
.. depending on how fast the API needs to be provided. ^^
 
Last edited:
Level 13
Joined
Nov 7, 2014
Messages
571
It seems to me that simply listing the possible abilities that a unit can have would save some "looping around". Also while listing the abilities one could also annotate/tag them so they can be filtered/queried.

JASS:
struct Abi
    integer id
    boolean aura
    boolean aoe
    boolean singletarget
    boolean singletarget_unit
    boolean singletarget_point
    boolean notarget
    boolean passive
    boolean summon
    boolean buf
    boolean buf_good
    boolean buf_bad
    boolean buf_both // Unholy Frenzy?
endstruct

globals
    Abi array Abi_list
    integer Abi_list_len = 0
    hashtable abi_ht = InitHashtable()
endglobals

function Abi_from_id takes integer id returns Abi
    return Abi(LoadInteger(abi_ht, 0, id))
endfunction

function Abi_id takes integer id returns Abi
    local Abi a = Abi.create()
    set a.id = id

    set Abi_list[Abi_list_len] = a
    set Abi_list_len = Abi_list_len + 1
    call SaveInteger(abi_ht, 0, id, integer(a))

    return a
endfunction

function Abi_aura takes integer id returns Abi
    local Abi a = Abi_id(id)
    set a.aura = true
    return a
endfunction

function Abi_aoe ...
function Abi_notarget ...
fucntion Abi_passive ...
fucntion Abi_summon ...
fucntion Abi_buf ...

function Abi_singletarget takes integer id, integer kind returns Abi
    local Abi a = Abi_id(id)
    set a.singletarget = true
    if kind == 1 then
        set a.singletarget_unit = true
    elseif kind == 2 then
        set a.singletarget_point = true
    endif
    return a
endfunction

function Abi_singletarget_unit takes integer id returns Abi
    return Abi_singletarget(id, 1)
endfunction

function Abi_singletarget_point takes integer id returns Abi
    return Abi_singletarget(id, 2)
endfunction

function Abi_setup takes nothing returns nothing
    // Archmage
    call Abi_aoe('AHbz') Blizzard
    call Abi_summon('AHwe') // Water Elemental
    call Abi_aura('AHab') // Brilliance Aura
    call Abi_singletarget_unit('AHab') // Brilliance Aura

    // Tauren Chieftain
    call abi_aoe('AOsh') // Shockwave
    call abi_aoe('AOws') // War Stomp
    call abi_aura('AOae') // Endurance Aura
    call abi_notarget('AOre') // Reincarnation
endfunction

globals
    integer Abi_list_iter_idx // should probably be a local
endglobals

//! textmacro Abi_list_iter_begin takes x, u
set Abi_list_iter_idx = 0
loop
    exitwhen Abi_list_iter_idx == Abi_list_len
    if 0 != GetUnitAbilityLevel($u$, Abi_list[Abi_list_iter_idx]) then
        set $x$ = Abi_list[Abi_list_iter_idx]
//! endtextmacro

//! textmacro abi_list_iter_end
    endif
    set Abi_list_iter_idx = Abi_list_iter_idx + 1
endloop
//! endtextmacro

function unit_has_aura takes unit u returns boolean
    local Abi a
    //! runtextmacro Abi_list_iter_begin("a", "u")
        if a.aura then
            return true
        endif
    //! runtextmacro Abi_list_iter_end()
    return false
endfunction

function unit_remove_bufs takes unit u, integer kind returns nothing
    local Abi a

    if kind == 1 then
        //! runtextmacro Abi_list_iter_begin("a", "u")
            if a.buf and a.good then
                call UnitRemoveAbility(u, a.id)
            endif
        //! runtextmacro Abi_list_iter_end()

    elseif kind == 2 then
        //! runtextmacro Abi_list_iter_begin("a", "u")
            if a.buf and a.bad then
                call UnitRemoveAbility(u, a.id)
            endif
        //! runtextmacro Abi_list_iter_end()

    elseif kind == 3 then
        //! runtextmacro Abi_list_iter_begin("a", "u")
            if a.buf then
                call UnitRemoveAbility(u, a.id)
            endif
        //! runtextmacro Abi_list_iter_end()

    endif
endfunction

...
 
Level 13
Joined
Nov 7, 2014
Messages
571
But for tagging you need the manual effort in Abi_Setup?
Yes. I guess one can also annoate the abilities in the Object Editor and then use (GetAbilityEffectById or GetAbilitySoundById), maybe...

What do you mean?

JASS:
constant integer FIRST_OBJECT_ID = 'A000'
constant integer LAST_OBJECT_ID = 'Azzz'

There are 62 digits: '0' .. '9', 'A' .. 'Z' and 'a' .. 'z'.
You are iterating from 'A000' to 'Azzz' (62^3 = 238_328, iterations). How many abilities is one going to manually "register", 100, 1000? A lot less than 238_328, for sure.
 
In standard editor I would not know a good way, currently. Using own constants is maybe what I would currently do.

Not that it's not possible to loop through valid order ids and try cast the spell on ground/tree/unit/no-target, but ..
  • it breaks current order
  • requires enough mana
  • unit needs all spell requirements one could think of (night-time, builings)
.. so one would definitly need additional dummies.

From common.j:
JASS:
constant native OrderId                    takes string  orderIdString     returns integer
constant native OrderId2String             takes integer orderId           returns string
constant native UnitId                     takes string  unitIdString      returns integer
constant native UnitId2String              takes integer unitId            returns string

// Not currently working correctly...
constant native AbilityId                  takes string  abilityIdString   returns integer
constant native AbilityId2String           takes integer abilityId         returns string
I think best would be Blizzard adds your suggested functionality, I doubt it would be hard. Maybe you could poke them.
 
Last edited:
Yes, you just need to output the value of UnitAbilityList.AbilityId[currentIndex] from the demo he made.

I tested: I made one ability "[A000] Flame Strake 2" from "[AHfs] Flame Strike", then replaced it for unit "Blood Mage".

I ran the demo and selected the unit.
UnitAbilityList.AbilityId[currentIndex] seems to be equal to 'A000', not 'AHfs' as I expected.

I'd like to get the id of the default ability my custom ability was made from ^^
 
It is not possible within Scripting alone, currently Maybe such feature will come who knows. But One could get that by converting ones data to slk/lni parse that Infos and generate a hashtable Code out of that that Code one puts into the main map and can load data from that hashtable. Disadvantage one has to update that with every release but one is not Bound to the Scripting api also one need a tool that generates the Code automatically cause by Hand it is to much work.
 
Okay..
Also a last question: some abilities have a code that doesn't start by "A". For example:
upload_2019-9-7_11-12-9.png


This means this system won't discover on init them, and thus won't detect if a unit has them no ?
 

Wrda

Spell Reviewer
Level 26
Joined
Nov 18, 2012
Messages
1,887
I'd like to get the id of the default ability my custom ability was made from ^^
That's pretty much pointless.

In my case, I was thinking about getting all aura abilities in the game for some system^^
Then do it, manually, the good old days you know. Don't tell me you have 100 auras :eek:
Your last question: No. It loops from A000, to Azzz. It's posted above...
 
This means this system won't discover on init them, and thus won't detect if a unit has them no ?
Oh, ok. It doesn't work with the special ones currently, I will have a look. Perhaps there are not too many, and I would just register them by hand. But looping from A000-Szzz is probably not a good solution.
 
Oh, ok. It doesn't work with the special ones currently, I will have a look. Perhaps there are not too many, and I would just register them by hand. But looping from A000-Szzz is probably not a good solution.

Yes it seems only A000-Azzz to S000-SZZZ are used for abilities no ?

A question: are the IDs case sensitive ?

Also, if you would like to reduce the time at game launch, I think the best optimizations would be to:
- Replace the default abilities ranges by static lists [Aaa0 to Azzz] & [Saa0 to Szzz] (ex: for v 1.31b)
- For custom abilities, let user customize the scan ranges [A000 to A9ZZ] and [S000 to S9ZZ].

In most cases, I think [A000 to A0ZZ] and [S000 to S0ZZ] are enough :)
 
Yes it seems only A000-Azzz to S000-SZZZ are used for abilities no ?
I'm not sure, no editor at current pc. But probably only a few ones start with 'S', not sure if something else.

A question: are the IDs case sensitive ?
Definitely, because they are ASCII symbols. While 'A' has a value of 65, 'a' has a value of '97'.

For most cases better loops could be used, indeed. :) One can change it if needed. And I maybe change the structure a bit, to process more batches, like from all with 'Axxx' and then afterwards with 'Sxxx'.
But as mentioned above, maybe it's just best to make the 'S' handwritten, as they are maybe only a few, and won't change. For newly created custom abilities probably always 'A' is used as start.
 
Some delay, but slightly updated. With some 'Sxxx' abilities.

One can start an other loop run like in code here:

JASS:
if ( currentObjectId > LastObjectId ) then
    if ( RunCounter == 1 ) then
        call StartNextRun('S000', 'Sczz')
    else
       call FireEvent()
       call CleanUp()
    endif
endif
To add example from loop from 'Z000' to 'Zzzz' it's like:
JASS:
if( currentObjectId > LastObjectId ) then
    if ( RunCounter == 1 ) then
        call StartNextRun('S000', 'Sczz')
    else if ( RunCounter == 2 ) then
        call StartNextRun('Z000', 'Zzzz')
    else
       call FireEvent()
       call CleanUp()
    endif
endif
 

I like your work. I am trying to work on a less time-consuming version with a few hypothesis:
- Defaul abilities codes will be hardcoded.
- Instead of browsing the whole range of abilities A000 to Azzz and S000 to Szzz, I'll try to stop after a given number of "fails" (i.e. ids not matching an ability).
- I will use "GetAbilityName(id) != null" as test instead of your "UnitAddAbility(dummy, <code> or UnitRemoveAbility(dummy, <code>", I hope it will be faster because it does not preload all abilities^^


Edit: boring Blizzard, for example for ability Aura of Blight I find the following ids:
AABR
AABr
AAbR
AAbr

etc...
Apparently for default abilities only, not for custom ones. Thus I am forced to also test UnitAddAbiliy & UnitRemoveAbility for those.

Also GetAbilityName('StWP') returns "Town of Scroll Portal", the name of an item of code 'stwp'...

And finally Chaos abilities (Sca1, Sca2, ...) don't pass the Add/Remove ability test.
 
Last edited:
It is incredibly demanding in try & tests but I managed to achieve dynamically getting a sorted list of all abilities in game with only 2s (hardcoded standard ones + optimized scan of custom ones).

However for the moment I haven't found a way to make the function that lists a unit's abilities quick (currently browsing ~1200 abilities and testing all of them takes about 200ms...)
 
Status
Not open for further replies.
Top