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!
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:
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.
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:
Field
Description
name
The name of the buff displayed in the Buff Bar as well as the name used to reference it throughout your code.
tooltip
The mouse-over tooltip of the buff in the Buff Bar.
icon
The icon of the buff in the Buff Bar.
buff
If the buff should be linked to a native buff, this is its fourCC code.
ability
The 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.
order
The order string of the ability the dummy caster should use to apply the native buff.
duration
The 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.
type
The 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.
color
The color of the buff name in the tooltip. Example: "|cffff0000".
values
A table with string keys that holds custom userdata that can be used in your callback functions.
onApply
A function that is called every time the buff is applied on a unit. It is called with the parameters (whichUnit, source, values, level, stacks).
onFirstApply
Similar 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).
onExpire
A function that is called when the buff expires on a unit. It is called with the parameters (whichUnit, source, values, level, stacks).
onPeriodic
A function that is periodically called while the buff is active on a unit. It is called with the parameters (whichUnit, source, values, level, stacks).
cooldown
Determines the interval at which the onPeriodic function is called.
delay
Determines the delay of the first execution of the onPeriodic function.
isEternal
If this flag is set to true, the buff has no duration and no duration is displayed in the Buff Bar tooltip.
cannotOverwrite
If 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.
effect
The effect path of the special effect that should visualize the buff. You can pass a table containing multiple strings to specify multiple special effects.
attachPoint
The 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.
showLevel
If set to true, the level of the buff is shown in parentheses after the buff name in the tooltip.
maxStacks
An integer, that, if specified, will cause the buff to stack up multiple times if applied repeatedly.
individualFallOff
If 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.
isHidden
If this flag is set, the buff will not appear in the buff bar.
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:
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:
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:
Field
Description
name
The name of the aura. Is copied to the buff, so it does not need to be present in the buffData table.
range
The range of the aura. Can be a number or a function that is called with (source, level).
condition
The condition that must be met for a unit to be affected by the aura. The function is called with the parameters (source, target, level).
effect
The special effect of the aura attached to the aura source.
attachPoint
The attachment point of the aura special effect. Defaults to "origin" if not set.
accuracy
A 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.
effectOnSelf
If this flag is set, the target effect of the buff will also be applied to the aura source.
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
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.
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.
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.
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 ).
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.
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!
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?
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
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.
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.
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?
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?
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?
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.
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.