Buff Bot v1.3

This bundle is marked as high quality. It exceeds standards and is highly desirable.

Overview

Installation

Tutorial


Overview

Buff Bot is an easy-to-use and highly flexible buff- and aura-managing system that comes with an optional custom UI buff bar. Buff Bot can leverage and enhance the Warcraft III buff system, or can be used without any native buffs at all, with the buff visuals being created with special effects instead.

Buff Bot's API allows for much easier access to buff information on a unit than the default Blizzard API. You can return the list of all buffs on a unit, the level of a specific buff, the source, the number of stacks, the remaining duration and more. Like native buffs, Buff Bot buffs can be dispelled, but can also be set to undispellable or be given a specific dispel type. Auras can have fully customizable conditions determining which units they affect.

For example, here is the definition of a Rejuvenation buff defined without any native buff attached to it:

Lua:
local REJUVENATION = {
    name = "Rejuvenation",
    color = "|cff00ff00",
    tooltip = "Heals for !health! health over !duration,$! seconds.",
    type = "Magic",
    values = {
        health = 400,
        duration = 12
    },
    icon = "Icons\\Rejuvenation.blp",
    effect = "Abilities\\Spells\\NightElf\\Rejuvenation\\RejuvenationTarget.mdl",
    attachPoint = "chest",
    onPeriodic = function(target, source, values)
        SetUnitState(target, UNIT_STATE_LIFE, GetUnitState(target, UNIT_STATE_LIFE) + ALICE_Config.MIN_INTERVAL*values.health/values.duration)
    end
}

Components

  • Custom Buff Bar: The Buff Bar is a custom UI system that displays buffs in a style similar to World of Warcraft. The display is tied to a main unit that must be declared for each player. This makes it primarily useful for RPG or hero arena maps, but it can also replace the default unit buff bar if one can solve the issue of detecting the current tab-highlighted selected unit.
  • Buff Tracker: The core of the system. This component tracks all buffs on a unit, handles their special effects, and invokes their onApply, onExpire, and periodic callback functions.
  • Aura Tracker: The aura API allows the creation of custom auras that automatically apply buffs that were previously defined to units in range.

Buff Bot is built with ALICE, but only the Aura Tracker requires the full installation, while all other functionalities only require the heavily downsized version MINACE.

Installation

Copy the BuffBot script file into your map. Next, make sure you have all the requirements imported. These are:

RequirementDescription
Total InitializationNeeds no explanation.
ALICE or MINACE
MINACE is required for the Buff Tracker. The Aura Tracker requires a full ALICE installation. You should follow the ALICE installation guide on its resource page.
Easy Ability Fields (optional)Required for enabling buff data to be automatically retrieved from abilities.
Frame Recycler (optional)Required for Custom Buff Bar.
CustomBuffBot.toc
BuffIcon.fdf
BuffIconTooltip.fdf
(optional)
Required for Custom Buff Bar.

Creating a Buff

Creating a Values Table

Understanding Stat Evaluation

Creating an Aura


Creating a Buff

Before a buff can be shown in the custom buff-bar, the main hero of each player must be set with BuffBot.SetMainUnit.

A buff is represented by a data table that is passed to the BuffBot.Apply function. The data table has the following recognized fields:

FieldDescription
nameThe name of the buff displayed in the Buff Bar as well as the name used to reference it throughout your code.
tooltipThe mouse-over tooltip of the buff in the Buff Bar.
iconThe icon of the buff in the Buff Bar.
buffIf the buff should be linked to a native buff, this is its fourCC code.
abilityThe fourCC code of the ability that the dummy caster should use to apply the native buff to the unit. If an ability is set, some properties such as buff icon and duration can be automatically retrieved without you needing to set those values in the data table.
orderThe order string of the ability the dummy caster should use to apply the native buff.
durationThe duration of the buff. If a native buff is linked, the buff can expire either through the native buff expiring or by the buff reaching the duration set here. The duration can also be declared in the values table.
typeThe type is displayed in the top-right corner of the buff toolip. You can dispel buffs of only certain types. Can either be a string or a string sequence for a buff with multiple types.
colorThe color of the buff name in the tooltip. Example: "|cffff0000".
valuesA table with string keys that holds custom userdata that can be used in your callback functions.
onApplyA function that is called every time the buff is applied on a unit. It is called with the parameters (whichUnit, source, values, level, stacks).
onFirstApplySimilar to onApply, but this function is only called when the buff is applied and it wasn't active on that unit already. It is called with the parameters (whichUnit, source, values, level).
onExpireA function that is called when the buff expires on a unit. It is called with the parameters (whichUnit, source, values, level, stacks).
onPeriodicA function that is periodically called while the buff is active on a unit. It is called with the parameters (whichUnit, source, values, level, stacks).
cooldownDetermines the interval at which the onPeriodic function is called.
delayDetermines the delay of the first execution of the onPeriodic function.
isEternalIf this flag is set to true, the buff has no duration and no duration is displayed in the Buff Bar tooltip.
cannotOverwriteIf this flag is set to true, a lower level version of this buff cannot overwrite a higher level version. If a lower level version of a buff attempts to overwrite a higher level version, the BuffBot.Apply function returns false.
effectThe effect path of the special effect that should visualize the buff. You can pass a table containing multiple strings to specify multiple special effects.
attachPointThe attachment point of the special effect specified in the effect field. Defaults to "origin" if not set. Like with the effect field, you can pass a table containing multiple attachment points.
showLevelIf set to true, the level of the buff is shown in parentheses after the buff name in the tooltip.
maxStacksAn integer, that, if specified, will cause the buff to stack up multiple times if applied repeatedly.
individualFallOffIf this flag is set, each individual stack of a stacking buff gets its own expiration timer. Only the duration of the youngest stack is shown. Some functions do not work correctly on buffs with this flag.
isHiddenIf this flag is set, the buff will not appear in the buff bar.


To apply the buff to a unit, you do
Lua:
BuffBot.Apply(whichUnit, buffData, source?, level?)
Providing a source is optional. If set, fields such as buff duration and level can be retrieved from the linked ability of that unit. The source is also passed into your callback functions. If not set, the level defaults to 1.

Creating a Values Table

The values table is a powerful tool to retrieve data from buffs as well as insert the values directly into the buff tooltips. The keys of the values table
are strings that should indicate what this value is representing. Example:

Lua:
values = {
    attackDamage = 10,
    armor = 5
}

You can insert the values automatically into the tooltip by using the referenced word surrounded by two exclamation marks:
"Increases your attack damage by !attackDamage! and your armor by !armor!."

The value provided in the values table will be multiplied by the buffs's level, so, for a 3-level ability, the tooltip will be:
"Increases your attack damage by 10/20/30 and your armor by 5/10/15."

We can turn the value into a constant by adding the $ character:
"Increases your attack damage by !attackDamage! and your armor by !armor,$!."

Now, the armor increase is 5 on each level.

A value can be expressed as a percentage, by adding the percentage symbol (use \x25 instead if you're adding the tooltip via the tooltip field):
"Increases your damage by !damageIncrease,%! and your armor by !armor,$!."

A minus sign in front of the value name will show the negative value instead:
"Reduces your movement speed by !-movementSpeedReduction,\x25!."

Finally, you can use an "m" to format a time number as a minute:
"Increases your strength by !strengthBonus! for !duration,m! minutes."

All symbols can be combined in any order.

You can provide a table instead of a number to specify the value of each rank individually:

Lua:
values = {
    attackDamage = {10, 15, 20},
    armor = 5
}

The values do not have to be numbers:

Lua:
values = {
    disabledItems = {
        "health regeneration",
        "health and mana regeneration",
        "health and mana regeneration, and all abilities",
    },
}

"A curse that disables your !disabledItems!."

The value can be retrieved for calculations in your callback functions with the BuffBot.GetValue function:

Lua:
function OnDamage()
    local eventDamage = GetEventDamage()
    local whichUnit = BlzGetEventDamageTarget()
    --GetValue will return 0 if the buff is not active on that unit.
    BlzSetEventDamage(eventDamage*(1 + BuffBot.GetValue(whichUnit, "Curse of Vulnerability", "damageIncrease")))
end

Values do not have to appear in the tooltips. However, constants can only be declared through a reference in the tooltip with the $ sign.

By linking the buff with an ability, you can retrieve the values directly from the ability fields of that ability:
Lua:
ability = "Ablo"
values = {
    attackSpeed = "Blo1",
    movementSpeed = "Blo2",
}

If multiple stacks of a buff are applied on a unit, each value in the values table will be multiplied by the number of stacks, unless that value has been set to constant.


Creating Custom Stats

You can automatically link behavior to keys you use in your buffs' values tables. This is done with the BuffBot.DefineStat function. This function takes a string as the custom stat's name and defines callback functions that are automatically executed when a buff with that stat in its values table is applied to or expires from a unit.

Understanding Stat Evaluation

Buff Bot uses different callback functions and also allows function arguments for various properties. These functions are all called with different arguments, so it is bound to get a bit confusing. Let's unpack what's happening when a buff is applied on a unit:

First, Buff Bot loops over the buff's values table and copies all the values into a values table unique to that buff instance. If the value is not set to constant, the value will be multiplied by the buff's level and the number of stacks. If the value is a table, the entry at the index of the buff's level will be stored. If the value is a function, that function is evaluated and the return value is stored. An evaluator function is called with the arguments (target, source, level, stacks).

Note that, on top of all user-defined properties, some, but not all, native properties of a buff (such as duration) can also be evaluated this way.

After the buff's values have been evaluated and copied to the data table of this buff instance, the tooltip is generated and the onApply and onFirstApply functions are invoked. These functions are called with (target, source, values, level, stacks). Note that values here refers to the evaluated data table of this buff instance, not the raw data table passed into BuffBot.Apply, so if the value is supposed to scale with the buff's level, it is not necessary to multiply it with level, as this has already happened.

Finally, the callback functions defined for any present custom stats are invoked. These are called with (target, source, value, oldValue?), where value is the amount of that stat. oldValue is set for the onApply function if the buff was already present on that unit.

Creating an Aura

An aura is created with:
Lua:
BuffBot.CreateAura(source, auraData, buffData)

The buff data table is identical to the one used for regular buffs. However, for an aura, you also need to specify aura properties. These are:

FieldDescription
nameThe name of the aura. Is copied to the buff, so it does not need to be present in the buffData table.
rangeThe range of the aura. Can be a number or a function that is called with (source, level).
conditionThe condition that must be met for a unit to be affected by the aura. The function is called with the parameters (source, target, level).
effectThe special effect of the aura attached to the aura source.
attachPointThe attachment point of the aura special effect. Defaults to "origin" if not set.
accuracyA number that specifies how often the in-range check is performed for this aura. This value defaults to 1 and any other value is a multiplier for the frequency of the check.
effectOnSelfIf this flag is set, the target effect of the buff will also be applied to the aura source.

Contents

BuffBot (Binary)

BuffBot v1.3 (Map)

Reviews
Wrda
Buffbot provides an excellent amount of utility, creating and applying buffs with ease. Additionally, it has aura functionality and allows different types of dispel. The only problem is: High Quality
Level 9
Joined
Oct 20, 2010
Messages
228
I have only just begun testing this resource. On paper it seems to be everything you'd want in an aura system.

My map is fairly large, with up to 9 players controlling upward of 50+ units. Would you expect a major performance impact if installing ALICE and Buff Bot into this environment? I suppose my main question is ALICE resource intensive if not in use?
 
ALICE does incur a performance overhead as it has to constantly track the positions of all units on the map. At ~9x50 (so ~500) units, this will however be minimal and not affect performance. For each unit, you can roughly anticipate an overhead of 10 microseconds per second. If you're only using ALICE for the aura tracker, you can probably even reduce the update interval and increase cell sizes, as you don't need to be that exact with auras, so it would be even less.

The aura tracker itself is very well optimized and it would maybe start lagging only if all 500 of those units each have an aura.
 
Level 9
Joined
Oct 20, 2010
Messages
228
Originally, I had set out in exploring this resource with the hopes that it could replace Tasyen's TasUnitBonus in my map. However, probably since this is specifically a buff resource, I could not replicate most of its effects. What it did replicate, though, was tying stat boosts to buffs! Wonderful! I can't rate this anything lower than 5 stars. It does exactly what it needs to do, is relatively easy to learn, has plenty of different examples provided, and I was unsuccessful in making it fail.

In your documentation you mention custom stats, but I could not figure these out, so I did not use them. I imagine they are pretty powerful, so I hope you could add an example of using these.

I have a few questions.
  • How does this system interact with spell steal? I didn't test this, but I would ultimately hope it could run the onApply callback when moving the buff.
  • Do you recommend avoid using vanilla dispel? This is just a thought in the back of my mind, and I did test this, and the vanilla dispel never failed or did something unexpected
  • Lastly, can types be a table? Can a buff have multiple types? The system doesn't seem to come with predetermined types, so my thought is that this is meant to be used with 'buff', 'debuff', etc?

I have also edited the Elune's Grace spell to make it apply attack damage. It works, no issues, but I'd like to run it by you to see if this is how you'd do it as well.

 
Originally, I had set out in exploring this resource with the hopes that it could replace Tasyen's TasUnitBonus in my map. However, probably since this is specifically a buff resource, I could not replicate most of its effects.
You could use TasUnitBonus in your onApply and onExpire functions to apply the effect you want the buff to have, wouldn't that work?

What it did replicate, though, was tying stat boosts to buffs! Wonderful! I can't rate this anything lower than 5 stars. It does exactly what it needs to do, is relatively easy to learn, has plenty of different examples provided, and I was unsuccessful in making it fail.
Thanks, glad it's working well for you!

In your documentation you mention custom stats, but I could not figure these out, so I did not use them. I imagine they are pretty powerful, so I hope you could add an example of using these.
There are two custom stats defined below the config as examples; movementSpeedPercent and vertexColor, but I guess a more simple example would be in order. Custom stats allow you to elegantly handle the problem that you may overwrite an already existing buff. Let's say you have a buff level 1 on a unit that gives +3 strength and now you cast level 2 on it, which gives +5 strength. Now you want to modify the strength by +2, not +5. You can add the correct value by first subtracting the old amount in the onApply function.

  • How does this system interact with spell steal? I didn't test this, but I would ultimately hope it could run the onApply callback when moving the buff.
The buff on the victim would get dispelled as the system detects that the native buff it is linked to is no longer on it, but the system would not detect that the buff has been applied to the other unit. You would have to somehow detect which unit the buff landed on and then use BuffBot.Apply on that unit. The other option would be to code the spellsteal yourself, which wouldn't be too hard with Buff Bot.

  • Do you recommend avoid using vanilla dispel? This is just a thought in the back of my mind, and I did test this, and the vanilla dispel never failed or did something unexpected
If you have a native buff linked to a Buff Bot buff, it will constantly check if the buff is still applied on the unit, and run its onExpire function as soon as it is not. Also, if you set a duration for the buff, it will dispel it once the timer runs out, even if the native buff still hasn't expired.

As with many things, for dispels, using the vanilla function is easier, using the custom functions gives you greater control (such as dispelling Magic, Curse, Poison etc.).

  • Lastly, can types be a table? Can a buff have multiple types? The system doesn't seem to come with predetermined types, so my thought is that this is meant to be used with 'buff', 'debuff', etc?
Oh yea, you can declare multiple types for a buff, like this:
Lua:
type = {"Magic", "Buff"}
I think I forgot to document that. I'll have to double-check my documentation again to see if everything is correct.

I have also edited the Elune's Grace spell to make it apply attack damage. It works, no issues, but I'd like to run it by you to see if this is how you'd do it as well.

Your code runs into the problem I mentioned earlier, that you have to account for the fact that the buff could already applied to the unit and now you're increasing its attack damage twice. You have three options:
  • Define attack damage as a custom stat so you can subtract oldAmount if necessary.
  • Run BuffBot.Dispel(whichUnit, "Elune's Shield") before applying it.
  • Calculate the attack damage based on all contributions every time you apply a new buff.
 
Level 9
Joined
Oct 20, 2010
Messages
228
You could use TasUnitBonus in your onApply and onExpire functions to apply the effect you want the buff to have, wouldn't that work?
It sure does! I have had a blast playing around with this system and see myself using it from here on out. My example application is rather simple, but learned a lot while doing it. My only worry is whether or not the function 'duration' in values is going to behave how I expect it to. That is, can I use the parameters in that values function like I currently am?

 
Oh right, that's another thing I forgot to document (apparently there's a lot :plol:).

duration is unique in that it can either be set in the buffData table or the values table (and it doesn't need to be explicitly set to a constant), so what you're doing should work just fine.
 
Level 9
Joined
Oct 20, 2010
Messages
228
I had a lot of headache with buff systems and now I found this, what a great time to be alive.

This starts to really provoke me to just abandon all Jass and went full Lua 😂
It is incredibly powerful! The ways it can create auras alone are very powerful.

For Antares, though, I hope you update this system to allow us to use the values table from conditions as well. I see that the onPeriodic can utilize it, but not the conditions' function. More requests may come as I grow comfortable, hope thats ok! Like
 
I don't think I fully understand your request. The condition function is part of the aura data. It determines whether the buff should be applied to a unit in the first place; that is before the values table is evaluated. Can you show me the type of code you want to be able to write?

More requests may come as I grow comfortable, hope thats ok!
Within reason, of course. :psmile:
 
Level 9
Joined
Oct 20, 2010
Messages
228
Can you show me the type of code you want to be able to write?
I have created the a function 'createAura' to easily create auras of similar structure. There is an issue that the condition cannot properly access values.threshold. You used a local VALUES table directly, and while this works, I feel it would be much more flexible if I could access 'values' from the condition function just as I can from the periodic function


Edit: I also believe the document says .GetBuff instead of .GetData
 
Last edited:
I don't think that is a change I want to make. The values table in the condition would by necessity be one containing the raw values instead of the evaluated ones, like it is the case for all other functions, which will add to the confusion. You can also access the values table in your code example just my reformating your code slightly. However, if you want to make the change yourself, you can do so easily by going to the ApplyAura function and adding source.buffData.values as an argument to the call of the condition function.

Edit: I also believe the document says .GetBuff instead of .GetData
Good catch, thanks!
 
Last edited:
Level 9
Joined
Oct 20, 2010
Messages
228
There seems to be issues with multiple units with the same aura. Either I see the SFX repeatedly create the values table for one of the auras becomes unreadable, or the aura's cancel each other out. I can't say for certain what situations this arises in because I'm not savvy enough. However, you can see one of these issues on your test map by having 2 heroes with vitality aura, just make sure to register the spell for all units instead of just Tyrande.

Also, is it possible to make it so applying a stack doesn't reset the duration of other stacks?
 
I've identified two smaller bugs while investigating this: The effectOnSelf flag was not working and the effects are being recreated when a high level aura overwrites a lower level one. The effects get recreated for normal buffs because otherwise you wouldn't have their effect sound play when they're reapplied to a unit that already has them. But of course, that doesn't make sense for auras, so I changed that behavior here. However, when a unit carrying a higher-level aura leaves range and a unit carrying a lower-level aura is still in range, the aura will first fall off and then reapplied from the lower-level source. I could fix that with some effort, but I think the benefit is quite small.

I couldn't reproduce the issues you're describing. As far as I can tell, two auras behave correctly apart from the issues I just mentioned.

Also, is it possible to make it so applying a stack doesn't reset the duration of other stacks?
Buffs that can stack but each stack gets its own buff slot is something that I want to add at some point

Version 1.1
  • The effectOnSelf flag is now working for CreateAura.
  • Fixed an issue that caused effects to be recreated when an aura is overwritten by a more powerful aura.
  • Added the GetAuraData function.
  • The condition function for auras now passes the aura level as the third argument.
 
Version 1.2
  • New functions: BuffBot.AddStack and BuffBot.SetStacks.
  • Added the individualFallOff flag. If enabled, each stack of a buff that stacks multiple times gets its own expiration timer.
  • Fixed a bug that caused the system to crash when a buff is dispelled before the native buff it is linked to is applied.
 
Buffbot provides an excellent amount of utility, creating and applying buffs with ease. Additionally, it has aura functionality and allows different types of dispel.
The only problem is: where's a slot for a 2nd user so I can test in multiplayer for desyncs? 🤓
"But there's no way it could desync."
Famous last words :plol:


Thanks for the approval!
 
Level 9
Joined
Oct 20, 2010
Messages
228
Buffbot provides an excellent amount of utility, creating and applying buffs with ease. Additionally, it has aura functionality and allows different types of dispel.
The only problem is: where's a slot for a 2nd user so I can test in multiplayer for desyncs? 🤓

High Quality
So deserved. This one resource has entirely changed the way I handle buffs and custom auras in the most elegant way possible.
 
Level 3
Joined
May 15, 2021
Messages
22
Excellent API! Do I need create a group for example to make them invisible for duration? If I cast a spell type Roar? Or your API do everything for me?
 
Excellent API! Do I need create a group for example to make them invisible for duration? If I cast a spell type Roar? Or your API do everything for me?
Thanks. You cannot use BuffBot to apply buffs in the way that Roar does (to all units in the area). You have to loop through all units individually. So, you would have the unit cast a spell, you make a group out of all nearby units and apply the Invisibility buff to each of them.

An API function for applying buffs to multiple units might be a good idea though.
 
Top