Professional Documents
Culture Documents
f=497
BOT SCRIPTING
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.
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
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.
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.
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.
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
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.
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%.
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%.
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.
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:
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.
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
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.
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)
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)
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:
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:
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)
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
Example
This will kill the targeted unit if its Health percent is less than half, and credits the kill to the
caster entity.
The concept of Modding Community doesn't go well together with Competitive Business
My Project Page || My GitHub Profile ||
Myll
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”}
}
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
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
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"
}
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
The concept of Modding Community doesn't go well together with Competitive Business
My Project Page || My GitHub Profile ||
Tigago
"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
"OnSpellStart"
{
""Damage"
{
"Target" "TARGET"
"Type" "DAMAGE_TYPE_MAGICAL"
"Damage" "%AbilityDamage"
}"
}
Tigago
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"
}
}
}
// 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"
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