You are on page 1of 56

https://dev.dota2.com/forumdisplay.php?

f=497

BOT SCRIPTING

Making a DotA2 Bot Using ML


Designing a resource-efficient machine-
learning algorithm

Musashi SchroederFollow
May 30
The bot roster

Problem
In December of 2018, the creators of AI Sports gave a
presentation and introduced the DotA2 AI Competition to the
school. DotA (Defense of the Ancients) is a game played by two
teams, each consisting of five players who can choose from
over one hundred different heroes. The goal of the game is to
destroy the opponents base, while defending your own. Each
hero has access to at least four unique abilities, and are able to
purchase items that also have different abilities. Items are
purchased with gold earned from destroying opponent
structures, or defeating players or creeps, NPC (non-player
character) units that spawn to help defend and attack bases.

The complexity of the game comes from not only the roster of
characters, but the ever changing state of the map. Full
information games such as chess or go leave no information
withheld from players, allowing them to see every possible
action on the board at any given time. The DotA map includes
a “fog of war” which hides any part of the map not seen by a
player or their teammates. Each hero’s abilities also have
“cooldowns” — after a player uses an ability, they cannot use it
again for a set amount of time — and use mana as a resource.
While a player has access to this information about their allies,
they do not have this information about the opponent, and
must take it into consideration when engaging in fights.

The rules for the competition were to program a full team of


five bots to play in Captain’s Mode. Captain’s Mode sets one
member of each team as a captain, giving them the ability to
choose the heroes for the rest of the team, and also “banning”,
or selecting heroes that the opponent’s team cannot use. In
order to avoid having all of our characters banned, we needed
to program at least sixteen of them. Our limitation set by
Maurice,42 Silicon Valley staff, was that we could not use the
built in “desires,” a system to provide default behaviors for the
bot to execute, provided by Valve. Instead of using the default
bot behaviors, we were tasked with writing the code from the
bottom up. The API for DotA2 is written in Lua and allows
players to create their own bots. The competition was
originally designed to use a C++ API written by
Overview of DotA battlefield with labels, image
from https://dota2.gamepedia.com/Map

the AI Sports creators, but due to “complications,” our team


used Lua instead.
How Was it Solved

Learning Lua and the API


In order to create the bot, we first read through the API and
looked for other examples that users had created. The DotA
API was made available in early 2016, though hasn’t received
any meaningful updates since approximately October of 2017.
The first resource we used was a guide on getting started,
written by RuoyuSon. RuoyuSon explained where to find other
resources and how to start games, as well as useful console
commands for the testing process. Valve also provides small
examples of bot scripts in the games directory that can be used
to potentially get started. With the API and other examples, we
naively believed we could create a bot and have a crude,
working version of the code within a week.

The first challenge came in selecting the heroes we wanted to


use and starting the game. What we didn’t know at the time
was that if the code for hero selection has an error, the entire
game will crash without displaying anything. The example
provided by Valve can be used to quickly create hero selection
code for All Pick Mode, but is unusable for Captain’s Mode. In
order to select heroes, we read through other examples of the
code. Although our current iteration of the bot allows for
human players to play against and alongside it, the original
version was only meant to play against another bot in
Captain’s Mode. Finally getting a simple version of the hero
selection working took a little over one week, but since then
has been modified to support All Pick Mode and human
players.

After getting the game to start, we began experimenting with


making heroes walk to locations on the map. We quickly
learned not knowing the Lua language made writing and
understanding other examples of code difficult. While we were
able to make bots walk to certain locations or purchase items,
we frequently made syntax errors and finding bugs in code
took considerable time. After a frustrating two weeks, we took
time to learn the language before engaging with the API again.

While the tournament drew near, we were still figuring out Lua
and fighting to understand the API. Our heroes moved to the
correct locations and they were able to fight enemy minions
and opponents, albeit poorly, but they would never retreat,
resulting in death after death. Even against the easiest default
bot difficulty, Passive, we were unable to win. We implemented
a crude retreat function — simply telling the bots to run back to
their base if they took too much damage — that helped, but left
a lot to be desired. We were able to consistently win against the
Passive bot, but usually ended the game with close to 100
deaths per game on our side, and we were lucky to see two
deaths on the opponents.

The next step, now that the groundwork had been laid for bot
behaviors, was to begin to individualize each bot so that they
could use their abilities. Each bot uses their skills based on
conditions, allowing them to fight the enemy. At this point our
lack of DotA experience began to show — although the bots
were able to use skills, they didn’t use them optimally simply
because we didn’t know what optimal was. We frequently
asked people with more experience for tips and slowly made
the bot stronger. Finally, we were able to defeat the Passive bot
with a positive score. We attempted to beat the Easy difficulty,
but struggled. The difference between the two was significant
and we needed to implement more behaviors in order to win.

State Machine
Up to this point, all code had been written as predefined
actions for each bot to execute. The complexity of DotA
gradually made it more and more difficult to separate what
actions to take and when. Should we fight in this instance, or
run away? When should we focus on hitting creeps? While we
were able to defeat the Passive difficulty consistently, we
realized that Easy would be a significant hurdle. We began to
discuss possible options, and landed on the State Machine.
Example of State Machine

When modifying bot behaviors, it became impossible to


cleanly separate when it would perform actions. They were so
closely intertwined that adjusting one would affect the
performance of the other, and none of the behaviors worked
particularly well. We were also unable to include more
behaviors neatly without disrupting other parts of the code. By
creating the State Machine, we were able to separate each
behavior, using weighted averages to decide which would be
the most optimal in any instance of the game. The code for
each bot is run every frame, allowing for constant calculations
and giving each behavior a value as a weight. Assuming we
programmed the bot well, it could now decide for itself what it
should do based on the game state.

At this point we were able to separate each behavior into its


own distinct code, broken down into components and
conditions. Components are pieces of information that are
always necessary to calculate a behavior, while conditions
would add or subtract from the weight only under specific
circumstances. Separating the code allowed us to make each
behavior perform better — previously, each behavior was
reliant on another, but by using the State Machine, we would
only execute the parts of the code we needed to and only when
we needed to.

While some of us set up the State Machine, we also continued


to improve the non-State Machine version, to the point that we
were able to defeat the Easy difficulty. We were once again
seeing 100 deaths on the scoreboard, but would get more kills
on our side and eke out wins. The code from the non-State
Machine version easily slotted into our new bot, allowing us to
continue working without any significant delays.
One of the benefits of the State Machine was the modularity of
the system. Prior to this, the bot’s generic behaviors were made
up of two files that had necessary comments written
throughout in order to understand which part of the code was
being looked at — the new version had separate files for each
weight and the behaviors were separated so they did not
interact with one another. The modularity allowed multiple
people to work on different parts of the project without
affecting what someone else might be working on, improving
clarity, simplicity, and the team’s workflow.

We were also preparing for our first bot vs bot match against
another team in the competition, but the State Machine was
untested and not ready to implement. This gave us one last
chance to see how the previous version held up. Before we
started our scrimmage, we decided to test and make sure both
teams’ code ran properly. When both bots had a random mix
of the opponent’s heroes and their own, the teams realized we
had made an error in the picking phase. Both teams were able
to fix the issue, but it was another instance of fighting with the
API, something that would persist throughout the entire
process. At this time, we were also notified by Maurice that the
tournament would postponed for a month, giving us a chance
to continue to improve our bots.

During testing against Valve’s proprietary bots, we would


frequently have to restart games because of the compatibility
issues with their bot and Captain’s Mode. We decided to make
our own picking mode for two teams in order to speed up the
process, and cut down on the unnecessary restarts. We gave
our opponent’s bots a random team of five and used this team
for much of our testing. What we didn’t know at the time was
that this would come back to bite us later.
Our team continued to work with the State Machine, adding
more behaviors that we were unable to implement before. As
the behaviors increased, we also started to see improvements
in our matches against Valve’s bot. After defeating Easy, within
24 hours we were able to beat Medium, and the next day we
beat Hard and Unfair back to back. We were ecstatic, not
expecting to beat Unfair much later down the line, but as we
decided to watch the opponent’s bots closer, our jaws dropped.
Two of our opponent’s bots didn’t buy items, and one didn’t
use any abilities. Although we were able to win, still a feat in
and of itself, it wasn’t a real victory against the Unfair bot.

What we didn’t know was that Valve only implemented specific


skill usage and item purchasing on 46 of the bots. We changed
the opponent’s lineup to five of those bots, and while we could
put up a good fight against Hard and win about forty percent
of the time, we rarely won against Unfair. We began to have
more discussions about what we could do to increase our win
rate, resulting in our first roster change. After looking at the
heroes we had implemented, at the time only five, we decided
to switch out heroes that would hopefully fit our overall game
plan better. Immediately we saw an increase and, while we had
become attached to the heroes we chose to use, we began to
consider swapping heroes as an option as we continued to
program.

Data Gathering
We continued to implement more behaviors into the State
Machine, and added more features and, as we did, saw a slow
but steady increase in performance in our matches. In order to
see how well we did when including something new, we had to
watch a full game to observe the specific behavior and to see
whether or not we won the match. All of the bot’s weights were
hand-tuned, and any tweaks we made might not be visible
within a single game. Even sped up, a game would take
between ten and fifteen minutes. In order to gather any
meaningful data, we could spend over hours just watching. To
speed up this process and make sure that any change we added
was meaningful, using Python, the Go programming language ,
and Docker, we began to create a way of gathering data over
hundreds of games.

Maurice gave us access to fifteen computers which we could


use to run games on and gather data. At this point, we had
researched a “headless” mode for DotA; we were able to run
games graphicless which would speed up the games
themselves, and allow us to run multiple instances of the game
without using the GPU. Using Docker, we set up a client to
server connection that allowed us to use virtual machines on
fourteen of those computers. We calculated that we could run
up to four games per computer optimally, so ran four virtual
machines at six times speed. Altogether, we were able to run
games approximately 300 times faster than with originally.

Each game could range between fifteen and eighty minutes.


Docker Swarm distributed the total number of games
requested evenly to all of our worker computers. If we were
running less than 56 games this solution would be fine, but
anything more would be suboptimal. We initially attempted to
deploy using Docker Swarm, but it made more sense for us to
create our own solution. It would need to be customizable,
work well on a distributed network, and have support for
simple concurrency. We decided to use Go because it filled our
criteria and was easy to build and deploy. Finally, Python was
used to graph and illustrate our data results as histograms and
line graphs.
Data showing wins and losses over time

Using this setup, we were able to run 500 games over an hour,
giving us meaningful data. While it was still necessary for us to
watch games to observe and confirm behaviors worked
properly, we could now test them and gather data in order to
confirm whether or not a change was beneficial or detrimental
to the bot.
As we went into the final weeks, we played with the idea of
incorporating a genetic algorithm. The State Machine weights
were all hand tuned and based on our observations.
Specifically our Farm, Hunt, and Retreat weight were so
closely tied together that by changing the values of one, we
would see dramatic differences in the way they played and
their win rates would generally decrease. We knew they were
at a good point, but were sure they weren’t optimal, especially
considering different characters played differently and using
the same weights made them all play more or less the same.
Using a genetic algorithm would use machine learning to tune
each weight, giving us the most ideal numbers to defeat the
default bots, and hopefully our opponents in the tournament.
An ambitious goal was to create different genes for each
character, thereby giving them each their own unique play
style, but we knew that without more time or more computing
power, we would have to make do with the hand-tuned weights
we had.

A week before the competition, we strayed away from adding


major features, only including small changes that our data
decisively proved would increase the win rate. By the end, with
the State Machine we were able to achieve a consistent win
rate above 98% against the Valve bots. Ready for the
competition, Maurice messaged us, informing us that once
again the competition had been extended for another month.

Genetic Algorithm
With the month long extension to the tournament, we began to
discuss how we could create a genetic algorithm. In the end,
we decided to use Go once again because our data gathering
programs had already been written in it, therefore making it
easier to tie the programs together.
Genetic algorithm flowchart, from arwn

In order to get the genetic algorithm to work, we needed to run


multiple iterations of our bot. From those iterations, we would
grab the top five heroes genes and “breed” them by shuffling,
averaging, and splicing them together. The next generation
would be made up of slightly modified versions (using a 10%
mutation probability to choose which genes to change, and
10% mutation rate to change each gene by the respective
amount) which we would then gather data on, repeating the
process until the beginning of the competition. Our plan was to
replace the current hand-tuned genes with our new machine
learned ones.

Our first step was making sure we could run the genetic
algorithm using Go and Docker, and modify the Lua script at
the same time. Each bot’s gene was a Lua file containing the
values we wanted to mutate using the genetic algorithm. We
used Go to read in the gene file, mutated the values, and
output the new gene using a gene template. The new genes
were then used for the subsequent iterations.

Having successfully created a way to read and write to our new


gene files, instead of making one generic genetic algorithm as
we had originally planned, we created genes for each hero we
were using. In order to make it work, each file had to include
the name of the hero we were writing to. Unfortunately we
could only train five heroes at a time, so we opted to train our
starting lineup and use our hand tuned genes for the rest of the
heroes we had implemented.

Finishing the genetic algorithm ended up taking longer than


planned. We hoped to have it running and training within a
week, but needed a few more days to iron out bugs. We had
each made separate parts of the genetic algorithm, and piecing
each together took some time.

Finally, the genetic algorithm worked, but as we began to run


the first generations, we ran into multiple issues. At this point,
we had continued to have some issues with our Docker
containers not running games, but had chosen to ignore it for
the time being because while it had been slower in collecting
data, it wasn’t a significant time difference. If one computer
malfunctioned and dropped off the network the server would
hang, waiting for data to come in from the downed machine.
When we decided to use the genetic algorithm, we needed it to
run non-stop and continue working through each generation.
If a worker failed to respond, the server could never move onto
the next generation because it was waiting for the remaining
games to come in. It made little sense for us to monitor the
computers in shifts all day, so we added in a way of timing out
if we did not get a response from the container after a period of
time.

In the end, after about four days of starting and stopping the
genetic algorithm, we finally had it working. While running the
genetic algorithm and confirming that it worked, we decided to
change our team lineup in favor of one we thought could raise
our win rate. When we began running the genetic algorithm
and set up the genes we wanted to manipulate, as a team, we
went through them and adjusted them to numbers we believed
made sense for the genetic algorithm to start on. At that time,
we decided to manipulate approximately 25 components and
conditions, the “genes,” from our Farm, Hunt, and Retreat
weights. This change combined with a new hero selection we
used for the opposing team dropped our win rate from 98% to
80%. While the genetic algorithm was slowly raising the win
rate, we spoke as a team and decided that if we could boost it
by switching or adding heroes early on, it could be worth
testing. After the switch, the initial 80% rose closer to 90%.

While we observed the bot, we knew that time was beginning


to run out and it wasn’t growing fast enough. Although it was a
risky decision that could result in a potentially drastic decrease
in win rate, we decided to adjust the rate of change from 10%
mutation probability and 10% mutation rate to 15% and 25%
respectively. We calculated that in the most ideal situation, in
order to get rid of a gene that was not useful, it would take at
least thirty generations, or at least one week. We wanted to
reduce that number and figured if we doubled it, we would see
a higher rate of change, for better or for worse. After days of
observing the results, our risk paid off and the bot saw a faster
and more consistent increase in win rate.

Fitness growth over time


Once we were sure of the outcome, we began to add in more
genes to manipulate from other State Machine weights.
Another problem we ran into throughout the project that we
had been unable to solve was how to play early on in the game,
and how to play near the end. In DotA, the play styles between
the two are drastically different. The behaviors that are
important early on are less important as the game goes on for
longer, and vice versa. Our strategy up to this point had been
to trade a slightly weaker start to the game for a more powerful
finish. We had tried to tweak the weights multiple times, but
even if they played better at the beginning, the manipulated
weights would fail in the end dropping the overall win rate.
Now that we had our working genetic algorithm, we added in
various multipliers to health for it to adjust, but also decided to
add in multipliers based on how powerful the hero is. Heroes
go from level 1 to 25 and get stronger as they gain levels. By
hand we were never able to manipulate the weights in an
effective way that would allow for early and late game
adjustments. With the genetic algorithm, we could now leave it
up to the computer to decide when to play differently.

After one more hero change, we settled on our final roster and
continued to let the genetic algorithm work. A few days before
the beginning of the tournament, we saw that the bot had
finally reached a 99% win rate for one generation, but this
dropped the next generation to 96%. While our rapid
manipulation of genes had created a powerful bot, once it got
closer and closer to it’s theoretical peak, the 25% mutation rate
would change to much at once and dropped the win rate. We
decided that in order to preserve our win rate, we would need
to slow down the mutation. The mutation probability was
dropped to 7% and the mutation rate was dropped to 15%.

As we changed our genetic algorithm once again, we decided to


take another risk. Up to this point we had been taking the top
five genes from each hero as parents, breeding them and using
the offspring for the next generation. While this had worked
for us, classically it was not how we should have been using a
genetic algorithm. In a genetic algorithm, all genes should
have a chance of being picked, but we were actively selecting
which to use. The importance of using the lower win rate bots
is diversity. While in the generation it may not have performed
as well, in a future generation its genes may be an important
part towards increasing the win rate. In order to make sure
those lower win rate genes had a chance at being selected, we
mapped all of the genes, allowing a higher probability of being
picked to the higher win rates, while still giving the lower win
rates a chance, albeit smaller.

We also discussed a change in strategy. Taking the genes from


each individual bot was necessary, but we considered the
importance of taking all of the genes from one “team” of bots.
A bot at first glance may have looked like it had less potential,
but as part of a team it’s genes could have been an important
key to victory. We thought about the benefits of switching from
the individual bots to only breeding teams, but couldn’t justify
losing out on more powerful heroes genes. As we came to the
conclusion that we should continue using the same strategy of
selecting the individual bots, a thought popped into our heads.
What if we do both? Taking the individual genes was
undeniably important, but by breeding bots from the same
team with the stronger individual bots, we believed we could
unlock the potential of both worlds.

Conclusion
The genetic algorithm seems to have improved the bot,
although its play style is considerably different from the
original non-genetic version. While we were much more
aggressive earlier, we now play more conservatively and aim to
win mostly by destroying structures and winning the
occasional team fights. The older bot would group together
more often as a team, forcing opponent bots to react and
resulting in more fights. If we continued to work on the
project, I believe the next step would be having the bot begin to
fight itself and the older, non-genetic version of the bot.

Through this project, I’ve been able to learn multiple


programming languages, as well as familiarize myself with
Docker, and the importance of documentation when working
as a team. The reason I decided to work on this project was less
an interest in DotA2, but more trying to understand machine
learning. I’d heard the term multiple times, had read about it,
but didn’t have a real understanding of what it entailed and
how to actually program it. Participating on this project gave
me a unique opportunity to work with machine learning, and
has increased my understanding of programming as a whole.

Getting Started With Dota 2 Modding


So you're completely new to Dota 2 modding? Don't know where in the hell to begin? This is
the guide for you, the future Dota 2 modder!
Note: “Addon”, "mod", and “custom game” are all synonymous throughout this guide (and likely
the entire website).

The Facets of Dota 2 Modding


The Workshop Tools Wiki Homepage does a good job with subdividing all the possible aspects
of Dota 2 modding:

 Level design (Uses the tool called "Hammer")


 Scripting (Divided into KeyValue editing and Lua scripting)
 Modeling (Importing your own custom models into your addon)
 Sounds (Importing your own custom sounds, or editing existing ones)
 Particles (Editing existing particles or creating your own using the Particle Editor Tool
(PET))
 Custom UI (Creating Panorama scripts to extend or modify the existing Dota 2 UI)

Step #0: Installing the Dota 2


Workshop Tools
You can't mod Dota without the Workshop Tools!
taken from How to install the Dota 2 Workshop Tools:

 Right-click on Dota 2 in Steam and select View Downloadable Content.


 Check the box in the Install column next to Dota 2 Workshop Tools DLC.
 Click Close. The required content will begin downloading.

Step #1: Creating a New Addon


From The 'Barebones' Template
To start off on a good foot, you’re going to want to create a new addon based off of the
Barebones template, which is a community made alternative to Valve’s default addon
templates (i.e. Holdout). This is the link to the updated BMD
Barebones: https://github.com/bmddota/barebones
After downloading it as a zip, you want to browse to your .../Steam/SteamApps/dota 2
beta/ and merge the gameand content folders from the .zip into the that /dota 2 beta/ folder
(which should already have folders in it called gameand content)
~Alternatively, you can use Dota 2 ModKit and go to File > New Addon > BMD's Barebones.~
Next, start up the Workshop Tools (or restart them if you have them opened already), and
double click your new addon. Set it as the default addon. Then, go into Hammer -> File ->
Open -> barebones.vmap -> Press F9 to begin building the map. After Hammer finishes
building your map, your custom game will automatically load in Dota.
Gfy Demo of Step #1. NOTE: Workshop tools now are launched through the same link in
steam as the main dota client, and not the "Tools" list in steam. Otherwise this image is roughly
still accurate.

Step #2: Creating your map in


Hammer

(Credits to DarkMio for the gfy.)


Hammer is the tool you use to create worlds for your custom game. I highly recommend you
start off creating something in Hammer first instead of diving straight into the scripting or
another facet. You can have the most sophisticated scripting in the workshop, but how are
people going to enjoy your game if there isn't a world they can play in?
Once you get to the point of having a rough layout blocked out for your map, it's probably safe
to move on to scripting. You don't want to spend too much time piddling with detailing on
something you realize needs changing once you get into the nitty gritty of your mode.
The wiki page on Hammer does a good job with giving you a run-down of Hammer. I'd
recommend you start with the Tile editor section.
BMD has made some rather nice beginner Hammer tutorial videos:

 part 1: Tile Editor


 part 2: Mesh Basics
 part 3: entity basics

Step #3: Scripting and beyond...


Scripting is the next most important part of your addon. It is divided into Lua scripting, and
KeyValue scripting. I'm going to go ahead and redirect you to Noya's Beginner Scripting Guide,
since it has essentially the same information that would go in this section.
Now I'm going keep this short and sweet. I've already presented a ton of information for you to
begin delving yourself into Dota 2 modding! Becoming good at Hammer mapping and good at
Lua and KeyValue scripting will go a very long way in making successful, fun Dota 2 custom
games. Please don't hesitate to ask questions in the #dota2modhelpdesk IRC channel or in
the Questions subforum.

Part 2 of Getting Started With Dota 2 Modding, this tutorial is meant to explain the basics of
programming Dota 2 custom mods.

Scripting
So now you have your freshly created gamemode running and have played around the map
editor a bit, it’s time to move into the programming realm of Dota 2 custom maps.
Go into your /scripts/ folder. The 2 main script folders are npc and vscripts. The first holds the
following .txt files:

 npc_abilities_custom.txt - Contains all the custom abilities of the


gamemode.
 npc_heroes_custom.txt - Heroes with its abilities and stats
 npc_items_custom.txt - Items are abilities that go into a units inventory
 npc_units_custom.txt - All the data for non-hero units like buildings or
creatures.
 npc_abilities_override.txt - Modified dota abilities/items with changed
values.
 herolist.txt - List of the heroes available for picking.
These files are defined using KeyValues (KV) and are the core of the the DataDriven system.
While they fulfill the definition of a programming language, it’s more like a big table containing
all the possible data in a static document. it uses a relatively simple syntax whose only special
characters are curly braces and quotes, with alternating sets of "Key" and "Value" or "Key"
{table} pairs, where table is another set of KeyValues.
KV will define the structure of abilities/items/units, while more elaborate behavior is handled
with Lua.
Each .txt file contains its particular KVs, and when the game starts, each client (and server) will
interpret them. Changes to these files won’t take effect until the game is started again, so be
very aware of the syntax, as any extra/missing "or { } will usually make all the keyvalues that
come after this error unusable. Consistent indentation is a good practice to learn early! KV is
case-sensitive, so also pay attention to write everything like the game expects you to write.
Now it’s a good time to get your environment ready to write Dota Scripts. For this, the best way
is getting Sublime Text Editor, with these 2 snippet plugins that add completions for some
commonly used functions and proper syntax coloring for KV and Lua.

 Sublime Text 3
 Sublime Text KeyValues Package
 Sublime Text Lua Package

This will be just an introductory example to the datadriven system, to understand what goes
where and how to expand it.
Start a new document in Sublime and make sure you are using Dota KV as the Syntax (press
Ctrl+Shift+P and write down Dota KV to select it quickly).
We’ll be making a very simple ability that does single target damage. Start by writing the name
of the ability between "" and no spaces. Then write BaseClass... and press Enter to insert the
completion. Move through the different fields with Tab.
http://puu.sh/g1Aks/252bb32b2d.png
A "BaseClass" is essential to every datadriven definition, it orders the game to interpret this
ability/item/unit in a certain way- in this case as a datadriven ability. Stock Dota 2 Items, units
and heroes have their own base classes which have "hard-coded" behavior that we as
modders can't change much.
AbilityTextureName can be a custom icon or any internal name of a dota ability, for example
lina_laguna_blade.
Other essential KV is the AbilityBehavior, write down AbilityB and use the autocomplete
http://puu.sh/g1AtQ/cda16f7138.png
http://puu.sh/g1Avn/65fe86524c.png
Then we need an ability event, this is a trigger for when certain event happens to the owner of
the ability. The most basic one is OnSpellStart, add one with the completions and you’ll see a
new "level" within { } is created, this is known as a block. In [ACTIONS], write down a
"Damage" action, some keys and a %AbilityDamage will appear. A % represents a value to be
taken from somewhere else, in this case, an AbilityDamage KV. Add this last key and this first
basic spell should be like this:

"test_ability"
{
"BaseClass" "ability_datadriven"
"AbilityTextureName" "lina_laguna_blade"
"MaxLevel" "1"

"AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET"
"AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY"
"AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC"
"AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL"

“AbilityDamage” "500"

"OnSpellStart"
{
"Damage"
{
"Target" "TARGET"
"Type" "DAMAGE_TYPE_MAGICAL"
"Damage" "%AbilityDamage"
}
}
}

Now, this ability has to be added to the npc_abilities_custom.txt file for a hero or unit to be able
to use it. To do this, you can either edit the file directly, or use modkit.
If editing the file directly, take extra care at the level of brackets you're using. (Sublime Text
protip: use Ctrl + [ or ] to move selected blocks of text left and right through layers of tabs)
Alternatively using ModKit you can choose to "break" this file in separate files and folders
(abilities/items/units/heroes), using Combine KV it will merge the contents of the folders on to
the npc custom files. Working with many small key value files is easier to maintain and debug
in case of errors. Beware that if using modkit to combine KV files in this way, you should only
edit the broken-up versions, as manual edits made to the final .txts themselves will be lost the
next time you combine.
After following this process for the test_ability you just created, it’s time to add the ability to a
hero. Open npc_heroes_custom.txt and change the "Ability1" value "example_ability" to
"test_ability" (the ability we just made), save and it’s ready to be tested ingame.
Whenever you need a testing dummy, you can create one by writing
-createhero (unit_name) enemy

in chat, unit_name being one of the available hero names pickable or just any unit name
available. It also accepts shortened names, like “ancient” instead of “ancient_apparition”. One
quick command is -createhero kobold enemywhich makes a default enemy neutral kobold.
The full unit name is “npc_dota_neutral_kobold”, but the shorter command will do. You can
also enable no-cooldown mode by writing -wtf (and -unwtf will disable it).
Extensive documentation and in-depth examples of the datadriven system can be found in the
following links spread over various moddota tutorials.

Datadriven Ability Breakdown


Datadriven Items
Datadriven Units

Lua Scripting
Going back to the game/scripts folder, there’s the vscripts folder. Here is the place where all
the Lua scripts are placed. Lua is fairly easy to pick up and its syntax is very straightforward.
For the most part, programming in Dota Lua is just knowing which API functions to use (more
on this later).
There's 4 applications for Lua in Dota:

 Game Logic
 DataDriven RunScript
 Hammer I/O
 Custom UI Events

Game Logic - Barebones Structure


To understand the core structure of the Dota Lua environment, I’ll be explaining the contents of
a simplified Barebones. Get one of these from this repository, and head to the vscripts folder.
In every single gamemode, a file named addon_game_mode.lua must be present. While it is
possible to add the game logic to this file (and in fact, valve did so in their holdout example), it
is recommended that you reserve this file only for this 3 functions:

 Require, here we put all the necessary files that will be used by the game
logic, treated as libraries, meaning all the functions inside those files can be
used at any point.
 Precache, when the game starts and players pick their heroes, the engine will
try to load the associated models/particles/sounds to those heroes. If we’re
dynamically using a resource in Lua before preloading it won’t be displayed
properly.
 Activate, creates the base game mode entity and calls the initialize function.

http://puu.sh/g2pUC/ca4413cc48.png
Precache function was folded in sublime
Using our barebones, you don’t need to touch this file apart from very specific situations, and
all the core game logic will be coded in barebones.lua, which has been already required. We’ll
call this your main lua file from now on.
Note: For the more advanced starting template you should use BMD’s Barebones, this basic
Barebones was specially designed for explaining the essential parts of the Dota Lua structure.
After addon_game_mode Precache & Activate are finished, the first function to be executed in
the barebones.lua file is GameMode:InitGameMode().
In here the game starts by initializing all sorts of rules and functions, which are registered over
the GameRules and GameMode entities. For this, many variables are defined on top of the file
to help organizing options like gold settings, kills, custom levels, etc.
This is the syntax of a function applied over GameRules, with one bool parameter:
GameRules:SetHeroRespawnEnabled( ENABLE_HERO_RESPAWN )

Just as KV, Lua is Case Sensitive. Also the placement of the functions within your main Lua file
doesn’t generally matter. All the script lines within a function call will be run one after another,
potentially on the same frame; one frame in Dota is 1/30 of a second.
Note the use of : colon before the function. In Lua, this is how we access the various Game
API functions. We say that GameRules is an HScript or a handle. Handles are basically huge
tables, with all the pertinent info of the entity. Over the Scripting API page you’ll see many
different types of functions which can use different handles
Global functions don’t need any handle : prefix. Heroes, Creatures, Abilities and Items all have
their different handle classes and attempting to call a function over an incompatible class will
cause a VScript error, as pink text in console and red text on the gamescreen.

The Console
You can access the game console by pressing the ` key.
This will provide tons of useful information for debugging. The different colors represent the
various “channels” of information. By default all the channels are in the same Log: Default tab.
It’s very recommended that you make your own tabs to split the log viewer.
For Lua Scripting, we want to have a VScript Tab. Messages about the DataDriven system are
in the General channel in yellow along with some other info, make a separate viewer for this
too.

The new tabs:


The console will notify whenever a Lua scripting error happens, either when the game is being
loaded (a syntax-compilation error) or at runtime. In this error, I wrote
GameRules.SetHeroRespawnEnabled with . instead of :

You can then trace the error to that line and attempt to solve it, writing script_reload in the
console to reload the script and check if it was actually fixed.
A DataDriven syntax error will usually look like this:

Engine Events
The second segment of the InitGameMode function is the Listeners:
ListenToGameEvent('dota_player_gained_level', Dynamic_Wrap(GameMode,
'OnPlayerLevelUp'), self)

The structure of this ListenToGameEvent is read as:


Whenever the dota_player_gained_level event is triggered, execute the scripts inside
the OnPlayerLevelUp function.
OnPlayerLevelUp and GameMode are just the names of the function and main class name,
normally you don’t need to worry about them, all Listeners and functions are already available
in barebones, ready to be expanded. Dynamic_Wrapis a function to ensure that
the script_reload command also reloads the listeners. script_reload restarts lua scripts at
runtime, unlike DataDriven files which require the game to be fully restarted. As you can see on
the barebones example there are tons of possible events, and not all of them are listed there,
those are just the most used ones.
The 3rd and last main element of InitGameMode are self defined variables to track info. These
use the self. entity, which is a local reference to the GameMode entity, seen through all the
functions inside the main lua file. Adding information to an entity like entity. is loosely called
“indexing” and is basically adding another entry to the big table of that entity. This is very useful
because this information is stored under the entity handle visible everywhere, and won’t
change until we reassign it or destroy it.
Enough theory, let’s see how this all comes together. We’ll add some simple script lines to the
OnNPCSpawned function, which is the listener for npc_spawned and triggers every time a unit
or hero entity is added to the map.
Let’s analyze the contents of the OnNPCSpawned default function:

-- An NPC has spawned somewhere in game. This includes heroes


function GameMode:OnNPCSpawned(keys)
print("[BAREBONES] NPC Spawned")
DeepPrintTable(keys)
local npc = EntIndexToHScript(keys.entindex)

if npc:IsRealHero() and npc.bFirstSpawned == nil then


npc.bFirstSpawned = true
GameMode:OnHeroInGame(npc)
end
end

First line will print the string under "" in the VConsole. The print function is native to Lua, and
accepts multiple parameters separated by commas, and concatenation of strings with ".." like
this:
print("[BAREBONES]".."NPC","Spawned")

DeepPrintTable is a Global Valve-made function which will display the information of the table
passed. For keys in this case, it will be the .entindex and .splitscreenplayer. The entity
index is a very important number to reference the entity. Ignore splitscreenplayer, it’s just
legacy source stuff and never used in Dota 2.

The next line defines a local variable. In Lua local variables have their scope limited to the
block where they are declared. It is good programming style to use local variables whenever
possible. Local variables help you avoid cluttering the global environment with unnecessary
names. Moreover, access to local variables is faster than to global ones.
local npc = EntIndexToHScript(keys.entindex)

This is basically reading the information that is provided by the event, and storing it into a local
variable within that function call. In this example all the Listener and their functions have
already been processed, but for reference you can always check the Built-In_Engine_Events
wiki page to know exactly what parameters are carried by each event.
The npc local variable is an HScript, of handle type. All changes done into the npc variable will
reflect on spawned unit.
The next line is a conditional, first it checks if the npc is a real hero (this excludes illusions) and
it also checks if the .bFirstSpawned index (a self-defined variable) has not been assigned yet.
If both conditions are true, changes the boolean value to true and calls the OnHeroInGame
function.
To finish this basic Dota Lua tutorial, let’s modify the OnNPCSpawned function so that if a unit
named npc_dota_neutral_kobold is spawned, wait 1 seconds and then kill itself. Added to the
first if statement there’s this else-if condition:
function GameMode:OnNPCSpawned(keys)
local npc = EntIndexToHScript(keys.entindex)

if npc:IsRealHero() and npc.bFirstSpawned == nil then


npc.bFirstSpawned = true
GameMode:OnHeroInGame(npc)
elseif npc:GetUnitName() == "npc_dota_neutral_kobold" then
Timers:CreateTimer( 1.0 , function()
npc:ForceKill(true)
end)
end
end

Here we make use of the Timers library for a simple 1.0 second delay, there are many different
timer functions included and explained in timers.lua. The bool on ForceKill is to enable the
death animation.

Tables.
Tables are the most important structure we will have to use. As mentioned before, all the info
on entities can be seen as a table (even though it's technically a pointer to a C++ object), and
you Get and Set the values through the various Game API functions.
There are some functions in the API that return a table of entity handles.
Let say you want to find all the units near the spawned kobold unit and kill them. The
function FindUnitsInRadius can be used for this purpose, and takes a lot of parameters with
different types which is worth explaining:
table FindUnitsInRadius(int teamNumber, Vector position, handle cacheUnit, float
radius, int teamFilter, int typeFilter, int flagFilter, int order, bool
canGrowCache)

The parameters Have to be in this order. This function is a global, so no handle: needed, but
we need to keep the table under a variable, like this:
local units = FindUnitsInRadius(...)

For the teamNumber, finding out which team an entity is in can be done
with GetTeamNumber() on the npc handle. As for the other Filter parameters, instead of real
integers, we use a bunch of Constants that represent different number values. The complete
list of Constants is found on this wiki page.
A Vector is represented as Vector(x,y,z) coordinates. The function to get the position of
particular unit is called GetAbsOrigin and takes a npc handle.
As for the cache parameters, just leave it nil and false, they aren't of much use generally.
The complete function call to get the heroes in 500 radius from the spawned kobold would be:

local units = FindUnitsInRadius( npc:GetTeamNumber(), npc:GetAbsOrigin(), nil, 5


00,
DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_H
ERO,
DOTA_UNIT_TARGET_FLAG_NONE, FIND_ANY_ORDER, false)

The use of extra break lines is just to make it more readable. Now we want to iterate over the
entities of this table, which is done like this:

for key,unit in pairs(units) do


print(key,value)
unit:ForceKill(true)
end

The key,unit are the chosen names to refer to the position and value inside the units table,
which will be read in pairs. Using '_' as the name of the key is a good convention when you
want to make it clear that the first parameter wont be used. The 2nd parameter, unit, is used to
itea handles of the units found.
There is one more thing to consider: the "wait one frame" issue. Because all units are actually
spawned at the (0,0,0) coordinates and then moved to the desired position, many times you'll
need to create a 0.03 second timer (1 frame) for some scripts to work, and this is one of those
cases.
So, OnNPCSpawned is looking like this:

function GameMode:OnNPCSpawned(keys)
local npc = EntIndexToHScript(keys.entindex)

if npc:IsRealHero() and npc.bFirstSpawned == nil then


npc.bFirstSpawned = true
GameMode:OnHeroInGame(npc)
elseif npc:GetUnitName() == "npc_dota_neutral_kobold" then
Timers:CreateTimer(0.03, function()
local units = FindUnitsInRadius( npc:GetTeamNumber(), npc:GetAbsOrig
in(), nil, 500,
DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO,
DOTA_UNIT_TARGET_FLAG_NONE, FIND_ANY_ORDER, false)
for key,value in pairs(units) do
print(key,value)
value:ForceKill(true)
end
end)
end
end

And the result ingame:

DataDriven RunScript
The 2nd application for Lua in Dota 2 Modding is the "RunScript" Action, which can be called
from any DataDriven Event to connect the ability or item with with a Lua Script File
RunScript creates a new instance of the lua file, so.globals don’t really apply here, all variables
should be either local and/or assigned to a handle.
Splitting the lua files for each ability is a good idea as it also helps splitting the ability scripts
just like with the txt files
Let’s go back to the first super simple single target damage datadriven ability and add this
block to the OnSpellStart ability event:
The Syntax is like this:

ScriptFile route is relative to the /vscripts/ folder. AbilityName is the name of the lua function
inside that file.
Let's go back to the super basic datadriven ability and add a RunScript block for a ScriptFile in
a subfolder. Unlike the general Game Logic, it's recommended to separate the lua files for
each ability script, and in different folders to separate heroes, units, items, etc. At the end,
organization is up to you.
Adding this block to a DD Event like OnSpellStart will make a new instance of the
example_script and execute the lines defined in function ScriptedAbility.

"RunScript"
{
"ScriptFile" "heroes/example_script.lua"
"Function" "ScriptedAbility"
}

In your vscripts folder, make a heroes folder and a example_script file with a .lua extension. I
recommend setting these files to automatically open with Sublime, and set the syntax to Dota
Lua.
In this example script, the event will pass some information to the first parameter of the
functions, which can have any name but you'll see most refer to this parameter
as keys or event.

In the body, most ability scripts start by defining the local variables for the target entities which
are passed by the event. This is explained more deeply in the guide All About The Target, but
the basic target variables visible on any script are

 .caster, the entity that started the ability.


 .target, the target of the ability (can be same as the caster in some cases)

Example

function ScriptedAbility( event )


local caster = event.caster
local target = event.target

if target:GetHealthPercent() < 50 then


target:Kill(nil, caster) -- Kills this NPC, with the params Ability and
Attacker
end
end

This will kill the targeted unit if its Health percent is less than half, and credits the kill to the
caster entity.

Scripting Examples and Sources


There are plenty of examples spread all across GitHub and with the contents of this guide you
should now be able to understand the scripting flow of game logic and scripted abilities. The
best GitHub repo to look for ability scripts is SpellLibrary, a community project to rewrite every
dota ability using KV and Lua.
If you want to check the scripts of a certain game on the Custom Games Workshop which
hasn't made their source public on GitHub (because they are fools), just follow these steps:

1. Subscribe to the game. Download GCFScape if you haven't done so yet, it


can be found in moddota's tools list on the nav-bar
2. Check the URL, steamcommunity.com/sharedfiles/filedetails/?id=copy this
number
3. Go to your Steam folder -> SteamApps -> workshop -> content -> 570 (this is
the dota folder)
4. Search for the copied number folder
5. Open the .vpk file with GCFScape and extract its contents anywhere you
want. Now you can access its scripts and compiled models/particles/sounds.
Whenever you have a doubt about how to use a particular GameAPI function, its possible to
find examples all over GitHub by just writing the name of it, additionally filtering by lua like this:
http://puu.sh/g2yTG/93f1641866.png
Just make sure it's actually Dota Lua and not another game API, as some of the functions
might share names with other engines.
That's all for the Scripting basics. I expect you to have more questions than when you started
reading, feel free to drop all your doubts at the community's #dota2modhelpdesk
IRC @GameSurge, you'll find help there 24/7. I recommend getting [HexChat IRC client] for
this purpose, but it's also possible to access from a web browser using the Chat Tab on the
nav-bar.

The concept of Modding Community doesn't go well together with Competitive Business
My Project Page || My GitHub Profile ||

 Myll

February 2015 Posts: 184

I regularly update my util.lua file, which contains basically all of the utility
functions that I've used for my Dota mods.
Also, small guide I wrote on Tables:
Example of nested tables:

Groceries =
{
[1] = {“apple”, “bananas”, “grapes”},
[2] = {“bread”, “corn”, “butter”},
[3] = {“ice cream”, “milk”, “jelly”}
}

You can iterate over a table two different ways:

for k,v in pairs(someTable) do


print(“Key: ” .. k .. “, Value: " .. tostring(v))
end

for i,v in ipairs(someTable) do


print(“Index: ” .. i .. “, Value: ” .. tostring(v))
end
Useful Table Functions:

-- Given element and list, returns true if element is in the list


.
function TableContains( list, element )
if list == nil then return false end
for k,v in pairs(list) do
if k == element then
return true
end
end
return false
end

-- Given element and list, returns the position of the element in


the list.
-- Returns -1 if element was not found, or if list is nil.
function GetIndex(list, element)
if list == nil then return -1 end
for i=1,#list do
if list[i] == element then
return i
end
end
return -1
end

function TableLength( t )
if t == nil or t == {} then
return 0
end
local len = 0
for k,v in pairs(t) do
len = len + 1
end
return len
end

An important thing to note is when you do A = someTable, A simply stores a


bunch of pointers to locations in someTable (i.e. A is a reference to
someTable). To create a new table that stores the keys and first level
children of someTable, you can do this:

-- Returns a shallow copy of the passed table.


function shallowcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in pairs(orig) do
copy[orig_key] = orig_value
end
else -- number, string, boolean, etc
copy = orig
end
return copy
end

To create a full copy of the table (i.e. deeper than first level children), you
would need a deepcopy function.
Basic data structures such as stacks and queues can be implemented with
tables:
Stack:

table.insert(A, 1, “Mary”)
table.insert(A, 1, “Joe”)
table.insert(A, 1, “Steve”)
-- table looks like this {[1] = “Steve”, [2] = “Joe”, [3] = “Mary
}
table.remove(A, 1) -- Removes “Steve”

Queue:

table.insert(A, “Mary”)
table.insert(A, “Joe”)
table.insert(A, “Steve”)
-- table looks like this {[1] = “Mary”, [2] = “Joe”, [3] = “Steve
}
table.remove(A, 1) -- Removes “Mary”
Treat everyday as if you are a student, not a master. The student learns, grows and sees
beauty. The master becomes bitter, resentful, and stagnates.

 Noya

March 2015 edited March 2015 Posts: 1,670

Passing parameters to a Lua Script with


DataDriven RunScript
There's 2 ways for sending values from DataDriven KeyValues to Lua:

1. Passing Ability Values into Lua, fully covered in this short old thread
2. Adding a new KeyValue line inside the RunScript block

Method 2 is very interesting because it's the only way of passing a String
value (AbilitySpecial only takes "FIELD_INTEGER" and "FIELD_FLOAT" for
some dumb reason).
Example:

"RunScript"
{
"ScriptFile" "path_to_file.lua"
"Function" "Ability"
"SomeValue" "Function1"
}

In Lua, the value can be accessed with .SomeValue like this:

function Ability( event )


local value = event.SomeValue
if value == "Function1" then
print(value)
else
print("No value")
end
end

This is specially useful for functions you want to use more than once but with
a different behavior, for example you can create a very quick method that
does "Learn and level up this ability to its max level on this hero" like this:
KV

"RunScript"
{
"ScriptFile" "example.lua"
"Function" "AddMaxLevelAbility"
"AbilityName" "ability_example"
}

Lua

function AddMaxLevelAbility( event )


local caster = event.caster
local ability_name = event.AbilityName

-- Add the ability


caster:AddAbility(ability_name)

-- Get the handle and level it up if possible


local ability = caster:FindAbilityByName(ability_name)
if ability then
local MaxLevel = ability:GetMaxLevel()
ability:SetLevel(MaxLevel )
print("Ability "..ability:GetAbilityName().. " Level "..M
axLevel)
end
end

This AddMaxLevelAbility example can now be reutilized with any


"AbilityName" "ability_example" Key-Value pair.
Note that I added a conditional to check the ability local, this is just in case
the FindAbilityByName returns nil (because the AddAbility failed to find an
ability with the ability_name argument).
´..´ is used to concatenate strings and is very useful for debugging what went
wrong, printing all variables and checking if something isn't what we
expected.

The concept of Modding Community doesn't go well together with Competitive Business
My Project Page || My GitHub Profile ||

 Tigago

September 2017 edited September 2017 Posts: 8

Hi, Thanks for the tutorial.


Can you help me? I am new to the community and to modding as well and
I'm having trouble with overriding abilities for heroes. I created the ability just
like you did in the beginning of the tutorial, went to "npc_heroes_custom.txt"
and changed the Ability1 field to the name of my ability, but when I run the
map on dota and pick the hero (TA in this case) it seems like the ability was
overidden with nothing, the first ability is gone! I compared my files with the
template addon to see if I have made any mistakes but couldn't spot
anything. And when I switch back the Ability1 field to the template ability it
had before, it doesn't show as well. PS: I used modkit to break the KV, don't
know if that is relevant. My txt files and the issue in-game:

"DOTAAbilities"
{
"Version" "1"

"habilidade_exemplo"
{
"BaseClass" "ability_datadriven"
"AbilityTextureName" "lina_laguna_blade"
"MaxLevel" "1"
"AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UN
IT_TARGET"
"AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_EN
EMY"
"AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO |
DOTA_UNIT_TARGET_BASIC"
"AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL"

// Casting
//-------------------------------------------------------
------------------------------------------------------
"AbilityCastPoint" "0.0"

// Time
//-------------------------------------------------------
------------------------------------------------------
"AbilityCooldown" "17.0"

// Cost
//-------------------------------------------------------
------------------------------------------------------
"AbilityManaCost" "100"

"AbilityDamage" "500"
"OnSpellStart"
{
""Damage"
{
"Target" "TARGET"
"Type" "DAMAGE_TYPE_MAGICAL"
"Damage" "%AbilityDamage"
}"
}
}
//===========================================================
======================================================
// Templar Assassin: Refraction Holdout
//===========================================================
======================================================
"templar_assassin_refraction_holdout"
{
// General
//-------------------------------------------------------
------------------------------------------------------
"AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO
_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE"
"AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL"

// Casting
//-------------------------------------------------------
------------------------------------------------------
"AbilityCastPoint" "0.0 0.0 0.0 0.0"

// Time
//-------------------------------------------------------
------------------------------------------------------
"AbilityCooldown" "17.0 17.0 17.0 17.0"

// Cost
//-------------------------------------------------------
------------------------------------------------------
"AbilityManaCost" "100"

// Special
//-------------------------------------------------------
------------------------------------------------------
"AbilitySpecial"
{
"01"
{
"var_type" "FIELD_INTEGER"
"damage_absorb" "200 300 400 500"
}
"02"
{
"var_type" "FIELD_INTEGER"
"bonus_damage" "20 40 60 80"
}
"04"
{
"var_type" "FIELD_FLOAT"
"duration" "17.0 17.0 17.0 17.0"
}
}
}
}
"DOTAHeroes"
{
//===========================================================
======================================================
// HERO: Templar Assassin
//=======================================================
==========================================================
"npc_dota_hero_templar_assassin_template"
{
"override_hero" "npc_dota_hero_templar_as
sassin" // Hero to override
"Ability1" "habilidade_exemplo"
// Ability 1
"VisionNighttimeRange" "1800"
// Range of vision at night time.
}
}
 DragonBlade

September 2017 Posts: 9

Your problem is the aditional quotes inside OnSpellStart: one before


"Damage" and the second one after the cloasing bracket }

"OnSpellStart"
{
""Damage"
{
"Target" "TARGET"
"Type" "DAMAGE_TYPE_MAGICAL"
"Damage" "%AbilityDamage"
}"
}

 Tigago

September 2017 edited September 2017 Posts: 8

OMG why am I so dumb? Thanks DragonBlade it worked just fine


 LORDEUS

October 2017 edited October 2017 Posts: 2

Hi guys, Please help me, i'am newbee and now trying to make my first map.
I have a few problems: 1. I didn't find a maelstrom or mjollnir text files on
github.com, so i took maestrom text code from Dota 2 folder using
GCFScape, but the code seems old I quess, maybe that's the reason why
item does't give any stats. That's the code - it's the original text, I just added:
"item_maelstrom1_datadriven" name, "BaseClass" "item_datadriven" "ID"
"2004"
and attack range, but anyways nothing works, but I can see the item in shop:
//=========================================================
======================================================== //
Maelstrom
//=========================================================
========================================================
"item_maelstrom1_datadriven" { // General //-------------------------------------------
------------------------------------------------------------------ "BaseClass"
"item_datadriven" "ID" "2004"
"AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE"
"AbilityTextureName" "Ms1"

// Item Info
//-----------------------------------------------------------
--------------------------------------------------
"ItemCost" "100"
"ItemShopTags" "damage;attack_speed;unique"
"ItemQuality" "artifact"
"ItemAliases" "maelstrom"
"ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMAT
ES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATOR
S"

// Special
//-----------------------------------------------------------
--------------------------------------------------
"AbilitySpecial"
{
"01"
{
"var_type" "FIELD_INTEGER"
"bonus_damage" "60"
}
"02"
{
"var_type" "FIELD_INTEGER"
"base_attack_range" "200"
}
"03"
{
"var_type" "FIELD_INTEGER"
"chain_chance" "20"
}
"04"
{
"var_type" "FIELD_INTEGER"
"chain_damage" "200"
}
"05"
{
"var_type" "FIELD_INTEGER"
"chain_strikes" "4"
}
"06"
{
"var_type" "FIELD_INTEGER"
"chain_radius" "900"
}
"07"
{
"var_type" "FIELD_FLOAT"
"chain_delay" "0.25"
}
"08"
{
"var_type" "FIELD_FLOAT"
"chain_cooldown" "0.2"
}
}
}

Second Problem is - My Drow' rangers ultimate (Marksmanship) gives me


agility bonus at lvl 6, but doesn't give at 12 and 18 lvls. I didn't even touch
text and lua file, everything was taken from github.com, but still doesn't work
properly :(
that's text and lua files: "marksmanship_datadriven" { // General //---------------
----------------------------------------------------------------------------------------------
"BaseClass" "ability_datadriven" "AbilityBehavior"
"DOTA_ABILITY_BEHAVIOR_PASSIVE" "AbilityType"
"DOTA_ABILITY_TYPE_ULTIMATE" "AbilityTextureName" "drow_4"

// Special
//-----------------------------------------------------------
--------------------------------------------------
"AbilitySpecial"
{
"01"
{
"var_type" "FIELD_INTEGER"
"marksmanship_agility_bonus" "40 60 80"
}
"02"
{
"var_type" "FIELD_INTEGER"
"radius" "400"
}
// Extra variables
"03"
{
"var_type" "FIELD_FLOAT"
"think_interval" "0.1"
}
}

// Data driven
//-----------------------------------------------------------
--------------------------------------------------
"precache"
{
"particle" "particles/units/hero
es/hero_drow/drow_marksmanship.vpcf"
"particle" "particles/units/hero
es/hero_drow/drow_marksmanship_start.vpcf"
}

"Modifiers"
{
"modifier_marksmanship_passive_datadriven"
{
"Passive" "1"
"IsHidden" "1"
"IsPurgable" "0"

"ThinkInterval" "%think_interval"
"OnIntervalThink"
{
"RunScript"
{
"ScriptFile" "heroes/hero_drow_ran
ger/marksmanship.lua"
"Function" "marksmanship_detecti
on"
}
}
}

"modifier_marksmanship_effect_datadriven"
{
"IsPurgable" "0"

"OnCreated"
{
"FireEffect"
{
"Target" "CASTER"
"EffectName" "particles/units/hero
es/hero_drow/drow_marksmanship_start.vpcf"
"EffectAttachType" "start_at_customorigi
n"
"ControlPointEntities"
{
"CASTER" "attach_attack1"
}
}

"AttachEffect"
{
"Target" "CASTER"
"EffectName" "particles/units/hero
es/hero_drow/drow_marksmanship.vpcf"
"EffectAttachType" "follow_origin"
"ControlPoints"
{
"02" "150 150 150"
}
}
}

"Properties"
{
"MODIFIER_PROPERTY_STATS_AGILITY_BONUS" "%mar
ksmanship_agility_bonus"
}
}
}
}

}
and lua:
function marksmanship_detection( keys ) local caster = keys.caster local
ability = keys.ability local radius = ability:GetLevelSpecialValueFor( "radius",
( ability:GetLevel() - 1 ) ) local modifierName =
"modifier_marksmanship_effect_datadriven"

-- Count units in radius


local units = FindUnitsInRadius( caster:GetTeamNumber(), caster:G
etAbsOrigin(), caster, radius,
DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, 0, 0,
false )
local count = 0
for k, v in pairs( units ) do
count = count + 1
end

-- Apply and destroy


if count == 0 and not caster:HasModifier( modifierName ) then
ability:ApplyDataDrivenModifier( caster, caster, modifierName
, {} )
elseif count ~= 0 and caster:HasModifier( modifierName ) then
caster:RemoveModifierByName( modifierName )
end

end
And the 3rd problem is, I can't add medusa's split shot ability to butterfly
item, how to do it? i have both butterfly text+lua file and splitshot text+lua file
but i don't know how to combine them.
Please help to solve the problems! :(
And if u guys know where to get text+lua files of items: Maelstrom, Mjollnir,
Echo Sabre, Lotus Orb, Bloodthorn, Dragon Lance and Hurricane Pike,
please add link or files if u have them. I didn't find this items

You might also like