Modifiers are an extremely important part of almost any Dota custom game. They allow you to modify certain properties of your hero, deal damage to it over time, or apply various effects on it. Like abilities, we'll also create them in Typescript.
We'll use an easy example which should cover a lot of common concepts for modifiers. This example is Skywrath's Ancient seal, which is an ability that simply applies a modifier to an enemy. The modifier applies the Silenced state on the enemy, and reduces its magic resist property by a percentage.
For simplicity sake, assume the ability has no shard or talents upgrades.
For starters, let's define the ability that applies the modifier.
We'll begin with the KV, which is straightforward. Open
/game/scripts/npc/npc_abilities_custom.txt and copy the following content inside the
ScriptFile denotes it, the lua file should be in
vscripts/abilities/. To do so, we'll create our TS file in
src/vscripts/abilities/, where it would be appropriately routed when compiled. Create the
typescript_skywrath_mage_ancient_seal.ts file and open it.
The ability itself is very straightforward, since all it does is apply a modifier on the target. For simplicity sake, let's decide the modifier will be named
modifier_typescript_ancient_seal. Following is the ability:
Great! This applies the modifier on the target. The caster of the ability, denoted by
this.GetCaster() in the first argument, is assigned to be modifier's associated caster, while the ability itself, denoted by
this in the second argument, is assigned as the modifier's associated ability. We can get those by calling
this.GetAbility(), respectively from the modifier.
The unit we're adding the modifier to, in this case our
target, becomes the parent of the modifier. We can get it from the modifier with
this.GetParent(). This can be useful in various cases, such as when emitting sound from it, dealing damage to it, or placing particles on its current location.
Now let's create the modifier.
This part is absolutely up to you and your organizational preferences: some prefer to add the modifier as a separate file, while some prefer to have the ability and its associated modifiers in the same file. You could place the modifier file inside
src/vscripts/modifiers, for instance. In order to keep the guide simple, let's make the modifier in the same file.
Very similar to an ability in TS, modifiers are also a class. We create a modifier with the following structure:
As you can see, it's very similar to an ability, replacing
@registerModifier(), and the
BaseAbility extension with
@registerModifier() takes care of LinkLuaModifier for you, so you don't need to call it on TS modifiers.
Before we continue, one thing we must do is link the ability to the modifier, which makes sure the modifier is registered. In addition, rather than relying on a string for the naming of the modifier, we'll link the class name.
To do so, simply remove the quotation marks around the modifier name, then add
.name to it. See below the code before and after linking the class:
This results at the exact name of the modifier as a string, which is enforced by Typescript.
If your modifier is in another file, you'll have to import it first before you can link it in the above fashion.
Alright. Let's set and apply the properties for the modifier such as the particle effect. In addition, let's set some useful properties via modifier functions. Also, this is my personal choice, but I usually put ability specials as a class property so they can be easily used everywhere in the modifier.
Okay, so the modifier is defined, but its main parts of it are not yet defined: the silence and the magic resistance reduction. Let's do those next.
CheckState function that modifiers have is called every frame and sets the state of the parent based on its modifiers. The function gets a bunch of states and pairs each of them with a boolean that decides whether the state should be applied.
We only need to silence the target, so that's the only state we require here. Add the following to the modifier:
Note the syntax: the curly braces start a Record of states, each assigned a boolean. If you have multiple states - boolean pairs, separate each pair with a comma.
DeclareFunctions declares which function properties are included in this modifier. Since we need the property that modifies the magical resistance, let's call it here:
Unlike states, DeclareFunctions expects an array of modifier functions. If you have multiple modifier functions, separate them with a comma.
When hovering over a modifier function's name (e.g.
MAGICAL_RESISTANCE_BONUS), a tooltip appears, showing you the name of the linked property function call. Simply copy the function into the modifier. This also has auto complete, if you prefer to do so manually.
Now that we declared the magical resistance bonus, let's return a negative bonus so the enemy get a negative magic resistance bonus from this modifier:
Note that this function expects a number - anything else is not accepted.
this.resist_debuff is supposedly a number that is fetched from the ability special value. However, if for some reason
this.resist_debuff is not initialized, it would be undefined, which is not accepted by this function. Using Nullish Coalescing, the value is defaulted to 0 if
this.resist_debuff is undefined.
That's it! A simple modifier is done with a bunch of simple lines, which are all typechecked for us.
The next part would involve listening to events in game mode and using timers. Stay tuned!