• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Auramancer v1.3 [Aura/Buffs/Debuffs System]

Description

Code

Demo

How to Import

Credits

Changelog

More Clarification


DESCRIPTION:
A system that assigns bonuses (in the form of spell books) to any specific de/buff.
Works with de/buffs obtained from anywhere including auras.

HOW TO USE:
Call the function below at map initialization.

API:
call Auramancer.register(BUFF_ID, ABILITY_ID, AURA_LEVEL)

Expectation from the system:
The system accepts a pair of BUFF_ID and ABILITY_ID.
Whenever a unit gains the BUFF_ID you registered, the system applies the
appropriate ABILITY_ID and AURA_LEVEL to the unit. Should a moment arise
where the unit obtains two BUFF_ID's that shares the same ABILITY_ID, the
ABILITY_ID with the higher AURA_LEVEL gets applied to the unit.

Expectation from the users:
The user is expected to call Auramancer.register(BUFF_ID, ABILITY_ID, AURA_LEVEL)
at map initialization either through GUI or vJass initialization.

If the user's BUFF_ID supports multiple levels, the user must declare each BUFF_ID for each
level.BUFF_ID with multiple levels can share the same spell book. The user must specify
the AURA_LEVEL. ABILITY_ID must support the levels defined by AURA_LEVEL.

JASS:
library Auramancer
//-----------------------------------------------------------------------
// Auramancer v1.3
// Author: Blightsower
//
// DESCRIPTION:
// A system that assigns bonuses (in the form of spell books) to any specific de/buff.
// Works with de/buffs obtained from anywhere including auras.
//
// HOW TO USE:
// Call the function below at map initialization.
//
// API:
// call Auramancer.register(BUFF_ID, ABILITY_ID, AURA_LEVEL)
//
// Expectation from the system:
// The system accepts a pair of BUFF_ID and ABILITY_ID.
// Whenever a unit gains the BUFF_ID you registered, the system applies the
// appropriate ABILITY_ID and AURA_LEVEL to the unit. Should a moment arise
// where the unit obtains two BUFF_ID's that shares the same ABILITY_ID, the
// ABILITY_ID with the higher AURA_LEVEL gets applied to the unit.
//
// Expectation from the users:
// The user is expected to call Auramancer.register(BUFF_ID, ABILITY_ID, AURA_LEVEL)
// at map initialization either through GUI or vJass initialization.
//
// If the user's BUFF_ID supports multiple levels, the user must declare each BUFF_ID for each
// level.BUFF_ID with multiple levels can share the same spell book. The user must specify
// the AURA_LEVEL. ABILITY_ID must support the levels defined by AURA_LEVEL.
//
//---------------------------------------------------------------
// Requirements:
//  - Enable JassHelper
//
// How to enable JassHelper
//  - In the Trigger Editor window, look for the menu labeled JassHelper
//    and click Enable JassHelper
//-------------------------------------------------------------------------
// HOW TO IMPORT:
//  - Copy and paste Auramancer to your map
//-----------------------------------------------------------------------
// SPECIAL THANKS
// Antares
//-----------------------------------------------------------------------

    //-----------------------------------------------------------------------
    // CONFIGURATION
    //-----------------------------------------------------------------------
    globals
        private real timerInterval = .03
        private real auramancerStartDelay = 0.
    endglobals
    //-----------------------------------------------------------------------
    // END OF CONFIGURATION
    //-----------------------------------------------------------------------

    private module CircularLinkedList
        readonly thistype next
        readonly thistype prev

        method init takes nothing returns thistype
            set next = this
            set prev = this

            return this
        endmethod

        method pushBack takes thistype node returns thistype
            set node.prev = prev
            set node.next = this
            set prev.next = node
            set prev = node

            return node
        endmethod

        method pushFront takes thistype node returns thistype
            set node.prev = this
            set node.next = next
            set next.prev = node
            set next = node

            return node
        endmethod

        method pop takes nothing returns nothing
            set prev.next = next
            set next.prev = prev
        endmethod
    endmodule

    module Auramancer
        implement CircularLinkedList
        private integer buffId
        private integer bonusId
        private integer auraLevel
        private group buffGroup = CreateGroup()

        private static timer auraTimer = CreateTimer()
        private static group g = CreateGroup()
        private static rect r
        private static thistype curr = 0

        private static method Check takes nothing returns boolean
            local unit u = GetFilterUnit()
            local thistype this = curr
            local integer bonusLevel

            if GetUnitAbilityLevel(u, .buffId) > 0 then              
                if not(IsUnitInGroup(u, .buffGroup)) then
                    // when unit is first added to the group, its ability level will match the
                    // aura level
                    if GetUnitAbilityLevel(u, .bonusId) == 0 then                          
                        call UnitAddAbility(u, .bonusId)
                    endif                                            
                    call SetUnitAbilityLevel(u, .bonusId, .auraLevel)
                    call GroupAddUnit(.buffGroup , u)
                else
                    // only happens when the unit is already in the group but the system
                    // is unsure if the unit has the correct level of bonuses applied
                    set bonusLevel = GetUnitAbilityLevel(u, .bonusId)
                    if bonusLevel == 0 then
                        call UnitAddAbility(u, .bonusId)
                        call SetUnitAbilityLevel(u, .bonusId, .auraLevel)
                    else
                        if bonusLevel < .auraLevel then
                            call SetUnitAbilityLevel(u, .bonusId, .auraLevel)
                        else
                            call SetUnitAbilityLevel(u, .bonusId, bonusLevel)
                        endif
                    endif
                endif
            // unit is expelled from the group and bonuses revoked when it no longer bears the buff
            elseif IsUnitInGroup(u, .buffGroup) then                 
                call UnitRemoveAbility(u, .bonusId)
                call GroupRemoveUnit(.buffGroup , u)
            endif

            call GroupRemoveUnit(g,u)
 
            return false
        endmethod

        private static method auraLoop takes nothing returns nothing
            call GroupEnumUnitsInRect(g, r, Filter(function Auramancer.Check))
            set curr = curr.next
        endmethod

        private static method disableAbility takes integer a returns nothing
            local integer i = 1
            loop
                exitwhen i > 24
                call SetPlayerAbilityAvailableBJ( false, a, ConvertedPlayer(i))
                set i = i + 1
            endloop
        endmethod

        static method register takes integer boof, integer bonus, integer level returns nothing
            local thistype this = thistype.allocate()

            set .buffId = boof
            set .bonusId = bonus
            set .auraLevel = level
            call disableAbility(.bonusId)

            if curr == 0 then
                call this.init()
                set curr = this
            else

                call curr.pushBack(this)
            endif

        endmethod
 
        static method auramancerTimerStart takes nothing returns nothing
            set .r = GetPlayableMapRect()
            call TimerStart(auraTimer, timerInterval, true, function thistype.auraLoop)
        endmethod

    endmodule
 
    struct Auramancer
        implement Auramancer
 
        private static method auramancerDelayedStart takes nothing returns nothing
            call auramancerTimerStart()
        endmethod

        static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterTimerEventSingle(t, auramancerStartDelay)
            call TriggerAddAction(t, function thistype.auramancerDelayedStart)
            set t = null
        endmethod

    endstruct

endlibrary

Sample usage.

JASS:
library Demo initializer Init requires Auramancer
    // the spell shares the same spell book.
    function Init takes nothing returns nothing
        // Savage aura level 1
        call Auramancer.register('B001', 'A004', 1)
        // Savage aura level 2
        call Auramancer.register('B002', 'A004', 2)
        // Savage aura level 3
        call Auramancer.register('B000', 'A004', 3)
    endfunction
endlibrary

Sample usage if you prefer GUI for your configuration
(Make sure that the bonus is pointing to a spell book)
  • Demo in GUI
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Set VariableSet Buff = Savage Aura - (Level 1)
      • Set VariableSet Bonus = Savage Aura
      • Set VariableSet Level = 1
      • Custom script: call Auramancer.register(udg_Buff, udg_Bonus, udg_Level)
      • -------- --------
      • Set VariableSet Buff = Savage Aura - (Level 2)
      • Set VariableSet Bonus = Savage Aura
      • Set VariableSet Level = 2
      • Custom script: call Auramancer.register(udg_Buff, udg_Bonus, udg_Level)
      • -------- --------
      • Set VariableSet Buff = Savage Aura - (Level 3)
      • Set VariableSet Bonus = Savage Aura
      • Set VariableSet Level = 3
      • Custom script: call Auramancer.register(udg_Buff, udg_Bonus, udg_Level)
Only register a buff once to avoid any unexpected results.


Requirements:
  • Enable JassHelper (from the latest version of the world editor)

How to enable JassHelper
  • In the Trigger Editor window, look for the menu labeled JassHelper and click Enable JassHelper

HOW TO IMPORT:
  • Copy and paste Auramancer script to your map


Special Thanks:
Antares

v1.0
- initial release
v1.1
- fixed the known leaks
v1.2
- uses unit indexer and optimized the code
v1.3
- now supports levels
- removed unit indexer

The bonuses that you defined will be applied to the unit regardless
of how the unit got the de/buff. It could be from an aura, a point and
click ability, it could even be from a stolen de/buff and it would still work.

You can have multiple auras in your map that uses the same de/buff
and it will still give you the correct bonuses. (In the event that a unit bears
multiple de/buffs from the same bonus book, the highest one will take
priority).


keywords:
aura, Aura, AUra, AURa, AURA, aURA, auRA, aurA, aura, aura, aura, aura, aura, aura
Contents

Blightsower Auramancer v1.3 (Map)

Reviews
Antares
All issues were addressed. Works nicely for tracking auras and is reasonably performant. Just don't use it in a 24-player total war... I don't know the genres... Approved
Level 13
Joined
Dec 12, 2021
Messages
146
Exactly what I was looking for, it's a shame that my WE does not support vJass configuration, would you please have the configuration in GUI? By the way, does it leak? because a while ago I got a GUI configuration for an aura system but it slowed down the game a lot and caused a lot of leaks, therefore it was not something viable
 
Exactly what I was looking for, it's a shame that my WE does not support vJass configuration, would you please have the configuration in GUI? By the way, does it leak? because a while ago I got a GUI configuration for an aura system but it slowed down the game a lot and caused a lot of leaks, therefore it was not something viable
We can arrange that. I don't think it has leaks.

Edit: @Dominus15 - in not entirely sure of what you mean when you said vJass is not supported in your editor but I'm going to assume that youre using an old version. Anyway, I've added a pure GUI version.
 
Last edited:
So, I noticed the code operates on high accuracy (0.03) with each process running N*M times, where N is the number of units and M is the number of buffs registered. My concern would be the intensity of the process, since group creation-destruction can be a heavy one.

I was thinking perhaps an approach that works by utilizing OnIndex event from Unit Indexers and the like can help reduce the overhead from the group creation and destruction process by having one global group that have units added and removed on their respective index event. It is not noticeable in the test map unless the number of unit is significantly increased.
 
So, I noticed the code operates on high accuracy (0.03) with each process running N*M times, where N is the number of units and M is the number of buffs registered. My concern would be the intensity of the process, since group creation-destruction can be a heavy one.

I was thinking perhaps an approach that works by utilizing OnIndex event from Unit Indexers and the like can help reduce the overhead from the group creation and destruction process by having one global group that have units added and removed on their respective index event. It is not noticeable in the test map unless the number of unit is significantly increased.
that is actually an excellent suggestion.
 
JASS:
set g = GetUnitsInRectMatching(r, null)
This function does not add units to g, but creates a new group, so you're leaking a massive amount of groups. The function you want is.
JASS:
GroupEnumUnitsInRect(g, r, null)
currently fixing it.

Edit: both versions have been updated. vJass now uses unit indexer to not run into phantom unit values.
 
Last edited:
With this approach, we avoid writing codes that calculates distances when making
custom auras and instead leverage the game's existing aura system. We only need
to account for the event where a unit gains or loses a de/buff
You say this as though this is a plus, but surely filtering out units based on if there even is a buff-giver in the vicinity is a good thing?

Your approach definitely works, but it is quite the blunt, brute-force approach with no regards for performance.

Let's assume we have a decently large map with 200 units on it and 3 different auras that need to be tracked. Each aura has three levels, so that's actually 9 different buffs, which makes 1800 checks.

You enum all units in the map area 9 times. That's about 1 millisecond total. Each check on a unit will take roughly 2 microseconds by my estimation, so that's another 3.6 milliseconds for a total of 4.6 milliseconds. And that's once every 30 milliseconds.

So, the CPU is busy about 15% of its total allotted time just running your script to check which units are affected by auras, and it might even already be enough to freeze someone's game if they have a potato pc.

The JASS version definitely works perfectly fine on smaller maps, though. But then translate your JASS code into GUI, you absolute madman :plol:. Why not just make a GUI interface for your JASS version? That would be totally reasonable.

You can optimize your JASS version by inverting the levels of the two loops so that you're not enumerating all units on the map for each aura.
 
You say this as though this is a plus, but surely filtering out units based on if there even is a buff-giver in the vicinity is a good thing?
im under the misapprehension that one of the pitfalls of an aura system is that proximity calculations is too taxing to calculate thats why i chose this approach, is it not?

The JASS version definitely works perfectly fine on smaller maps, though. But then translate your JASS code into GUI, you absolute madman :plol:. Why not just make a GUI interface for your JASS version? That would be totally reasonable.
Im definitely scrapping the GUI one.
You can optimize your JASS version by inverting the levels of the two loops so that you're not enumerating all units on the map for each aura.
that one i can do.
 
im under the misapprehension that pitfalls of an aura system is that proximity calculations is too taxing to calculate thats why i chose this approach, is it not?
Can't you just GroupEnumUnitsInRange around each aura source? I would be surprised if that were more taxing in more than the minority of situations.

Some more benchmarking revealed that this is a more optimal solution using groups:
vJASS:
private static method Check takes nothing returns boolean
	local unit u = GetFilterUnit()
	local thistype this

	set this = thistype(0).next
	loop 
		exitwhen this == 0

		if GetUnitAbilityLevel(u, .buffId) > 0 then
			if not(IsUnitInGroup(u, .buffGroup)) then
				call UnitAddAbility(u, .bonusId)
				call GroupAddUnit(.buffGroup , u)
			endif
		elseif IsUnitInGroup(u, .buffGroup) then
			call UnitRemoveAbility(u, .bonusId)
			call GroupRemoveUnit(.buffGroup , u)
		endif

		set this = this.next
	endloop

	return false
endmethod

private static method auraLoop takes nothing returns nothing
	local unit u = null
	local thistype this
	local integer buffLevel

	call GroupEnumUnitsInRect(g, r, Filter(function AuraRegister.Check))
endmethod

I don't know exactly how these UnitIndexers work. Do they store all units in a linked list or array? If so, can you use that array to loop through all units? If you can replace all that with simply
JASS:
set u = UnitList[i]
//Do Stuff
set i = i + 1
that should make your code much faster if I'm not mistaken.
 
I don't know exactly how these UnitIndexers work. Do they store all units in a linked list or array? If so, can you do that array to loop through all units? If you can replace all that with simply
i dont know exactly how it works neither but the indexer definitely came after the codebase. right now, it exists to protect against phantom unit data that i read from some thread. i dont use unit indexers so i wouldnt know but ill find out.

Can't you just GroupEnumUnitsInRange around each aura source? I would be surprised if that were more taxing in more than the minority of situations.
we can definitely try.
Edit: @Antares - hold on, that would be outside the scope. this is not an aura system. the system only cares if the unit has a buff that has been registered, which means proximity does not matter at all. as long as the unit has the buff, we assign the bonuses.
 
Last edited:
I don't know exactly how these UnitIndexers work. Do they store all units in a linked list or array? If so, can you use that array to loop through all units? If you can replace all that with simply
JASS:
set u = UnitList[i]
//Do Stuff
set i = i + 1
that should make your code much faster if I'm not mistaken.
Im unable to find the resources that will explain this, so i decided to keep the existing codebase.

If anyone can direct me to the resource where I can read how to work with the unit indexer, it will be greatly appreciated.

Some more benchmarking revealed that this is a more optimal solution using groups:
vJASS:
private static method Check takes nothing returns boolean
local unit u = GetFilterUnit()
local thistype this

set this = thistype(0).next
loop
exitwhen this == 0

if GetUnitAbilityLevel(u, .buffId) > 0 then
if not(IsUnitInGroup(u, .buffGroup)) then
call UnitAddAbility(u, .bonusId)
call GroupAddUnit(.buffGroup , u)
endif
elseif IsUnitInGroup(u, .buffGroup) then
call UnitRemoveAbility(u, .bonusId)
call GroupRemoveUnit(.buffGroup , u)
endif

set this = this.next
endloop

return false
endmethod

private static method auraLoop takes nothing returns nothing
local unit u = null
local thistype this
local integer buffLevel

call GroupEnumUnitsInRect(g, r, Filter(function AuraRegister.Check))
Unfortunately, this approach has caused the spell to have unexpected behavior so im keeping my firstofgroup enumeration.

Spell has been updated to v1.2 - it now uses most changes prescribed by @Antares
 
Last edited:
Level 13
Joined
Dec 12, 2021
Messages
146
base.gif
Demo in GUI
  • joinminus.gif
    events.gif
    Events
    • line.gif
      joinbottom.gif
      folder.gif
      Map initialization
  • join.gif
    cond.gif
    Conditions
  • joinbottomminus.gif
    actions.gif
    Actions
    • empty.gif
      join.gif
      set.gif
      Set VariableSet Buff = Savage Aura - (Level 1)
    • empty.gif
      join.gif
      set.gif
      Set VariableSet Bonus = Savage Aura
    • empty.gif
      join.gif
      set.gif
      Set VariableSet Level = 1
    • empty.gif
      join.gif
      page.gif
      Custom script: call Auramancer.register(udg_Buff, udg_Bonus, udg_Level)
    • empty.gif
      join.gif
      comment.gif
      -------- --------
    • empty.gif
      join.gif
      set.gif
      Set VariableSet Buff = Savage Aura - (Level 2)
    • empty.gif
      join.gif
      set.gif
      Set VariableSet Bonus = Savage Aura
    • empty.gif
      join.gif
      set.gif
      Set VariableSet Level = 2
    • empty.gif
      join.gif
      page.gif
      Custom script: call Auramancer.register(udg_Buff, udg_Bonus, udg_Level)
    • empty.gif
      join.gif
      comment.gif
      -------- --------
    • empty.gif
      join.gif
      set.gif
      Set VariableSet Buff = Savage Aura - (Level 3)
    • empty.gif
      join.gif
      set.gif
      Set VariableSet Bonus = Savage Aura
    • empty.gif
      join.gif
      set.gif
      Set VariableSet Level = 3
    • empty.gif
      joinbottom.gif
      page.gif
      Custom script: call Auramancer.register(udg_Buff, udg_Bonus, udg_Level)

Is this just supposed to be the GUI or are there more commands for it? (My version does not support the map and I cannot open it in WE and therefore I need the commands in GUI)
 
I'm not referring to the contents of the map, i was referring to the code that you see on the resource's description. What happens when you paste that one into your map? I only recently started writing things in vJass so I'm not sure about older set ups. You might need to have JNGP into your map to be able to use this one. Its best to ask @Uncle for sure.
 

Uncle

Warcraft Moderator
Level 70
Joined
Aug 10, 2018
Messages
7,391
I'm not referring to the contents of the map, i was referring to the code that you see on the resource's description. What happens when you paste that one into your map? I only recently started writing things in vJass so I'm not sure about older set ups. You might need to have JNGP into your map to be able to use this one. Its best to ask @Uncle for sure.
I only started learning to program/mod wc3 and such in 2019, so I'm not too familiar with anything besides the 1.31+ World Editor and Lua. Anyway, it seems like the user forgot to import your code, I imagine it'll work fine as long as they are on a recent enough version.

Maybe update the How To Import tab to be a bit more specific about needing JassHelper only on newer versions and that Auramancer is the script.
 
Last edited:
Level 13
Joined
Dec 12, 2021
Messages
146
I tried my best and I didn't succeed, maybe it's because of the "call" that my version of WE doesn't detect, and also I don't know how to use the spell books in this case, also I think there's another detonator missing from my map but It's not in GUI in the demo that's why I didn't put it here, anyway I didn't achieve anything successful, if you could help me I would really appreciate it.
 

Attachments

  • TestAura.w3x
    16.8 KB · Views: 1
I tried my best and I didn't succeed, maybe it's because of the "call" that my version of WE doesn't detect, and also I don't know how to use the spell books in this case, also I think there's another detonator missing from my map but It's not in GUI in the demo that's why I didn't put it here, anyway I didn't achieve anything successful, if you could help me I would really appreciate it.
Here you go. Whoops i forgot to delete the demo code on the first one. Here is another one with no demo code.
 

Attachments

  • TestAura.w3x
    22.6 KB · Views: 3
Last edited:
View attachment 492922

I get that it is impossible to open the map in my WE, it must be because of my old version that I use, otherwise, you could just pass me the triggers in GUI through the HIVE, and I transcribe it to my map
GUI is not recommended for this type of system as mentioned by the moderator. Isnt there a patch that will allow you to open newer maps?
Anyway, i exported the trigger data. Hope it helps
 

Attachments

  • Blightsower Auramancer v1.3.wtg
    1.5 KB · Views: 4
  • TestAura.wtg
    1.4 KB · Views: 0
Last edited:

Uncle

Warcraft Moderator
Level 70
Joined
Aug 10, 2018
Messages
7,391
When in doubt, pictures:

Step 1: Copy and Paste the code into a new custom text file:
paste_code.png

Step 2: Create the Aura ability that your Hero learns:
aura.png

Step 3: Create a Buff for each Level of your Aura ability. Make sure the Aura ability uses these Buffs at Level 1, 2, 3:
buffs.png

Step 4: Create the Spell Book abiltity which will contain the effects of your Aura:
spellbook.png

Step 5: Register these new Abilities/Buffs in a GUI trigger:
  • Aura
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Set VariableSet Buff = Aura x 1
      • Set VariableSet Bonus = Spell Book (Custom)
      • Set VariableSet Level = 1
      • Custom script: call Auramancer.register(udg_Buff, udg_Bonus, udg_Level)
      • -------- --------
      • Set VariableSet Buff = Aura x 2
      • Set VariableSet Bonus = Spell Book (Custom)
      • Set VariableSet Level = 2
      • Custom script: call Auramancer.register(udg_Buff, udg_Bonus, udg_Level)
      • -------- --------
      • Set VariableSet Buff = Aura x 3
      • Set VariableSet Bonus = Spell Book (Custom)
      • Set VariableSet Level = 3
      • Custom script: call Auramancer.register(udg_Buff, udg_Bonus, udg_Level)
Now when your Hero learns the Aura ability it will share whatever bonuses you put into the Spell Book with nearby allies.
 
Last edited:
Level 13
Joined
Dec 12, 2021
Messages
146
Sorry for the delay uncle, I just finished it and it works for me, thank you so much :D

EDIT: Hello again uncle uncle, I have tested my map again with the aura commands and they work well, except for one thing, there is abundant lag, especially when I tested it against the AI of 8 players, I don't know something I did wrong, but I followed your steps, the only defect that gives me problems is the lag, help :( @Uncle
 
Last edited:
Top