├── .gitignore
├── Content
├── maps
│ └── template_map.vmap
├── materials
│ └── overviews
│ │ ├── template_map.tga
│ │ ├── template_map.txt
│ │ └── template_map.vmat
└── panorama
│ ├── layout
│ └── custom_game
│ │ ├── custom_hud.xml
│ │ ├── custom_ui_manifest.xml
│ │ └── team_select.xml
│ ├── scripts
│ └── custom_game
│ │ ├── ai_info.js
│ │ └── team_select.js
│ └── styles
│ └── custom_game
│ ├── custom_hud.css
│ └── gamemode_select.css
├── Documentation
├── AI Framework Architecture.png
├── GenAIDocs.bat
└── GenDocs.py
├── Game
├── addoninfo.txt
├── resource
│ ├── addon_english.txt
│ ├── flash3
│ │ └── custom_ui.txt
│ └── overviews
│ │ └── template_map.txt
└── scripts
│ ├── config
│ └── gamemode_ai.kv
│ ├── custom_net_tables.txt
│ ├── npc
│ ├── npc_abilities_custom.txt
│ ├── npc_heroes_custom.txt
│ ├── npc_items_custom.txt
│ └── npc_units_custom.txt
│ └── vscripts
│ ├── AI
│ ├── AIEvents.lua
│ ├── AIManager.lua
│ ├── AIPlayerResource.lua
│ ├── AIUnitTests.lua
│ ├── AIWrapper.lua
│ ├── AbilityWrapper.lua
│ ├── UnitWrapper.lua
│ └── UserAI
│ │ ├── 1v1SF
│ │ ├── AIStateCode.lua
│ │ ├── AIUtil.lua
│ │ ├── Documentation
│ │ │ ├── Pushbot design.txt
│ │ │ └── Pushbot statespace.png
│ │ ├── StateMachine.lua
│ │ └── ai_init.lua
│ │ ├── DotaSample
│ │ └── ai_init.lua
│ │ └── sample_ai
│ │ └── ai_init.lua
│ ├── AIFramework.lua
│ ├── AIGameModes
│ ├── 1v1Mid.lua
│ ├── BaseAIGameMode.lua
│ ├── DotA.lua
│ └── FarmOptimizer.lua
│ ├── Libraries
│ └── Timers.lua
│ ├── LuaModifiers
│ └── modifier_dummy.lua
│ └── addon_game_mode.lua
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | Game/tools_thumbnail_cache.bin
2 | Game/tools_asset_info.bin
3 | *.vpcf_c
4 | *.vtex_c
5 | *.vxml_c
6 | *.vcss_c
7 | Game/maps/
8 | Game/panorama_debugger.cfg
9 | *.vmat_c
10 | *.vjs_c
11 | Game/network_measurement.txt
12 |
--------------------------------------------------------------------------------
/Content/maps/template_map.vmap:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ModDota/Dota2AIFramework/56498981a6d199df9790e8e19f6bf204e00b7f3f/Content/maps/template_map.vmap
--------------------------------------------------------------------------------
/Content/materials/overviews/template_map.tga:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ModDota/Dota2AIFramework/56498981a6d199df9790e8e19f6bf204e00b7f3f/Content/materials/overviews/template_map.tga
--------------------------------------------------------------------------------
/Content/materials/overviews/template_map.txt:
--------------------------------------------------------------------------------
1 | "settings"
2 | {
3 | "clampu" "1"
4 | "clampv" "1"
5 | "nocompress" "1"
6 | "nomip" "1"
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/Content/materials/overviews/template_map.vmat:
--------------------------------------------------------------------------------
1 | "Layer0"
2 | {
3 | "Shader" "ui.vfx"
4 | "F_STENCIL_MASKING" "1"
5 | "F_TRANSLUCENT" "0"
6 | "Texture" "materials/overviews/template_map.tga"
7 | }
8 |
--------------------------------------------------------------------------------
/Content/panorama/layout/custom_game/custom_hud.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Content/panorama/layout/custom_game/custom_ui_manifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
10 |
11 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Content/panorama/layout/custom_game/team_select.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
118 |
119 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
161 |
162 |
166 |
167 |
168 |
169 |
172 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
--------------------------------------------------------------------------------
/Content/panorama/scripts/custom_game/ai_info.js:
--------------------------------------------------------------------------------
1 | //Set AI info
2 | GameUI.CustomUIConfig().AI_Info = {
3 | 'sample_ai' : { 'name' : 'Sample ai' },
4 | '1v1Push' : { 'name' : '1v1 Push' }
5 | }
--------------------------------------------------------------------------------
/Content/panorama/scripts/custom_game/team_select.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // Global list of panels representing each of the teams
4 | var g_TeamPanels = [];
5 |
6 | // Global list of panels representing each of the players (1 per-player). These are reparented
7 | // to the appropriate team panel to indicate which team the player is on.
8 | var g_PlayerPanels = [];
9 |
10 | var g_TEAM_SPECATOR = 1;
11 |
12 | //--------------------------------------------------------------------------------------------------
13 | // Handeler for when the unssigned players panel is clicked that causes the player to be reassigned
14 | // to the unssigned players team
15 | //--------------------------------------------------------------------------------------------------
16 | function OnLeaveTeamPressed()
17 | {
18 | Game.PlayerJoinTeam( DOTATeam_t.DOTA_TEAM_NOTEAM );
19 | }
20 |
21 |
22 | //--------------------------------------------------------------------------------------------------
23 | // Handler for when the Lock and Start button is pressed
24 | //--------------------------------------------------------------------------------------------------
25 | function OnLockAndStartPressed()
26 | {
27 | // Don't allow a forced start if there are unassigned players
28 | if ( Game.GetUnassignedPlayerIDs().length > 0 )
29 | return;
30 |
31 | // Lock the team selection so that no more team changes can be made
32 | Game.SetTeamSelectionLocked( true );
33 |
34 | // Disable the auto start count down
35 | Game.SetAutoLaunchEnabled( false );
36 |
37 | // Set the remaining time before the game starts
38 | Game.SetRemainingSetupTime( 4 );
39 | }
40 |
41 |
42 | //--------------------------------------------------------------------------------------------------
43 | // Handler for when the Cancel and Unlock button is pressed
44 | //--------------------------------------------------------------------------------------------------
45 | function OnCancelAndUnlockPressed()
46 | {
47 | // Unlock the team selection, allowing the players to change teams again
48 | Game.SetTeamSelectionLocked( false );
49 |
50 | // Stop the countdown timer
51 | Game.SetRemainingSetupTime( -1 );
52 | }
53 |
54 |
55 | //--------------------------------------------------------------------------------------------------
56 | // Handler for the auto assign button being pressed
57 | //--------------------------------------------------------------------------------------------------
58 | function OnAutoAssignPressed()
59 | {
60 | // Assign all of the currently unassigned players to a team, trying
61 | // to keep any players that are in a party on the same team.
62 | Game.AutoAssignPlayersToTeams();
63 | }
64 |
65 |
66 | //--------------------------------------------------------------------------------------------------
67 | // Handler for the shuffle player teams button being pressed
68 | //--------------------------------------------------------------------------------------------------
69 | function OnShufflePlayersPressed()
70 | {
71 | // Shuffle the team assignments of any players which are assigned to a team,
72 | // this will not assign any players to a team which are currently unassigned.
73 | // This will also not attempt to keep players in a party on the same team.
74 | Game.ShufflePlayerTeamAssignments();
75 | }
76 |
77 |
78 | //--------------------------------------------------------------------------------------------------
79 | // Find the player panel for the specified player in the global list or create the panel if there
80 | // is not already one in the global list. Make the new or existing panel a child panel of the
81 | // specified parent panel
82 | //--------------------------------------------------------------------------------------------------
83 | function FindOrCreatePanelForPlayer( playerId, parent )
84 | {
85 | // Search the list of player player panels for one witht the specified player id
86 | for ( var i = 0; i < g_PlayerPanels.length; ++i )
87 | {
88 | var playerPanel = g_PlayerPanels[ i ];
89 |
90 | if ( playerPanel.GetAttributeInt( "player_id", -1 ) == playerId )
91 | {
92 | playerPanel.SetParent( parent );
93 | return playerPanel;
94 | }
95 | }
96 |
97 | // Create a new player panel for the specified player id if an existing one was not found
98 | var newPlayerPanel = $.CreatePanel( "Panel", parent, "player_root" );
99 | newPlayerPanel.SetAttributeInt( "player_id", playerId );
100 | newPlayerPanel.BLoadLayout( "file://{resources}/layout/custom_game/team_select_player.xml", false, false );
101 |
102 | // Add the panel to the global list of player planels so that we will find it next time
103 | g_PlayerPanels.push( newPlayerPanel );
104 |
105 | return newPlayerPanel;
106 | }
107 |
108 |
109 | //--------------------------------------------------------------------------------------------------
110 | // Find player slot n in the specified team panel
111 | //--------------------------------------------------------------------------------------------------
112 | function FindPlayerSlotInTeamPanel( teamPanel, playerSlot )
113 | {
114 | var playerListNode = teamPanel.FindChildInLayoutFile( "PlayerList" );
115 | if ( playerListNode == null )
116 | return null;
117 |
118 | var nNumChildren = playerListNode.GetChildCount();
119 | for ( var i = 0; i < nNumChildren; ++i )
120 | {
121 | var panel = playerListNode.GetChild( i );
122 | if ( panel.GetAttributeInt( "player_slot", -1 ) == playerSlot )
123 | {
124 | return panel;
125 | }
126 | }
127 |
128 | return null;
129 | }
130 |
131 |
132 | //--------------------------------------------------------------------------------------------------
133 | // Update the specified team panel ensuring that it has all of the players currently assigned to its
134 | // team and the the remaining slots are marked as empty
135 | //--------------------------------------------------------------------------------------------------
136 | function UpdateTeamPanel( teamPanel )
137 | {
138 | // Get the id of team this panel is displaying
139 | var teamId = teamPanel.GetAttributeInt( "team_id", -1 );
140 | if ( teamId <= 0 )
141 | return;
142 |
143 | // Add all of the players currently assigned to the team
144 | var teamPlayers = Game.GetPlayerIDsOnTeam( teamId );
145 | for ( var i = 0; i < teamPlayers.length; ++i )
146 | {
147 | var playerSlot = FindPlayerSlotInTeamPanel( teamPanel, i );
148 | playerSlot.RemoveAndDeleteChildren();
149 | FindOrCreatePanelForPlayer( teamPlayers[ i ], playerSlot );
150 | }
151 |
152 | // Fill in the remaining player slots with the empty slot indicator
153 | var teamDetails = Game.GetTeamDetails( teamId );
154 | var nNumPlayerSlots = teamDetails.team_max_players;
155 | for ( var i = teamPlayers.length; i < nNumPlayerSlots; ++i )
156 | {
157 | var playerSlot = FindPlayerSlotInTeamPanel( teamPanel, i );
158 | if ( playerSlot.GetChildCount() == 0 )
159 | {
160 | var empty_slot = $.CreatePanel( "Panel", playerSlot, "player_root" );
161 | empty_slot.BLoadLayout( "file://{resources}/layout/custom_game/team_select_empty_slot.xml", false, false );
162 | }
163 | }
164 |
165 | // Change the display state of the panel to indicate the team is full
166 | teamPanel.SetHasClass( "team_is_full", ( teamPlayers.length === teamDetails.team_max_players ) );
167 |
168 | // If the local player is on this team change team panel to indicate this
169 | var localPlayerInfo = Game.GetLocalPlayerInfo()
170 | if ( localPlayerInfo )
171 | {
172 | var localPlayerIsOnTeam = ( localPlayerInfo.player_team_id === teamId );
173 | teamPanel.SetHasClass( "local_player_on_this_team", localPlayerIsOnTeam );
174 | }
175 | }
176 |
177 |
178 | //--------------------------------------------------------------------------------------------------
179 | // Update the unassigned players list and all of the team panels whenever a change is made to the
180 | // player team assignments
181 | //--------------------------------------------------------------------------------------------------
182 | function OnTeamPlayerListChanged()
183 | {
184 | //Stop timer
185 | Game.SetRemainingSetupTime( -1 );
186 |
187 | var unassignedPlayersContainerNode = $( "#UnassignedPlayersContainer" );
188 | if ( unassignedPlayersContainerNode === null )
189 | return;
190 |
191 | // Move all existing player panels back to the unassigned player list
192 | for ( var i = 0; i < g_PlayerPanels.length; ++i )
193 | {
194 | var playerPanel = g_PlayerPanels[ i ];
195 | playerPanel.SetParent( unassignedPlayersContainerNode );
196 | }
197 |
198 | // Make sure all of the unassigned player have a player panel
199 | // and that panel is a child of the unassigned player panel.
200 | var unassignedPlayers = Game.GetUnassignedPlayerIDs();
201 | for ( var i = 0; i < unassignedPlayers.length; ++i )
202 | {
203 | var playerId = unassignedPlayers[ i ];
204 | FindOrCreatePanelForPlayer( playerId, unassignedPlayersContainerNode );
205 | }
206 |
207 | // Update all of the team panels moving the player panels for the
208 | // players assigned to each team to the corresponding team panel.
209 | for ( var i = 0; i < g_TeamPanels.length; ++i )
210 | {
211 | UpdateTeamPanel( g_TeamPanels[ i ] )
212 | }
213 |
214 | // Set the class on the panel to indicate if there are any unassigned players
215 | $( "#GameAndPlayersRoot" ).SetHasClass( "unassigned_players", unassignedPlayers.length != 0 );
216 | $( "#GameAndPlayersRoot" ).SetHasClass( "no_unassigned_players", unassignedPlayers.length == 0 );
217 | }
218 |
219 |
220 | //--------------------------------------------------------------------------------------------------
221 | //--------------------------------------------------------------------------------------------------
222 | function OnPlayerSelectedTeam( nPlayerId, nTeamId, bSuccess )
223 | {
224 | var playerInfo = Game.GetLocalPlayerInfo();
225 | if ( !playerInfo )
226 | return;
227 |
228 | // Check to see if the event is for the local player
229 | if ( playerInfo.player_id === nPlayerId )
230 | {
231 | // Play a sound to indicate success or failure
232 | if ( bSuccess )
233 | {
234 | Game.EmitSound( "ui_team_select_pick_team" );
235 | }
236 | else
237 | {
238 | Game.EmitSound( "ui_team_select_pick_team_failed" );
239 | }
240 | }
241 | }
242 |
243 |
244 | //--------------------------------------------------------------------------------------------------
245 | // Check to see if the local player has host privileges and set the 'player_has_host_privileges' on
246 | // the root panel if so, this allows buttons to only be displayed for the host.
247 | //--------------------------------------------------------------------------------------------------
248 | function CheckForHostPrivileges()
249 | {
250 | var playerInfo = Game.GetLocalPlayerInfo();
251 | if ( !playerInfo )
252 | return;
253 |
254 | // Set the "player_has_host_privileges" class on the panel, this can be used
255 | // to have some sub-panels on display or be enabled for the host player.
256 | $.GetContextPanel().SetHasClass( "player_has_host_privileges", playerInfo.player_has_host_privileges );
257 | }
258 |
259 |
260 | //--------------------------------------------------------------------------------------------------
261 | // Update the state for the transition timer periodically
262 | //--------------------------------------------------------------------------------------------------
263 | function UpdateTimer()
264 | {
265 | var gameTime = Game.GetGameTime();
266 | var transitionTime = Game.GetStateTransitionTime();
267 |
268 | CheckForHostPrivileges();
269 |
270 | var mapInfo = Game.GetMapInfo();
271 | $( "#MapInfo" ).SetDialogVariable( "map_name", mapInfo.map_display_name );
272 |
273 | if ( transitionTime >= 0 )
274 | {
275 | $( "#StartGameCountdownTimer" ).SetDialogVariableInt( "countdown_timer_seconds", Math.max( 0, Math.floor( transitionTime - gameTime ) ) );
276 | $( "#StartGameCountdownTimer" ).SetHasClass( "countdown_active", true );
277 | $( "#StartGameCountdownTimer" ).SetHasClass( "countdown_inactive", false );
278 | }
279 | else
280 | {
281 | $( "#StartGameCountdownTimer" ).SetHasClass( "countdown_active", false );
282 | $( "#StartGameCountdownTimer" ).SetHasClass( "countdown_inactive", true );
283 | }
284 |
285 | var autoLaunch = Game.GetAutoLaunchEnabled();
286 | $( "#StartGameCountdownTimer" ).SetHasClass( "auto_start", autoLaunch );
287 | $( "#StartGameCountdownTimer" ).SetHasClass( "forced_start", ( autoLaunch == false ) );
288 |
289 | // Allow the ui to update its state based on team selection being locked or unlocked
290 | $.GetContextPanel().SetHasClass( "teams_locked", Game.GetTeamSelectionLocked() );
291 | $.GetContextPanel().SetHasClass( "teams_unlocked", Game.GetTeamSelectionLocked() == false );
292 |
293 | $.Schedule( 0.1, UpdateTimer );
294 | }
295 |
296 |
297 | //--------------------------------------------------------------------------------------------------
298 | // Entry point called when the team select panel is created
299 | //--------------------------------------------------------------------------------------------------
300 | (function()
301 | {
302 | var bShowSpectatorTeam = true;
303 | var bAutoAssignTeams = true;
304 |
305 | $( "#TeamSelectContainer" ).SetAcceptsFocus( true ); // Prevents the chat window from taking focus by default
306 | var teamsListRootNode = $( "#TeamsListRoot" );
307 |
308 | // Construct the panels for each team
309 | var allTeamIDs = Game.GetAllTeamIDs();
310 |
311 | if ( bShowSpectatorTeam )
312 | {
313 | allTeamIDs.unshift( g_TEAM_SPECATOR );
314 | }
315 |
316 | for ( var teamId of allTeamIDs )
317 | {
318 | var teamNode = $.CreatePanel( "Panel", teamsListRootNode, "" );
319 | teamNode.AddClass( "team_" + teamId ); // team_1, etc.
320 | teamNode.SetAttributeInt( "team_id", teamId );
321 | teamNode.BLoadLayout( "file://{resources}/layout/custom_game/team_select_team.xml", false, false );
322 |
323 | // Add the team panel to the global list so we can get to it easily later to update it
324 | g_TeamPanels.push( teamNode );
325 | }
326 |
327 | // Automatically assign players to teams.
328 | if ( bAutoAssignTeams )
329 | {
330 | Game.AutoAssignPlayersToTeams();
331 | }
332 |
333 | // Do an initial update of the player team assignment
334 | OnTeamPlayerListChanged();
335 |
336 | // Start updating the timer, this function will schedule itself to be called periodically
337 | UpdateTimer();
338 |
339 | //Stop timer
340 | Game.SetRemainingSetupTime( -1 );
341 |
342 | // Register a listener for the event which is brodcast when the team assignment of a player is actually assigned
343 | $.RegisterForUnhandledEvent( "DOTAGame_TeamPlayerListChanged", OnTeamPlayerListChanged );
344 |
345 | // Register a listener for the event which is broadcast whenever a player attempts to pick a team
346 | $.RegisterForUnhandledEvent( "DOTAGame_PlayerSelectedCustomTeam", OnPlayerSelectedTeam );
347 |
348 | })();
349 |
--------------------------------------------------------------------------------
/Content/panorama/styles/custom_game/custom_hud.css:
--------------------------------------------------------------------------------
1 | #SpectatorOptions {
2 | width: 220px;
3 | height: 125px;
4 | padding: 10px;
5 | position: 1685px 720px 0px;
6 | background-color:#222;
7 | flow-children: down;
8 | }
9 |
10 | .OptionToggle Label {
11 | font-size: 20px;
12 | }
--------------------------------------------------------------------------------
/Content/panorama/styles/custom_game/gamemode_select.css:
--------------------------------------------------------------------------------
1 | .GameModeSelection {
2 | width: 400px;
3 | height: 300px;
4 | background-color: #111;
5 | margin-left: 100px;
6 | margin-top: 100px;
7 | flow-children: down;
8 | padding: 5px 15px 15px 15px;
9 | }
10 |
11 | .TitleLabel {
12 | font-size: 35px;
13 | }
14 |
15 | .SpawnAIButton {
16 | width: 300px;
17 | height: 50px;
18 | horizontal-align: center;
19 | vertical-align: bottom;
20 | background-color: gradient( linear, 0% 0%, 0% 100%, from( #ee0000 ), to( #bb0000 ) );
21 | text-align: center;
22 | }
23 |
24 | .SpawnAIButton:hover {
25 | box-shadow: #ff2222cc 0px 0px 4px 0px;
26 | wash-color: #ff000044;
27 | }
28 |
29 | .SpawnAIButton:active {
30 | background-color: gradient( linear, 0% 0%, 0% 100%, from( #880000 ), to( #bb0000 ) );
31 | }
32 |
33 | .SpawnAIButton Label {
34 | font-size: 30px;
35 | margin-top: 3px;
36 |
37 | horizontal-align: center;
38 | text-transform: uppercase;
39 | letter-spacing: 2px;
40 | text-align: center;
41 | vertical-align: center;
42 | text-shadow: 2px 2px 4px 1.0 #000000;
43 | }
44 |
45 | .GameModeDropDown {
46 | horizontal-align: center;
47 | margin-top: 5px;
48 | height: 50px;
49 | }
50 |
51 | .GameModeDropDown Label {
52 | font-size: 25px;
53 | }
54 |
55 | .AISelection {
56 | width: 400px;
57 | }
58 |
59 | .AISelectionLabel {
60 | margin-top: 8px;
61 | margin-left: 12px;
62 | font-size: 22px;
63 | }
64 |
65 | .AIDropdownContainer {
66 | margin-left: 10px;
67 | margin-top: 40px;
68 | flow-children: right;
69 | }
70 |
71 | .AIDropdown {
72 | width: 170px;
73 | margin-right: 10px;
74 | }
--------------------------------------------------------------------------------
/Documentation/AI Framework Architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ModDota/Dota2AIFramework/56498981a6d199df9790e8e19f6bf204e00b7f3f/Documentation/AI Framework Architecture.png
--------------------------------------------------------------------------------
/Documentation/GenAIDocs.bat:
--------------------------------------------------------------------------------
1 | python GenDocs.py ../Game/scripts/vscripts/AI/AIWrapper.lua global.txt
2 | python GenDocs.py ../Game/scripts/vscripts/AI/UnitWrapper.lua unit.txt
3 | python GenDocs.py ../Game/scripts/vscripts/AI/AbilityWrapper.lua ability.txt
--------------------------------------------------------------------------------
/Documentation/GenDocs.py:
--------------------------------------------------------------------------------
1 | """"
2 | Lua docs generator
3 | Generates code documentation based on comments.
4 |
5 | Comment syntax:
6 |
7 | This script only considers comment blocks with a --[[[ opening and ]] ending, one comment block per function.
8 | Data can be added to each documentation entry using the following keywords:
9 | @func [name] - The name of the function (Required!).
10 | @desc [text] - Adds a description of the function.
11 | @modification [text] - Adds a description of the modification of this function.
12 | @param {[type]} [name] [description] - Adds a single parameter description, multiple of these can be used.
13 | the type part is optionals for example '@param varA variable A' is valid and '@param {string} varB variable B' is too.
14 | @return {[type]} [description] - Adds a description for the output of this function.
15 | """""
16 |
17 | import sys
18 | import re
19 |
20 | def GenerateDocs( inputFileName, outputFileName ):
21 | inputString = ""
22 |
23 | with open( inputFileName, "r" ) as myFile:
24 | inputString = myFile.read()
25 |
26 | blockPattern = '--\[\[\[([\s\S]*?)\]\]'
27 | blocks = re.finditer( blockPattern, inputString )
28 |
29 | docList = []
30 |
31 | for block in blocks:
32 | blockString = block.group(1)
33 |
34 | functionDoc = {}
35 |
36 | funcIndices = FindAllIndices( '@func', blockString )
37 | descIndices = FindAllIndices( '@desc', blockString )
38 | paramIndices = FindAllIndices( '@param', blockString )
39 | modIndices = FindAllIndices( '@modification', blockString )
40 | returnIndices = FindAllIndices( '@return', blockString )
41 |
42 | allIndices = [len( blockString )]
43 | allIndices.extend( funcIndices )
44 | allIndices.extend( descIndices )
45 | allIndices.extend( paramIndices )
46 | allIndices.extend( modIndices )
47 | allIndices.extend( returnIndices )
48 | allIndices.sort()
49 |
50 | #Add function name
51 | funcStart = funcIndices[0]
52 | funcEnd = FindNextIndex( allIndices, funcStart )
53 | functionDoc['func'] = blockString[funcStart+6:funcEnd].strip().replace('( ','(').replace(' )',')')
54 |
55 | #Add description
56 | if len( descIndices ) > 0:
57 | descStart = descIndices[0]
58 | descEnd = FindNextIndex( allIndices, descStart )
59 | functionDoc['desc'] = blockString[descStart+6:descEnd].strip()
60 |
61 | #Add modification
62 | if len( descIndices ) > 0:
63 | modStart = modIndices[0]
64 | modEnd = FindNextIndex( allIndices, modStart )
65 | functionDoc['mod'] = blockString[modStart+14:modEnd].strip()
66 |
67 | #Add return
68 | if len( returnIndices ) > 0:
69 | returnStart = returnIndices[0]
70 | returnEnd = FindNextIndex( allIndices, returnStart )
71 | returnString = blockString[returnStart+8:returnEnd].strip()
72 | returnType = re.match('^{([\S]*?)} ', returnString)
73 | if returnType == None:
74 | functionDoc['return'] = {'desc' : returnString}
75 | else:
76 | functionDoc['return'] = {'desc' : returnString.replace(returnType.group(0),''), 'type' : returnType.group(1)}
77 |
78 | functionDoc['params'] = []
79 | for paramIndex in paramIndices:
80 | paramStart = paramIndex
81 | paramEnd = FindNextIndex( allIndices, paramStart )
82 | paramString = blockString[paramStart+7:paramEnd].strip()
83 | paramType = re.match('^{([\S]*?)} ', paramString)
84 | if paramType != None:
85 | paramString = paramString.replace(paramType.group(0),'')
86 | firstSpace = paramString.find(' ')
87 | paramName = paramString[0:firstSpace]
88 | paramDesc = paramString[firstSpace+1:]
89 |
90 | if paramType == None:
91 | functionDoc['params'].append({'name':paramName,'desc':paramDesc})
92 | else:
93 | functionDoc['params'].append({'name':paramName,'desc':paramDesc,'type':paramType.group(1)})
94 |
95 | docList.append( functionDoc )
96 |
97 | WriteDocs( docList, outputFileName )
98 |
99 | def WriteDocs( docList, outputFileName ):
100 | docList.sort(key=lambda f: f['func'])
101 |
102 | lines = []
103 |
104 | lines.append( '## Table of contents\n' )
105 | for f in docList:
106 | funcName = f['func'][:f['func'].find('(')]
107 | refName = GetRefName( f['func'] )
108 | lines.append( '+ [' + funcName + '](#' + refName + ')\n' )
109 |
110 | lines.append( '\n# Function List\n\n' )
111 |
112 | for f in docList:
113 | lines.append( '## ' + f['func'].replace('(','(_').replace(')','_)').replace('__','') + '\n' )
114 |
115 | if 'desc' in f:
116 | lines.append( '#### Description\n' + f['desc'].replace('\n','
') + '\n' )
117 |
118 | if len(f['params']) > 0:
119 | lines.append('\n#### Parameters\n')
120 | for p in f['params']:
121 | if 'type' in p:
122 | lines.append( '+ **' + p['name'] + '** - ' + p['desc'] + '\n' )
123 | else:
124 | lines.append( '+ **' + p['name'] + '** [_' + p['type'] + '_] - ' + p['desc'] + '\n' )
125 |
126 | if 'mod' in f:
127 | lines.append( '\n#### Modification\n' + f['mod'].replace('-','\-').replace('\n','
') + '\n' )
128 |
129 | if 'return' in f:
130 | lines.append( '\n#### Return value\n' )
131 | if 'type' in f['return']:
132 | lines.append( '**Type:** '+ f['return']['type'] +'
\n' )
133 | lines.append( '**Description:** '+ f['return']['desc'].replace('\n','
') +'\n' )
134 |
135 | lines.append( '\n****\n\n' )
136 |
137 | with open( outputFileName, 'w' ) as fo:
138 | fo.writelines( lines )
139 |
140 | def FindAllIndices( word, string ):
141 | list = []
142 | index = -1
143 |
144 | while True:
145 | index = string.find( word, index + 1 )
146 |
147 | if index == -1: break
148 |
149 | list.append( index )
150 |
151 | return list
152 |
153 | def FindNextIndex( indices, oldIndex ):
154 | for index in indices:
155 | if index > oldIndex:
156 | return index
157 | return -1
158 |
159 | def GetRefName( name ):
160 | newstring = name.replace(' ','-')
161 | newstring = newstring.replace('(','')
162 | newstring = newstring.replace(')','')
163 | newstring = newstring.replace(',','')
164 | newstring = newstring.replace(':','')
165 | return newstring.lower()
166 |
167 | GenerateDocs( sys.argv[1], sys.argv[2] )
--------------------------------------------------------------------------------
/Game/addoninfo.txt:
--------------------------------------------------------------------------------
1 | "AddonInfo"
2 | {
3 | }
4 |
--------------------------------------------------------------------------------
/Game/resource/addon_english.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ModDota/Dota2AIFramework/56498981a6d199df9790e8e19f6bf204e00b7f3f/Game/resource/addon_english.txt
--------------------------------------------------------------------------------
/Game/resource/flash3/custom_ui.txt:
--------------------------------------------------------------------------------
1 | "CustomUI"
2 | {
3 | // swf files should be published into the same folder as this file
4 |
5 | // Add a numbered block for each swf file to load when your addon starts
6 |
7 |
8 | //"1"
9 | //{
10 | // "File" "Example"
11 | // "Depth" "50"
12 | //}
13 | }
14 |
15 | // =============================================
16 | // Depths of base Dota UI elements
17 | // =============================================
18 | // hud_chat: 8,
19 |
20 | // error_msg: 10,
21 |
22 | // voicechat: 11,
23 | // shop: 12,
24 | // tutorial: 13,
25 | // herodisplay: 14,
26 | // actionpanel: 15,
27 | // inventory: 16,
28 | // channelbar: 17,
29 |
30 | // gameend: 19,
31 | // chat_wheel: 20,
32 | // survey: 21,
33 | // quests: 22,
34 | // questlog: 23,
35 |
36 | // ti_onstage_side: 30,
37 |
38 | // last_hit_challenge: 35,
39 | // waitingforplayers: 36,
40 | // highlight_reel: 37,
41 | // stats_dropdown: 38,
42 | // halloween: 39,
43 | // killcam: 40, // and inspect
44 | // scoreboard: 41,
45 | // quickstats: 42,
46 | // shared_units: 43,
47 | // shared_content: 44,
48 |
49 | // holdout: 50,
50 |
51 | // spectator_items: 145,
52 | // spectator_graph: 146,
53 | // spectator_harvest: 147,
54 | // spectator_player: 148,
55 | // spectator_fantasy: 149,
56 |
57 | // heroselection: 250,
58 | // spectate_heroselection: 251,
59 | // shared_heroselectorandloadout : 252,
60 |
61 | // broadcaster: 364,
62 |
63 | // spectate: 365,
64 | // coach: 366,
65 |
66 | // combat_log: 367,
67 |
68 | // guide_panel: 368,
69 |
70 | // loadgame: 380,
71 |
72 | // report_dialogue : 381,
73 | // popups : 382,
74 | // matchmaking_ready : 383,
75 |
76 | // ti_onstage_pods: 500,
77 |
78 | // overlay: 1000
79 | // =============================================
--------------------------------------------------------------------------------
/Game/resource/overviews/template_map.txt:
--------------------------------------------------------------------------------
1 | template_map
2 | {
3 | material materials/overviews/template_map.vmat
4 | pos_x -8192
5 | pos_y 8192
6 | scale 16.000
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/Game/scripts/config/gamemode_ai.kv:
--------------------------------------------------------------------------------
1 | "GameModes"
2 | {
3 | "1"
4 | {
5 | "Name" "1v1 Mid"
6 | "Path" "1v1Mid"
7 |
8 | "AI"
9 | {
10 | "1" "1v1SF"
11 | "2" "sample_ai"
12 | }
13 | }
14 |
15 | "2"
16 | {
17 | "Name" "Default DotA"
18 | "Path" "DotA"
19 |
20 | "AI"
21 | {
22 | "1" "DotaSample"
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/Game/scripts/custom_net_tables.txt:
--------------------------------------------------------------------------------
1 |
2 | {
3 | custom_net_tables =
4 | [
5 | "config",
6 | ]
7 | }
--------------------------------------------------------------------------------
/Game/scripts/npc/npc_abilities_custom.txt:
--------------------------------------------------------------------------------
1 | // Dota Heroes File
2 | "DOTAAbilities"
3 | {
4 | "Version" "1"
5 |
6 | //=================================================================================================================
7 | // Templar Assassin: Refraction Holdout
8 | //=================================================================================================================
9 | "templar_assassin_refraction_holdout"
10 | {
11 | // General
12 | //-------------------------------------------------------------------------------------------------------------
13 | "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE"
14 | "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL"
15 |
16 | // Casting
17 | //-------------------------------------------------------------------------------------------------------------
18 | "AbilityCastPoint" "0.0 0.0 0.0 0.0"
19 |
20 | // Time
21 | //-------------------------------------------------------------------------------------------------------------
22 | "AbilityCooldown" "17.0 17.0 17.0 17.0"
23 |
24 | // Cost
25 | //-------------------------------------------------------------------------------------------------------------
26 | "AbilityManaCost" "100"
27 |
28 | // Special
29 | //-------------------------------------------------------------------------------------------------------------
30 | "AbilitySpecial"
31 | {
32 | "01"
33 | {
34 | "var_type" "FIELD_INTEGER"
35 | "damage_absorb" "200 300 400 500"
36 | }
37 | "02"
38 | {
39 | "var_type" "FIELD_INTEGER"
40 | "bonus_damage" "20 40 60 80"
41 | }
42 | "04"
43 | {
44 | "var_type" "FIELD_FLOAT"
45 | "duration" "17.0 17.0 17.0 17.0"
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Game/scripts/npc/npc_heroes_custom.txt:
--------------------------------------------------------------------------------
1 | // Dota Heroes File
2 | // NOTE: This is not for creating new heroes, this is for taking a currently existing hero as a template and overriding
3 | // the specified key-value combinations. Use override_hero for this.
4 | "DOTAHeroes"
5 | {
6 | //=================================================================================================================
7 | // HERO: Templar Assassin
8 | //=================================================================================================================
9 | "npc_dota_hero_templar_assassin_template"
10 | {
11 | "override_hero" "npc_dota_hero_templar_assassin" // Hero to override
12 | "Ability1" "templar_assassin_refraction_holdout" // Ability 1
13 | "VisionNighttimeRange" "1800" // Range of vision at night time.
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Game/scripts/npc/npc_items_custom.txt:
--------------------------------------------------------------------------------
1 | "DOTAAbilities"
2 | {
3 | }
--------------------------------------------------------------------------------
/Game/scripts/npc/npc_units_custom.txt:
--------------------------------------------------------------------------------
1 | // Units File
2 | "DOTAUnits"
3 | {
4 | "Version" "1"
5 |
6 | //=================================================================================
7 | // Creature: Gnoll Assassin
8 | //=================================================================================
9 | "npc_dota_creature_gnoll_assassin"
10 | {
11 | // General
12 | //----------------------------------------------------------------
13 | "Model" "models/creeps/neutral_creeps/n_creep_gnoll/n_creep_gnoll_frost.vmdl" // Model.
14 | "BaseClass" "npc_dota_creature"
15 | "SoundSet" "n_creep_Ranged"
16 | "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts"
17 | "Level" "1"
18 | "ModelScale" ".9"
19 |
20 | // Abilities
21 | //----------------------------------------------------------------
22 | "Ability1" "" // Ability 1
23 | "Ability2" "" // Ability 2
24 | "Ability3" "" // Ability 3
25 | "Ability4" "" // Ability 4
26 |
27 | // Armor
28 | //----------------------------------------------------------------
29 | "ArmorPhysical" "1" // Physical protection.
30 |
31 | // Attack
32 | //----------------------------------------------------------------
33 | "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK"
34 | "AttackDamageMin" "30" // Damage range min.
35 | "AttackDamageMax" "36" // Damage range max.
36 | "AttackRate" "1.6" // Speed of attack.
37 | "AttackAnimationPoint" "0.4" // Normalized time in animation cycle to attack.
38 | "AttackAcquisitionRange" "800" // Range within a target can be acquired.
39 | "AttackRange" "500" // Range within a target can be attacked.
40 | "ProjectileModel" "particles/neutral_fx/gnoll_base_attack.vpcf" // Particle system model for projectile.
41 | "ProjectileSpeed" "1500" // Speed of projectile.
42 |
43 | // Bounds
44 | //----------------------------------------------------------------
45 | "RingRadius" "40"
46 | "HealthBarOffset" "170"
47 |
48 | // Bounty
49 | //----------------------------------------------------------------
50 | "BountyXP" "24" // Experience earn.
51 | "BountyGoldMin" "21" // Gold earned min.
52 | "BountyGoldMax" "29" // Gold earned max.
53 |
54 | // Movement
55 | //----------------------------------------------------------------
56 | "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND"
57 | "MovementSpeed" "270" // Speed.
58 |
59 | // Status
60 | //----------------------------------------------------------------
61 | "StatusHealth" "75" // Base health.
62 | "StatusHealthRegen" "0.5" // Health regeneration rate.
63 | "StatusMana" "0" // Base mana.
64 | "StatusManaRegen" "0.0" // Mana regeneration rate.
65 |
66 | // Vision
67 | //----------------------------------------------------------------
68 | "VisionDaytimeRange" "400" // Range of vision during day light.
69 | "VisionNighttimeRange" "400" // Range of vision at night time.
70 |
71 | // Team
72 | //----------------------------------------------------------------
73 | "TeamName" "DOTA_TEAM_NEUTRALS" // Team name.
74 | "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE"
75 | "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC"
76 | "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT"
77 |
78 | // Creature Data
79 | //----------------------------------------------------------------
80 | "Creature"
81 | {
82 | //Level Up
83 | "HPGain" "50"
84 | "DamageGain" "2"
85 | "ArmorGain" "0.25"
86 | "MagicResistGain" "0.1"
87 | "MoveSpeedGain" "1"
88 | "BountyGain" "3"
89 | "XPGain" "15"
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/Game/scripts/vscripts/AI/AIEvents.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | AIEvents.
3 |
4 | This file contains the event manager available to AI. It filters events so that
5 | AIs will only get information that a human player would get.
6 |
7 | Code: Perry
8 | Date: October, 2015
9 | ]]
10 |
11 | --Class definition
12 | if AIEvents == nil then
13 | AIEvents = class({})
14 | end
15 |
16 | --Constructor
17 | function AIEvents:constructor( team )
18 | self.team = team
19 | end
20 |
21 | local function ForAllTeams( params, team )
22 | return true
23 | end
24 |
25 | local function IsAlly( unit, team )
26 | return unit:GetTeamNumber() == team
27 | end
28 |
29 | local function IsPlayerAlly( playerID, team )
30 | return PlayerResource:GetTeam( playerID ) == team
31 | end
32 |
33 | --Define event filters
34 | local FilterFunctions = {
35 | ['team_score'] = ForAllTeams,
36 | ['npc_spawned'] = function( params, team )
37 | --Fetch the spawned unit
38 | local unit = EntIndexToHScript( params.entindex )
39 |
40 | --Allow the event if it's in team vision
41 | if InVision( unit, team ) then
42 | return true
43 | end
44 |
45 | return false
46 | end,
47 | ['entity_killed'] = function( params, team )
48 | local killedUnit = EntIndexToHScript( params.entindex_killed )
49 |
50 | if InVision( killedUnit, team ) then
51 | return true
52 | end
53 |
54 | return true
55 | end,
56 | ['entity_hurt'] = function( params, team )
57 | --Get hurt unit
58 | local hurtUnit = EntIndexToHScript( params.entindex_killed )
59 |
60 | --Check if the hurt unit is in vision
61 | if InVision( hurtUnit, team ) then
62 | return true
63 | end
64 |
65 | return false
66 | end,
67 | ['game_rules_state_change'] = ForAllTeams,
68 | ['modifier_event'] = function( params, team )
69 | return false
70 | end,
71 | ['dota_player_kill'] = ForAllTeams,
72 | ['dota_player_deny'] = ForAllTeams,
73 | ['dota_barracks_kill'] = ForAllTeams,
74 | ['dota_tower_kill'] = ForAllTeams,
75 | ['dota_roshan_kill'] = ForAllTeams,
76 | ['dota_courier_lost'] = ForAllTeams,
77 | ['dota_courier_respawned'] = ForAllTeams,
78 | ['dota_glyph_used'] = ForAllTeams,
79 | ['dota_super_creeps'] = ForAllTeams,
80 | ['dota_rune_pickup'] = function( params, team )
81 | local hero = PlayerResource:GetSelectedHeroEntity( params.userid )
82 |
83 | if InVision( hero, team ) then
84 | return true
85 | end
86 |
87 | return false
88 | end,
89 | ['dota_rune_spotted'] = function( params, team )
90 | local hero = PlayerResource:GetSelectedHeroEntity( params.userid )
91 |
92 | if InVision( hero, team ) then
93 | return true
94 | end
95 |
96 | return false
97 | end,
98 | ['dota_item_spotted'] = function( params, team )
99 | local hero = PlayerResource:GetSelectedHeroEntity( params.userid )
100 |
101 | if InVision( hero, team ) then
102 | return true
103 | end
104 |
105 | return false
106 | end,
107 | ['dota_item_picked_up'] = function( params, team )
108 | local hero = EntIndexToHScript( params.HeroEntityIndex )
109 |
110 | if InVision( hero, team ) then
111 | return true
112 | end
113 |
114 | return false
115 | end,
116 | ['last_hit'] = function( params, team )
117 | local hero = PlayerResource:GetSelectedHeroEntity( params.PlayerID )
118 |
119 | if InVision( hero, team ) then
120 | return true
121 | end
122 |
123 | return false
124 | end,
125 | ['player_reconnected'] = ForAllTeams,
126 | ['nommed_tree'] = function( params, team )
127 | local hero = PlayerResource:GetSelectedHeroEntity( params.PlayerID )
128 |
129 | if InVision( hero, team ) then
130 | return true
131 | end
132 |
133 | return false
134 | end,
135 | ['dota_rune_activated_server'] = function( params, team )
136 | local hero = PlayerResource:GetSelectedHeroEntity( params.PlayerID )
137 |
138 | if InVision( hero, team ) then
139 | return true
140 | end
141 |
142 | return false
143 | end,
144 | ['dota_player_gained_level'] = ForAllTeams,
145 | ['dota_player_pick_hero'] = ForAllTeams,
146 | ['dota_player_learned_ability'] = function( params, team )
147 | if IsPlayerAlly( params.player ) then
148 | return true
149 | end
150 |
151 | return false
152 | end,
153 | ['dota_player_used_ability'] = function( params, team )
154 | local hero = PlayerResource:GetSelectedHeroEntity( params.PlayerID )
155 |
156 | if InVision( hero, team ) then
157 | return true
158 | end
159 |
160 | return false
161 | end,
162 | ['dota_player_killed'] = ForAllTeams,
163 | ['dota_item_purchased'] = function( params, team )
164 | if IsPlayerAlly( params.PlayerID, team ) then
165 | return true
166 | end
167 |
168 | return false
169 | end,
170 | ['dota_item_used'] = function( params, team )
171 | local hero = PlayerResource:GetSelectedHeroEntity( params.PlayerID )
172 |
173 | if InVision( hero, team ) then
174 | return true
175 | end
176 |
177 | return false
178 | end,
179 | ['player_fullyjoined'] = ForAllTeams,
180 | ['dota_match_done'] = ForAllTeams,
181 | ['dota_hero_swap'] = ForAllTeams,
182 | ['show_center_message'] = ForAllTeams,
183 | ['player_chat'] = function( params, team )
184 | if params.teamonly then
185 | if IsPlayerAlly( params.userid ) then
186 | return true
187 | else
188 | return false
189 | end
190 | else
191 | return true
192 | end
193 | end
194 | }
195 |
196 | --Register an event listener
197 | function AIEvents:RegisterEventListener( eventName, callback )
198 | ListenToGameEvent( eventName, function( s, params )
199 | --Wait one frame to prevent weirdness
200 | Timers:CreateTimer( function()
201 | --Get filter function
202 | local filter = FilterFunctions[ eventName ]
203 |
204 | --If the filter allows, fire the callback
205 | if filter and filter( params, self.team ) then
206 | callback( params )
207 | end
208 | end)
209 | end, self )
210 | end
211 |
212 | --Test if all events are there
213 | function AIEvents:UnitTest()
214 | local events = {
215 | 'team_score',
216 | 'npc_spawned',
217 | 'entity_killed',
218 | 'entity_hurt',
219 | 'game_rules_state_change',
220 | 'dota_player_kill',
221 | 'dota_player_deny',
222 | 'dota_barracks_kill',
223 | 'dota_tower_kill',
224 | 'dota_roshan_kill',
225 | 'dota_courier_lost',
226 | 'dota_courier_respawned',
227 | 'dota_glyph_used',
228 | 'dota_super_creeps',
229 | 'dota_rune_pickup',
230 | 'dota_rune_spotted',
231 | 'dota_item_spotted',
232 | 'dota_item_picked_up',
233 | 'last_hit',
234 | 'player_reconnected',
235 | 'nommed_tree',
236 | 'dota_rune_activated_server',
237 | 'dota_player_gained_level',
238 | 'dota_player_pick_hero',
239 | 'dota_player_learned_ability',
240 | 'dota_player_used_ability',
241 | 'dota_player_killed',
242 | 'dota_item_purchased',
243 | 'dota_item_used',
244 | 'player_fullyjoined',
245 | 'dota_match_done',
246 | 'dota_hero_swap',
247 | 'show_center_message',
248 | 'player_chat'
249 | }
250 |
251 | local success = 0
252 | local fail = 0
253 |
254 | --Check if all events are there
255 | for _,event in ipairs( events ) do
256 | if FilterFunctions[event] == nil then
257 | fail = fail + 1
258 | Warning( string.format( 'Event %q missing from AI events!', event ) )
259 | else
260 | success = success + 1
261 | end
262 | end
263 |
264 | if fail == 0 then
265 | print( string.format('AI Event Unit test: (%i/%i) Success!', success, success ) )
266 | else
267 | Warning( string.format('AI Event Unit test: (%i/%i) Succeeded.\n', success, success + fail ) )
268 | end
269 |
270 | end
--------------------------------------------------------------------------------
/Game/scripts/vscripts/AI/AIManager.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | AI Manager.
3 |
4 | Highest level AI module, used for setting up AI in the game.
5 |
6 | Code: Perry
7 | Date: October, 2015
8 | ]]
9 |
10 | require( 'AI.AIWrapper' )
11 | require( 'AI.AIEvents' )
12 | require( 'AI.AIPlayerResource' )
13 | require( 'AI.UnitWrapper' )
14 | require( 'AI.AbilityWrapper' )
15 | require( 'AI.AIUnitTests' )
16 |
17 | if AIManager == nil then
18 | AIManager = class({})
19 | else
20 | AIManager:OnScriptReload()
21 | end
22 |
23 | --Initialise the AIManager
24 | function AIManager:Init()
25 | ListenToGameEvent( 'dota_player_pick_hero', AIManager.OnPlayerConnect, self )
26 | ListenToGameEvent( 'game_rules_state_change', AIManager.OnGameStateChange, self )
27 |
28 | AIManager.visionDummies = {}
29 | AIManager.numPlayers = 0
30 |
31 | AIManager.aiHandles = {}
32 | AIManager.aiNames = {}
33 |
34 | AIManager.playerRequests = {}
35 | AIManager.aiPlayers = {}
36 | AIManager.aiHeroes = {}
37 |
38 | AIManager.heroesToSpawn = 0
39 | AIManager.heroesSpawned = 0
40 |
41 | AIManager:PopulateItemTable()
42 |
43 | AIManager.AllowInGameLogging = true
44 | AIManager.AllowDebugDrawing = true
45 | AIManager.ForceDrawColor = true
46 |
47 | --Update the settings when an event from the client is received
48 | CustomGameEventManager:RegisterListener( 'spectator_options_update', function( player, event )
49 | AIManager.AllowInGameLogging = event.allowLog == 1
50 | if event.allowDraw == 0 and AIManager.AllowDebugDrawing == 1 then
51 | DebugDrawClear()
52 | end
53 | AIManager.AllowDebugDrawing = event.allowDraw == 1
54 | AIManager.ForceDrawColor = event.forceColor == 1
55 | end)
56 | end
57 |
58 | --Fetch a table of item and custom items
59 | function AIManager:PopulateItemTable()
60 | AIManager.itemTable = LoadKeyValues( 'scripts/npc/items.txt' )
61 | local customItems = LoadKeyValues( 'scripts/npc/items_custom.txt' )
62 | if customItems ~= nil then
63 | for itemName, item in pairs( customItems ) do
64 | AIManager.itemTable[ itemName ] = item
65 | end
66 | end
67 | end
68 |
69 | --script_reload handling
70 | function AIManager:OnScriptReload()
71 | --Reload AI functions
72 | for team, ai in pairs( AIManager.aiHandles ) do
73 | local newAI = AIManager:LoadAI( AIManager.aiNames[ team ], team )
74 | for k,v in pairs( newAI ) do
75 | if type(v) == 'function' then
76 | ai[k] = v
77 | end
78 | end
79 | end
80 | end
81 |
82 | --A player has connected, if it's a fake client assign it to an AI that wants a player
83 | function AIManager:OnPlayerConnect( event )
84 | --Only bots pick heroes in setup
85 | if GameRules:State_Get() == DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then
86 | --Handle player request
87 | local request = table.remove( AIManager.playerRequests, 1 )
88 |
89 | if request ~= nil then
90 | local hero = EntIndexToHScript( event.heroindex )
91 | local pID = hero:GetPlayerOwnerID()
92 |
93 | -- Set team
94 | PlayerResource:SetCustomTeamAssignment( pID, request.team )
95 |
96 | --Remember we have to spawn a hero for this player
97 | AIManager.heroesToSpawn = AIManager.heroesToSpawn + 1
98 |
99 | if AIManager.aiPlayers[ request.team ] == nil then
100 | AIManager.aiPlayers[ request.team ] = {}
101 |
102 | --Initialise array for heroes for this team too while we're at it
103 | AIManager.aiHeroes[ request.team ] = {}
104 | end
105 |
106 | table.insert( AIManager.aiPlayers[ request.team ], { pID = pID, hero = request.hero } )
107 | end
108 | end
109 | end
110 |
111 | --Game state change handler
112 | function AIManager:OnGameStateChange( event )
113 | local gameState = GameRules:State_Get()
114 |
115 | if gameState == DOTA_GAMERULES_STATE_HERO_SELECTION then
116 | --In hero selection, spawn the heroes for all AIs
117 | for team, players in pairs( AIManager.aiPlayers ) do
118 | for _,player in pairs( players ) do
119 | --Precache the hero
120 | PrecacheUnitByNameAsync( player.hero, function()
121 | AIManager:PrecacheDone( player.pID, player.hero, team )
122 | end, player.pID )
123 | end
124 | end
125 | end
126 | end
127 |
128 | --The precache of a hero is done, spawn the hero for the player
129 | function AIManager:PrecacheDone( pID, heroName, team )
130 | --Spawn the hero
131 | local player = PlayerResource:GetPlayer( pID )
132 | local hero = PlayerResource:ReplaceHeroWith( pID, heroName, 0, 0 )
133 | hero:RespawnHero(false, true, false)
134 |
135 | table.insert( AIManager.aiHeroes[ team ], hero )
136 | end
137 |
138 | --Initialise all AI
139 | function AIManager:InitAllAI( gameMode )
140 | --Initialise all AI
141 | print('Initialising AI')
142 | for team, ai in pairs( AIManager.aiHandles ) do
143 | --Wrap heroes
144 | local wrappedHeroes = {}
145 | for _, hero in pairs( AIManager.aiHeroes[ team ] ) do
146 | table.insert( wrappedHeroes, WrapUnit( hero, team ) )
147 | end
148 |
149 | --Initialise AI
150 | ai:Init( { team = team, heroes = wrappedHeroes, data = gameMode:GetExtraData( team ) } )
151 | end
152 | end
153 |
154 | --Attach AI to an existing unit
155 | function AIManager:AttachAI( aiName, unit )
156 | --Load the ai
157 | local team = unit:GetTeam()
158 | local ai = AIManager:LoadAI( aiName, team )
159 |
160 | --Initialise the AI with the unit
161 | ai:Init( { team = team, unit = unit } )
162 | end
163 |
164 | --Get all AI heroes
165 | function AIManager:GetAllAIHeroes()
166 | return AIManager.aiHeroes
167 | end
168 |
169 | --Get all heroes
170 | function AIManager:GetAllHeroes()
171 | local heroes = {}
172 | local heroList = HeroList:GetAllHeroes()
173 | for _, hero in pairs(heroList) do
174 | if heroes[hero:GetTeam()] == nil then
175 | heroes[hero:GetTeam()] = {}
176 | end
177 |
178 | table.insert(heroes[hero:GetTeam()], hero)
179 | end
180 |
181 | return heroes
182 | end
183 |
184 | --Get all heroes belonging to an AI team
185 | function AIManager:GetAIHeroes( team )
186 | return AIManager.aiHeroes[ team ]
187 | end
188 |
189 | --Get all heroes belonging to an AI team wrapped
190 | function AIManager:GetWrappedAIHeroes( team )
191 | local wrappedHeroes = {}
192 |
193 | for _, hero in pairs( AIManager.aiHeroes[ team ] ) do
194 | table.insert( wrappedHeroes, WrapUnit( hero, team ) )
195 | end
196 |
197 | return wrappedHeroes
198 | end
199 |
200 | --Initialise an AI player
201 | function AIManager:AddAI( name, team, heroes )
202 | --Load an AI
203 | local ai = AIManager:LoadAI( name, team )
204 | AIManager.aiHandles[ team ] = ai
205 | AIManager.aiNames[ team ] = name
206 |
207 | --Make a dummy to use for visoin checks
208 | AIManager.visionDummies[ team ] = CreateUnitByName( 'npc_dota_thinker', Vector(0,0,0), false, nil, nil, team )
209 | AIManager.visionDummies[ team ]:AddNewModifier( nil, nil, 'modifier_dummy', {} ) --Apply the dummy modifier
210 |
211 | --Request heroes
212 | for i, hero in ipairs( heroes ) do
213 | table.insert( AIManager.playerRequests, { team = team, hero = hero } )
214 | Tutorial:AddBot( hero, '', '', false )
215 | end
216 |
217 | AIManager.numPlayers = AIManager.numPlayers + #heroes
218 |
219 | return ai
220 | end
221 |
222 | --Load a sandboxed AI player
223 | function AIManager:LoadAI( name, team )
224 | --Define custom _G
225 | local global = {}
226 | global.AIWrapper = AIWrapper( team )
227 | global.AIEvents = AIEvents( team )
228 | global.AIPlayerResource = AIPlayerResource( team )
229 |
230 | --Populate global functions
231 | global = AIManager:PopulateAIGlobals( name, global, global.AIWrapper )
232 |
233 | --Load file in sandbox
234 | local script = assert(loadfile('AI.UserAI.'..name..'.ai_init'))
235 | setfenv( script, global )
236 | return script()
237 | end
238 |
239 | --Make wrapper functions available globally to the AI
240 | function AIManager:PopulateAIGlobals( name, global, wrapper )
241 |
242 | --Lua defaults
243 | global.math = math
244 | global.table = table
245 | global.bit = bit
246 | global.print = print
247 | global.pairs = pairs
248 | global.ipairs = ipairs
249 | global.type = type
250 | global.string = string
251 | global._G = global
252 | global.pcall = pcall
253 | global.debug = debug
254 |
255 | --Enable the require function, but only for the sandboxed environment
256 | global.require = function( filename )
257 | --Only load from the AI folder
258 | local script = assert(loadfile( 'AI.UserAI.'..name..'.'..filename ))
259 | setfenv( script, global )
260 | script()
261 | end
262 |
263 | --Auxiliary includes
264 | global.DeepPrintTable = DeepPrintTable
265 | global.Timers = Timers
266 | global.Vector = Vector
267 | global.Dynamic_Wrap = Dynamic_Wrap
268 | global.Warning = Warning
269 | global.AIUnitTests = AIUnitTests
270 | global.class = class
271 |
272 | --Enable the LoadKeyValues function but set the AI directory as root
273 | global.LoadKeyValues = function( path )
274 | return LoadKeyValues( 'scripts/vscripts/AI/UserAI/'..name..'/'..path )
275 | end
276 |
277 | --Default Dota global functions
278 | global.GetItemCost = GetItemCost
279 | global.RandomFloat = RandomFloat
280 | global.RandomInt = RandomInt
281 | global.RandomVector = RandomVector
282 | global.RotateOrientation = RotateOrientation
283 | global.RotatePosition = RotatePosition
284 | global.RotateQuaternionByAxisAngle = RotateQuaternionByAxisAngle
285 | global.RotationDelta = RotationDelta
286 |
287 | --Overriden Dota global functions
288 | function global.AI_FindUnitsInRadius( ... ) return wrapper:AI_FindUnitsInRadius( ... ) end
289 | function global.AI_EntIndexToHScript( ... ) return wrapper:AI_EntIndexToHScript( ... ) end
290 | function global.AI_MinimapEvent( ... ) return wrapper:AI_MinimapEvent( ... ) end
291 | function global.AI_ExecuteOrderFromTable( ... ) return wrapper:AI_ExecuteOrderFromTable( ... ) end
292 | function global.AI_Say( ... ) return wrapper:AI_Say( ... ) end
293 | function global.AI_BuyItem( ... ) return wrapper:AI_BuyItem( ... ) end
294 | function global.AI_GetGameTime( ... ) return wrapper:AI_GetGameTime( ... ) end
295 | function global.AI_Log( ... ) return wrapper:AI_Log( ... ) end
296 | function global.DebugDrawBox( ... ) return wrapper:DebugDrawBox( ... ) end
297 | function global.DebugDrawCircle( ... ) return wrapper:DebugDrawCircle( ... ) end
298 | function global.DebugDrawSphere( ... ) return wrapper:DebugDrawSphere( ... ) end
299 | function global.DebugDrawLine( ... ) return wrapper:DebugDrawLine( ... ) end
300 | function global.DebugDrawText( ... ) return wrapper:DebugDrawText( ... ) end
301 |
302 | --Copy over constants
303 | for k, v in pairs( _G ) do
304 | if type( v ) == 'string' or type( v ) == 'number' then
305 | global[k] = v
306 | end
307 | end
308 |
309 | return global
310 | end
--------------------------------------------------------------------------------
/Game/scripts/vscripts/AI/AIPlayerResource.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | AIPlayerResource
3 |
4 | This file contains a facade for PlayerResource which is exposed to AI. This limits
5 | the functionality the AI has access to.
6 |
7 | Code: Perry
8 | Date: October, 2015
9 | ]]
10 |
11 | AIPlayerResource = class({})
12 |
13 | function AIPlayerResource:constructor( team )
14 | self.team = team
15 | end
16 |
17 | --[[[
18 | @func AIPlayerResource:GetConnectionState( playerID )
19 | @desc Get connection state of a player.
20 |
21 | @modification -
22 | @param {integer} playerID The ID of the player.
23 | ]]
24 | function AIPlayerResource:GetConnectionState( playerID )
25 | return PlayerResource:GetConnectionState( playerID )
26 | end
27 |
28 | --[[[
29 | @func AIPlayerResource:GetPlayerLoadedCompletely( playerID )
30 | @desc Check if a player has loaded completely or not.
31 |
32 | @modification -
33 | @param {integer} playerID The ID of the player.
34 | ]]
35 | function AIPlayerResource:GetPlayerLoadedCompletely( playerID )
36 | return PlayerResource:GetPlayerLoadedCompletely( playerID )
37 | end
38 |
39 | --[[[
40 | @func AIPlayerResource:GetTeam( playerID )
41 | @desc Get the team of a player.
42 |
43 | @modification -
44 | @param {integer} playerID The ID of the player.
45 | ]]
46 | function AIPlayerResource:GetTeam( playerID )
47 | return PlayerResource:GetTeam( playerID )
48 | end
49 |
50 | --[[[
51 | @func AIPlayerResource:GetSteamAccountID( playerID )
52 | @desc Get the steam ID of a player.
53 |
54 | @modification -
55 | @param {integer} playerID The ID of the player.
56 | ]]
57 | function AIPlayerResource:GetSteamAccountID( playerID )
58 | return PlayerResource:GetSteamAccountID( playerID )
59 | end
60 |
61 | --[[[
62 | @func AIPlayerResource:GetPlayerName( playerID )
63 | @desc Get the name of a player.
64 |
65 | @modification -
66 | @param {integer} playerID The ID of the player.
67 | ]]
68 | function AIPlayerResource:GetPlayerName( playerID )
69 | return PlayerResource:GetPlayerName( playerID )
70 | end
71 |
72 | --[[[
73 | @func AIPlayerResource:GetKills( playerID )
74 | @desc Get the number of kills a player has.
75 |
76 | @modification -
77 | @param {integer} playerID The ID of the player.
78 | ]]
79 | function AIPlayerResource:GetKills( playerID )
80 | return PlayerResource:GetKills( playerID )
81 | end
82 |
83 | --[[[
84 | @func AIPlayerResource:GetAssists( playerID )
85 | @desc Get the number of assists a player has.
86 |
87 | @modification -
88 | @param {integer} playerID The ID of the player.
89 | ]]
90 | function AIPlayerResource:GetAssists( playerID )
91 | return PlayerResource:GetAssists( playerID )
92 | end
93 |
94 | --[[[
95 | @func AIPlayerResource:GetDeaths( playerID )
96 | @desc Get the number of deaths a player has.
97 |
98 | @modification -
99 | @param {integer} playerID The ID of the player.
100 | ]]
101 | function AIPlayerResource:GetDeaths( playerID )
102 | return PlayerResource:GetDeaths( playerID )
103 | end
104 |
105 | --[[[
106 | @func AIPlayerResource:GetLevel( playerID )
107 | @desc Get level of a player.
108 |
109 | @modification -
110 | @param {integer} playerID The ID of the player.
111 | ]]
112 | function AIPlayerResource:GetLevel( playerID )
113 | return PlayerResource:GetLevel( playerID )
114 | end
115 |
116 | --[[[
117 | @func AIPlayerResource:GetGold( playerID )
118 | @desc Get the amount of gold a player has.
119 |
120 | @modification Only works for players on the AI's team.
121 | @param {integer} playerID The ID of the player.
122 | ]]
123 | function AIPlayerResource:GetGold( playerID )
124 | if PlayerResource:GetTeam( playerID ) == self.team then
125 | return PlayerResource:GetGold( playerID )
126 | else
127 | return 0
128 | end
129 | end
130 |
131 | --[[[
132 | @func AIPlayerResource:GetReliableGold( playerID )
133 | @desc Get the amount of reliable gold a player has.
134 |
135 | @modification Only works for players on the AI's team.
136 | @param {integer} playerID The ID of the player.
137 | ]]
138 | function AIPlayerResource:GetReliableGold( playerID )
139 | if PlayerResource:GetTeam( playerID ) == self.team then
140 | return PlayerResource:GetReliableGold( playerID )
141 | else
142 | return 0
143 | end
144 | end
145 |
146 | --[[[
147 | @func AIPlayerResource:GetUnreliableGold( playerID )
148 | @desc Get the amount of unreliable gold a player has.
149 |
150 | @modification Only works for players on the AI's team.
151 | @param {integer} playerID The ID of the player.
152 | ]]
153 | function AIPlayerResource:GetUnreliableGold( playerID )
154 | if PlayerResource:GetTeam( playerID ) == self.team then
155 | return PlayerResource:GetUnreliableGold( playerID )
156 | else
157 | return 0
158 | end
159 | end
160 |
161 | --[[[
162 | @func AIPlayerResource:GetLastHits( playerID )
163 | @desc Get the number of last hits a player has.
164 |
165 | @modification Only works for players on the AI's team.
166 | @param {integer} playerID The ID of the player.
167 | ]]
168 | function AIPlayerResource:GetLastHits( playerID )
169 | if PlayerResource:GetTeam( playerID ) == self.team then
170 | return PlayerResource:GetLastHits( playerID )
171 | else
172 | return 0
173 | end
174 | end
175 |
176 | --[[[
177 | @func AIPlayerResource:GetDenies( playerID )
178 | @desc Get the number of denies a player has.
179 |
180 | @modification Only works for players on the AI's team.
181 | @param {integer} playerID The ID of the player.
182 | ]]
183 | function AIPlayerResource:GetDenies( playerID )
184 | if PlayerResource:GetTeam( playerID ) == self.team then
185 | return PlayerResource:GetDenies( playerID )
186 | else
187 | return 0
188 | end
189 | end
190 |
191 | --Team functions
192 | --==============================================================================
193 | --[[[
194 | @func AIPlayerResource:GetTeamKills( team )
195 | @desc Get the amount of kills for a team.
196 |
197 | @modification -
198 | @param {integer} team The number of the team.
199 | ]]
200 | function AIPlayerResource:GetTeamKills( team )
201 | return PlayerResource:GetTeamKills( team )
202 | end
203 |
204 | --[[[
205 | @func AIPlayerResource:GetNumCouriersForTeam( team )
206 | @desc Get the amount of couriers for a team.
207 |
208 | @modification Only works for own team.
209 | @param {integer} team The number of the team.
210 | ]]
211 | function AIPlayerResource:GetNumCouriersForTeam( team )
212 | if team == self.team then
213 | return PlayerResource:GetNumCouriersForTeam( team )
214 | else
215 | Warning( string.format( 'AI %i tried to get amount of couriers for team %i.', self.team, team ) )
216 | end
217 | end
218 |
219 | --[[[
220 | @func AIPlayerResource:GetNthCourierForTeam( courierIndex, team )
221 | @desc Get the n-th courier for a team.
222 |
223 | @modification Only works for own team.
224 | Parameters: -
225 | ]]
226 | function AIPlayerResource:GetNthCourierForTeam( courierIndex, team )
227 | if team == self.team then
228 | return PlayerResource:GetNthCourierForTeam( courierIndex, team )
229 | else
230 | Warning( string.format( 'AI %i tried to get courier from team %i.', self.team, team ) )
231 | end
232 | end
233 |
234 | --[[[
235 | @func AIPlayerResource:GetNthPlayerIDOnTeam( team, n )
236 | @desc Get the n-th player on a team.
237 |
238 | @modification -
239 | @param {integer} team The number of the team.
240 | @param {integer} n The index of the courier to get.
241 | ]]
242 | function AIPlayerResource:GetNthPlayerIDOnTeam( team, n )
243 | return PlayerResource:GetNthPlayerIDOnTeam( team, n )
244 | end
245 |
246 | --[[[
247 | @func AIPlayerResource:GetPlayerCount()
248 | @desc Get the playercount including spectators.
249 |
250 | @modification -
251 | ]]
252 | function AIPlayerResource:GetPlayerCount()
253 | return PlayerResource:GetPlayerCount()
254 | end
255 |
256 | --[[[
257 | @func AIPlayerResource:GetPlayerForTeam( team )
258 | @desc Get the playercount for a team.
259 |
260 | @modification -
261 | @param {integer} team The number of the team.
262 | ]]
263 | function AIPlayerResource:GetPlayerCountForTeam( team )
264 | return PlayerResource:GetPlayerCountForTeam( team )
265 | end
266 |
267 | --[[[
268 | @func AIPlayerResource:GetTeamPlayerCount( team )
269 | @desc Get the playercount (not abandonned) for a valid team.
270 |
271 | @modification -
272 | @param {integer} team The number of the team.
273 | ]]
274 | function AIPlayerResource:GetTeamPlayerCount( team )
275 | return PlayerResource:GetTeamPlayerCount( team )
276 | end
--------------------------------------------------------------------------------
/Game/scripts/vscripts/AI/AIUnitTests.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | AI Unit tests
3 | Mainly just to see if all AI functionality is available.
4 |
5 | Code: Perry
6 | Date: October, 2015
7 | ]]
8 | AIUnitTests = class({})
9 |
10 | function AIUnitTests:Run( global, unit, ability, aiPlayerRes )
11 |
12 | local success = 0
13 | local fail = 0
14 |
15 | local globalF = {
16 | 'AI_FindUnitsInRadius',
17 | 'AI_MinimapEvent',
18 | 'AI_EntIndexToHScript',
19 | 'AI_Say',
20 | 'AI_GetGameTime',
21 | 'AI_Log',
22 | 'GetItemCost',
23 | 'LoadKeyValues',
24 | 'RandomFloat',
25 | 'RandomInt',
26 | 'RandomVector',
27 | 'RotateOrientation',
28 | 'RotatePosition',
29 | 'RotateQuaternionByAxisAngle',
30 | 'RotationDelta'
31 | }
32 |
33 | --Validate global
34 | for _,func in ipairs( globalF ) do
35 | if global[func] == nil then
36 | fail = fail + 1
37 | Warning('Global function '..func..' not set in AI!')
38 | else
39 | success = success + 1
40 | end
41 | end
42 |
43 | local unitF = {
44 | 'GetAbsOrigin',
45 | 'GetHealth',
46 | 'GetMaxHealth',
47 | 'GetModelName',
48 | 'GetOrigin',
49 | 'GetOwner',
50 | 'GetOwnerEntity',
51 | 'GetTeam',
52 | 'GetTeamNumber',
53 | 'IsAlive',
54 | 'IsInVision',
55 | 'HasBuyback',
56 | 'GetAbilityByIndex',
57 | 'FindAbilityByName',
58 | 'GetClassname',
59 | 'GetEntityHandle',
60 | 'GetEntityIndex',
61 | 'GetName',
62 | 'GetPlayerOwnerID',
63 | 'GetItemInSlot'
64 | }
65 |
66 | --Validate unit
67 | for _,func in ipairs( unitF ) do
68 | if unit[func] == nil then
69 | fail = fail + 1
70 | Warning('Unit function '..func..' not set in AI!')
71 | else
72 | success = success + 1
73 | end
74 | end
75 |
76 | local abilityF = {
77 | 'GetAbilityDamage',
78 | 'GetAbilityDamageType',
79 | 'GetAbilityTargetFlags',
80 | 'GetAbilityTargetTeam',
81 | 'GetAbilityTargetType',
82 | 'GetAutoCastState',
83 | 'GetBackswingTime',
84 | 'GetCastPoint',
85 | 'GetCooldownTime',
86 | 'GetCooldownTimeRemaining',
87 | 'GetAbilityType',
88 | 'GetAbilityIndex',
89 | 'GetChannelledManaCostPerSecond',
90 | 'GetCooldown',
91 | 'GetClassname',
92 | 'GetEntityHandle',
93 | 'GetEntityIndex',
94 | 'GetName',
95 | 'GetAbilityName'
96 | }
97 |
98 | --Validate unit
99 | for _,func in ipairs( abilityF ) do
100 | if ability[func] == nil then
101 | fail = fail + 1
102 | Warning('Ability function '..func..' not set in AI!')
103 | else
104 | success = success + 1
105 | end
106 | end
107 |
108 | local playerResF = {
109 | 'GetAssists',
110 | 'GetConnectionState',
111 | 'GetDeaths',
112 | 'GetGold',
113 | 'GetKills',
114 | 'GetLastHits',
115 | 'GetDenies',
116 | 'GetLevel',
117 | 'GetNthCourierForTeam',
118 | 'GetNumCouriersForTeam',
119 | 'GetNthPlayerIDOnTeam',
120 | 'GetPlayerCount',
121 | 'GetPlayerCountForTeam',
122 | 'GetPlayerLoadedCompletely',
123 | 'GetPlayerName',
124 | 'GetReliableGold',
125 | 'GetSteamAccountID',
126 | 'GetTeam',
127 | 'GetTeamKills',
128 | 'GetTeamPlayerCount',
129 | 'GetUnreliableGold'
130 | }
131 |
132 | --Validate AIPlayerResource
133 | for _,func in ipairs( playerResF ) do
134 | if aiPlayerRes[func] == nil then
135 | fail = fail + 1
136 | Warning('AIPlayerResource function '..func..' not set in AI!')
137 | else
138 | success = success + 1
139 | end
140 | end
141 |
142 | if fail == 0 then
143 | print( string.format('AI Unit test: (%i/%i) Success!', success, success ) )
144 | else
145 | Warning( string.format('AI Unit test: (%i/%i) Succeeded.\n', success, success + fail ) )
146 | end
147 |
148 | AIEvents:UnitTest()
149 | end
--------------------------------------------------------------------------------
/Game/scripts/vscripts/AI/AIWrapper.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | AI Wrapper.
3 |
4 | This file contains the wrapper for dota 2 AI. The main purpose is to hide any normal
5 | server-side functionality and provide the AI with a subset of that. Delegates wrapping
6 | units and abilities to UnitWrapper and AbilityWrapper respectively.
7 |
8 | Code: Perry
9 | Date: October, 2015
10 | ]]
11 |
12 |
13 | LinkLuaModifier( 'modifier_dummy', 'LuaModifiers/modifier_dummy', LUA_MODIFIER_MOTION_NONE )
14 |
15 | --Class definition
16 | if AIWrapper == nil then
17 | AIWrapper = class({})
18 | end
19 |
20 | --Keep some colors to use for each team
21 | local teamColor = {
22 | [2] = { hex="#3375FF", vec=Vector(51,117,255) },
23 | [4] = { hex="#66FFBF", vec=Vector(102,255,191) },
24 | [6] = { hex="#BF00BF", vec=Vector(191,0,191) },
25 | [8] = { hex="#F3F00B", vec=Vector(243,240,11) },
26 | [10] = { hex="#FF6B00", vec=Vector(255,107,0) },
27 | [3] = { hex="#FE86C2", vec=Vector(254,134,194) },
28 | [5] = { hex="#A1B447", vec=Vector(161,180,71) },
29 | [7] = { hex="#65D9F7", vec=Vector(101,217,247) },
30 | [9] = { hex="#008321", vec=Vector(0,131,33) },
31 | [11] = { hex="#A46900", vec=Vector(164,105,0) }
32 | }
33 |
34 | --Constructor
35 | function AIWrapper:constructor( team )
36 | self.team = team
37 | end
38 |
39 | --=======================================================================================================================
40 | --AI-accessible functions
41 | --=======================================================================================================================
42 |
43 | --[[[
44 | @func AI_FindUnitsInRadius( position, radius, teamFilter, typeFilter, flagFilter, order, canGrowCache )
45 | @desc Finds units in a radius with some parameters.
46 |
47 | @modification Can only find units visible by the AI's team.
48 | @param {Vector} Position The center of the circle to search in
49 | @param {integer} Radius The radius to search in
50 | @param {integer} TeamFilter DOTA_UNIT_TARGET_TEAM_* filter.
51 | @param {integer} TypeFilter DOTA_UNIT_TARGET_TYPE_* filter.
52 | @param {integer} FlagFilter DOTA_UNIT_TARGET_FLAG_* filter.
53 | @param {integer} Order The order to return results in.
54 | @param {boolean} CanGrowCache Can the search grow the cache.
55 | ]]
56 | function AIWrapper:AI_FindUnitsInRadius( position, radius, teamFilter, typeFilter, flagFilter, order, canGrowCache )
57 |
58 | --Add DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE to the flagFilter
59 | flagFilter = bit.bor( flagFilter, DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE )
60 |
61 | --Do the search
62 | local result = FindUnitsInRadius( self.team, position, cacheUnit, radius, teamFilter, typeFilter, flagFilter, order, canGrowCache )
63 |
64 | --Wrap result units
65 | for k, unit in pairs( result ) do
66 | result[k] = WrapUnit( unit, self.team )
67 | end
68 |
69 | return result
70 | end
71 |
72 | --[[[
73 | @func AI_EntIndexToHScript( ent_index )
74 | @desc Return the entity by its entity index.
75 |
76 | @modification Returns wrapped units/abilities.
77 | @param {integer} ent_index The entity index of a unit or ability.
78 | ]]
79 | function AIWrapper:AI_EntIndexToHScript( ent_index )
80 | local entity = EntIndexToHScript( ent_index )
81 |
82 | if entity == nil then
83 | return nil
84 | else
85 | --Check if this is a unit or ability
86 | if entity.GetAbilityName == nil then
87 | --Unit
88 | return WrapUnit( entity, self.team )
89 | else
90 | --Ability
91 | return WrapAbility( entity )
92 | end
93 | end
94 | end
95 |
96 | --[[[
97 | @func AI_MinimapEvent( entity, xCoord, yCoord, eventType, eventDuration )
98 | @desc Fire an event on the minimap.
99 |
100 | @modification Removed team parameter, limited to entities in vision.
101 | @param {handle} entity Entity the event was fired on ( can be nil ).
102 | @param {float} xCoord The x-coordinate of the event.
103 | @param {float} yCoord The y-coordinate of the event.
104 | @param {integer} eventType The type of the event, DOTA_MINIMAP_EVENT_*.
105 | @param {float} eventDuration The duration of the minimap event.
106 | ]]
107 | function AIWrapper:AI_MinimapEvent( entity, xCoord, yCoord, eventType, eventDuration )
108 | --Check if the unit is in vision
109 | if InVision( entity, self.team ) then
110 | MinimapEvent( self.team, entity, xCoord, yCoord, eventType, eventDuration )
111 | else
112 | --ILL EAGLE
113 | Warning( string.format( 'AI %i tried to fire minimap event on entity in fog.\n', self.team ) )
114 | end
115 | end
116 |
117 | --[[[
118 | @func AI_ExecuteOrderFromTable( ent_index )
119 | @desc Execute an order from a table
120 |
121 | @modification Only works for units of the AI, and the target entity is not in fog.
122 | @param {table} table The order table, contains the following parameters:
123 | * UnitIndex - The entity index of the unit the order is given to.
124 | * OrderType - The type of unit given.
125 | * TargetIndex - (OPTIONAL) The entity index of the target unit.
126 | * AbilityIndex - (OPTIONAL) The entity index of the target unit.
127 | * Position - (OPTIONAL) The (vector) position of the order.
128 | * Queue - (OPTIONAL) Queue the order or not (boolean).
129 | ]]
130 | function AIWrapper:AI_ExecuteOrderFromTable( table )
131 | --Verify if the unit belongs to the AI
132 | local unit = EntIndexToHScript( table.UnitIndex )
133 | if unit:GetTeamNumber() ~= self.team then
134 | Warning( string.format( 'AI %i tried to execute order on illegal entity.\n', self.team ) )
135 | return
136 | end
137 |
138 | --[[Verity the target is not in fog if it is set
139 | if table.TargetIndex ~= nil and InVision( EntIndexToHScript( table.TargetIndex ), self.team ) == false then
140 | Warning( string.format( 'AI %i tried to execute order with illegal target.', self.team ) )
141 | return
142 | end]]
143 |
144 | --Execute order
145 | ExecuteOrderFromTable( table )
146 | end
147 |
148 | --[[[
149 | @func AI_Say( player, message, teamOnly )
150 | @desc Make a player say something in chat.
151 |
152 | @modification Only works for players owned by the AI, uses player ID instead of player.
153 | @param {integer} playerID The id of the player doing the talking.
154 | @param {string} message The message.
155 | @param {boolean} teamOnly Is the message in team chat or not (boolean).
156 | ]]
157 | function AIWrapper:AI_Say( playerID, message, teamOnly )
158 | local player = PlayerResource:GetPlayer( playerID )
159 | local team = PlayerResource:GetTeam( playerID )
160 |
161 | if team == self.team then
162 | Say( player, message, teamOnly )
163 | else
164 | Warning( string.format( 'AI %i tried to AI_Say for another team.\n', self.team ) )
165 | end
166 | end
167 |
168 | --[[[
169 | @func AI_BuyItem( unit, itemName )
170 | @desc Buy an item on a unit.
171 |
172 | @modification Does not exist in the original AI.
173 | @param {handle} unit The unit to buy the item on.
174 | @param {string} itemName The item to buy.
175 | ]]
176 | function AIWrapper:AI_BuyItem( unit, itemName )
177 | local orgUnit = EntIndexToHScript( unit:GetEntityIndex() )
178 | local cost = GetItemCost( itemName )
179 |
180 | -- Check if unit is in shop trigger
181 | local inTrigger = false
182 | local unitPos = orgUnit:GetAbsOrigin()
183 | for _, trigger in pairs( Entities:FindAllByClassname( 'trigger_shop' ) ) do
184 | local maxs = trigger:GetBoundingMaxs() + trigger:GetAbsOrigin()
185 | local mins = trigger:GetBoundingMins() + trigger:GetAbsOrigin()
186 |
187 | if unitPos.x >= mins.x and unitPos.y >= mins.y and unitPos.x <= maxs.x and unitPos.y <= maxs.y then
188 | inTrigger = true
189 | break
190 | end
191 | end
192 | print(inTrigger)
193 |
194 | if inTrigger then
195 | local hasSpace = false
196 | for i=0,11 do
197 | local item = orgUnit:GetItemInSlot( i )
198 | if item == nil then
199 | hasSpace = true
200 | break
201 | end
202 | end
203 |
204 | if orgUnit:GetGold() >= cost and hasSpace then
205 | orgUnit:ModifyGold( -cost, false, DOTA_ModifyGold_PurchaseItem )
206 | local item = CreateItem( itemName, orgUnit, orgUnit )
207 | orgUnit:AddItem( item )
208 | end
209 | end
210 | end
211 |
212 | --[[[
213 | @func AI_GetGameTime()
214 | @desc Get the current game time.
215 |
216 | @modification -
217 | ]]
218 | function AIWrapper:AI_GetGameTime()
219 | return GameRules:GetGameTime()
220 | end
221 |
222 | --[[[
223 | @func AI_Log( message )
224 | @desc Log functionality for AI.
225 |
226 | @modification -
227 | @param {string} message The message to log
228 | ]]
229 | function AIWrapper:AI_Log( message )
230 | local time = GameRules:GetGameTime()
231 | if AIManager.AllowInGameLogging then
232 | GameRules:SendCustomMessage( string.format( '[AI %i | %.2f]: %s', teamColor[ self.team ].hex, self.team, time, message ), -1, 0 )
233 | end
234 | print( string.format( '[AI %i | %.2f]: %s', self.team, time, message ) )
235 | end
236 |
237 | --[[[
238 | @func AI_DebugDrawBox( origin, min, max, r, g, b, a, duration )
239 | @desc Draw a box.
240 |
241 | @modification Only works if AIManager.AllowDebugDrawing. Forces a certain color if AIManager.ForceDrawColor.
242 | @param {Vector} origin The origin vector.
243 | @param {Vector} min A vector relative to the origin with the minimal coordinate corner.
244 | @param {Vector} max A vector relative to the origin with the maximal coordinate corner.
245 | @param {integer} r The red component in the RGBA color (0-255).
246 | @param {integer} g The green component in the RGBA color (0-255).
247 | @param {integer} b The blue component in the RGBA color (0-255).
248 | @param {integer} a The alpha component in the RGBA color (0-255).
249 | @param {float} duration The duration of the drawing in seconds.
250 | ]]
251 | function AIWrapper:DebugDrawBox( origin, min, max, r, g, b, a, duration )
252 | if AIManager.AllowDebugDrawing then
253 | if AIManager.ForceDrawColor then
254 | r = teamColor[ self.team ].vec.x
255 | g = teamColor[ self.team ].vec.y
256 | b = teamColor[ self.team ].vec.z
257 | end
258 | DebugDrawBox( origin, min, max, r, g, b, a, math.min( duration, 5 ) )
259 | end
260 | end
261 |
262 | --[[[
263 | @func AI_DebugDrawCircle( center, vRgb, a, rad, ztest, duration )
264 | @desc Draw a circle.
265 |
266 | @modification Only works if AIManager.AllowDebugDrawing. Forces a certain color if AIManager.ForceDrawColor.
267 | @param {Vector} center The center of the circle
268 | @param {Vector} vRgba The color vector for the circle
269 | @param {integer} a The alpha channel of the color.
270 | @param {integer} rad The radius of the circle.
271 | @param {boolean} ztest Disable ztest?
272 | @param {float} duration The duration of the drawing
273 | ]]
274 | function AIWrapper:DebugDrawCircle( center, vRgb, a, rad, ztest, duration )
275 | if AIManager.AllowDebugDrawing then
276 | if AIManager.ForceDrawColor then
277 | vRgb = teamColor[ self.team ].vec
278 | end
279 | DebugDrawCircle( center, vRgb, a, rad, ztest, math.min( duration, 5 ) )
280 | end
281 | end
282 |
283 | --[[[
284 | @func AI_DebugDrawSphere( center, vRgb, a, rad, ztest, duration )
285 | @desc Draw a sphere.
286 |
287 | @modification Only works if AIManager.AllowDebugDrawing. Forces a certain color if AIManager.ForceDrawColor.
288 | @param {Vector} center The center of the circle
289 | @param {Vector} vRgba The color vector for the circle
290 | @param {integer} a The alpha channel of the color.
291 | @param {integer} rad The radius of the circle.
292 | @param {boolean} ztest Disable ztest?
293 | @param {float} duration The duration of the drawing
294 | ]]
295 | function AIWrapper:DebugDrawSphere( center, vRgb, a, rad, ztest, duration )
296 | if AIManager.AllowDebugDrawing then
297 | if AIManager.ForceDrawColor then
298 | vRgb = teamColor[ self.team ].vec
299 | end
300 | DebugDrawSphere( center, vRgb, a, rad, ztest, math.min( duration, 5 ) )
301 | end
302 | end
303 |
304 | --[[[
305 | @func AI_DebugDrawLine( origin, target, r, g, b, ztest, duration )
306 | @desc Draw a line.
307 |
308 | @modification Only works if AIManager.AllowDebugDrawing. Forces a certain color if AIManager.ForceDrawColor.
309 | @param {Vector} origin The origin vector (from).
310 | @param {Vector} target The target vector (to).
311 | @param {integer} r The red component in the RGBA color (0-255).
312 | @param {integer} g The green component in the RGBA color (0-255).
313 | @param {integer} b The blue component in the RGBA color (0-255).
314 | @param {boolean} ztest Disable ztest?
315 | @param {float} duration The duration of the drawing in seconds.
316 | ]]
317 | function AIWrapper:DebugDrawLine( origin, target, r, g, b, ztest, duration )
318 | if AIManager.AllowDebugDrawing then
319 | if AIManager.ForceDrawColor then
320 | r = teamColor[ self.team ].vec.x
321 | g = teamColor[ self.team ].vec.y
322 | b = teamColor[ self.team ].vec.z
323 | end
324 | DebugDrawLine( origin, target, r, g, b, ztest, math.min( duration, 5 ) )
325 | end
326 | end
327 |
328 | --[[[
329 | @func AI_DebugDrawText( origin, text, viewCheck, duration )
330 | @desc Draw text.
331 |
332 | @modification Only works if AIManager.AllowDebugDrawing. Forces a certain color if AIManager.ForceDrawColor.
333 | @param {Vector} origin The origin vector (from).
334 | @param {string} text The text to display.
335 | @param {boolean} viewCheck Is the text aligned with the viewport?
336 | @param {float} duration The duration of the drawing in seconds.
337 | ]]
338 | function AIWrapper:DebugDrawText( origin, text, viewCheck, duration )
339 | if AIManager.AllowDebugDrawing then
340 | DebugDrawText( origin, text, viewCheck, math.min( duration, 5 ) )
341 | end
342 | end
343 |
--------------------------------------------------------------------------------
/Game/scripts/vscripts/AI/AbilityWrapper.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | Ability Wrapper.
3 |
4 | This file contains the wrapper for dota 2 ability entities. A facade is created to
5 | provide only certain ability functionality to AI.
6 |
7 | Code: Perry
8 | Date: October, 2015
9 | ]]
10 |
11 | --Wrap an ability for a set team
12 | function WrapAbility( ability, team )
13 | --If the ability is nil just return
14 | if ability == nil then return nil end
15 |
16 | --Check if we wrapped already
17 | if ability.wrapped ~= nil then
18 | return ability.wrapped
19 | end
20 |
21 | --Create facade ability
22 | local a = {}
23 |
24 | --Add functionality to the ability
25 | AbilitySetup( a, ability, team )
26 |
27 | --Store the wrapped ability
28 | ability.wrapped = a
29 |
30 | --Return the wrapped ability
31 | return a
32 | end
33 |
34 | local function OwnedByAlly( ability, team )
35 | return ability:GetCaster():GetTeamNumber() == team
36 | end
37 |
38 | --====================================================================================================
39 | -- Define Ability functionality
40 | --====================================================================================================
41 | function AbilitySetup( ability, globalAbility, team )
42 | --[[[
43 | @func ability:GetCooldown( level )
44 | @desc Get the cooldown for a certain level of the ability.
45 |
46 | @modification -
47 | @param {integer} Level The level to query the cooldown for.
48 | ]]
49 | function ability:GetCooldown( level )
50 | return globalAbility:GetCooldown( level )
51 | end
52 |
53 | --[[[
54 | @func ability:GetCooldownTime()
55 | @desc Get the cooldown time for the current level of the ability.
56 |
57 | @modification Only returns times for abilities of teammates, 0 otherwise.
58 | ]]
59 | function ability:GetCooldownTime()
60 | if OwnedByAlly( globalAbility, team ) then
61 | return globalAbility:GetCooldownTime()
62 | else
63 | return 0
64 | end
65 | end
66 |
67 | --[[[
68 | @func ability:GetCooldownTimeRemaining()
69 | @desc Get the remaining cooldown time for the ability.
70 |
71 | @modification Only returns times for abilities of teammates, 0 otherwise.
72 | ]]
73 | function ability:GetCooldownTimeRemaining()
74 | if OwnedByAlly( globalAbility, team ) then
75 | return globalAbility:GetCooldownTimeRemaining()
76 | else
77 | return 0
78 | end
79 | end
80 |
81 | --[[[
82 | @func ability:GetAbilityName()
83 | @desc Get the name of the ability.
84 |
85 | @modification -
86 | ]]
87 | function ability:GetAbilityName()
88 | return globalAbility:GetAbilityName()
89 | end
90 |
91 | --[[[
92 | @func ability:GetAbilityType()
93 | @desc Get the type of the ability.
94 |
95 | @modification -
96 | ]]
97 | function ability:GetAbilityType()
98 | return globalAbility:GetAbilityType()
99 | end
100 |
101 | --[[[
102 | @func ability:GetAbilityDamage()
103 | @desc Get the damage an ability does.
104 |
105 | @modification -
106 | ]]
107 | function ability:GetAbilityDamage()
108 | return globalAbility:GetAbilityDamage()
109 | end
110 |
111 | --[[[
112 | @func ability:GetAbilityDamageType()
113 | @desc Get the type of damage an ability does.
114 |
115 | @modification -
116 | ]]
117 | function ability:GetAbilityDamageType()
118 | return globalAbility:GetAbilityDamageType()
119 | end
120 |
121 | --[[[
122 | @func ability:GetAbilityTargetFlags()
123 | @desc Get the target flags of an ability.
124 |
125 | @modification -
126 | ]]
127 | function ability:GetAbilityTargetFlags()
128 | return globalAbility:GetAbilityTargetFlags()
129 | end
130 |
131 | --[[[
132 | @func ability:GetAbilityTargetTeam()
133 | @desc Get the target team of an ability.
134 |
135 | @modification -
136 | ]]
137 | function ability:GetAbilityTargetTeam()
138 | return globalAbility:GetAbilityTargetTeam()
139 | end
140 |
141 | --[[[
142 | @func ability:GetAbilityTargetType()
143 | @desc Get the target type of an ability.
144 |
145 | @modification -
146 | ]]
147 | function ability:GetAbilityTargetType()
148 | return globalAbility:GetAbilityTargetType()
149 | end
150 |
151 | --[[[
152 | @func ability:GetAutoCastState()
153 | @desc Get the auto-cast state (enabled/disabled) of the ability.
154 |
155 | @modification Only works for allied units, false otherwise.
156 | ]]
157 | function ability:GetAutoCastState()
158 | if OwnedByAlly( ability, team ) then
159 | return globalAbility:GetAutoCastState()
160 | else
161 | return false
162 | end
163 | end
164 |
165 | --[[[
166 | @func ability:GetBackswingTime()
167 | @desc Get the backswing time of an ability.
168 |
169 | @modification -
170 | ]]
171 | function ability:GetBackswingTime()
172 | return globalAbility:GetBackswingTime()
173 | end
174 |
175 | --[[[
176 | @func ability:GetCastPoint()
177 | @desc Get the cast point of an ability.
178 |
179 | @modification -
180 | ]]
181 | function ability:GetCastPoint()
182 | return globalAbility:GetCastPoint()
183 | end
184 |
185 | --[[[
186 | @func ability:GetAbilityIndex()
187 | @desc Get the index of the ability on the unit.
188 |
189 | @modification Only works in vision
190 | ]]
191 | function ability:GetAbilityIndex()
192 | if InVision( globalAbility:GetCaster(), team ) then
193 | return globalAbility:GetAbilityIndex()
194 | else
195 | return 0
196 | end
197 | end
198 |
199 | --[[[
200 | @func ability:GetChannelledManaCostPerSecond( level )
201 | @desc Get manacost per second during channel for some level of the ability.
202 |
203 | @modification -
204 | ]]
205 | function ability:GetChannelledManaCostPerSecond( level )
206 | return globalAbility:GetChannelledManaCostPerSecond( level )
207 | end
208 |
209 | --[[[
210 | @func ability:IsItem()
211 | @desc Return if the ability is an item or not.
212 |
213 | @modification -
214 | ]]
215 | function ability:IsItem()
216 | return globalAbility:IsItem()
217 | end
218 |
219 | --[[[
220 | @func ability:GetCurrentCharges()
221 | @desc Get the current amount of charges on an item.
222 |
223 | @modification Only works if the owner is in vision.
224 | ]]
225 | function ability:GetCurrentCharges()
226 | if InVision( globalAbility:GetCaster(), team ) then
227 | return globalAbility:GetCurrentCharges()
228 | else
229 | return 0
230 | end
231 | end
232 |
233 | --Entity functions
234 | --==========================================================================
235 | --[[[
236 | @func entity:GetClassname()
237 | @desc Get the classname of the entity.
238 |
239 | @modification -
240 | ]]
241 | function ability:GetClassname()
242 | return globalAbility:GetClassname()
243 | end
244 |
245 | --[[[
246 | @func entity:GetEntityHandle()
247 | @desc Get the entity handle of the entity.
248 |
249 | @modification -
250 | ]]
251 | function ability:GetEntityHandle()
252 | return globalAbility:GetEntityHandle()
253 | end
254 |
255 | --[[[
256 | @func entity:GetEntityIndex()
257 | @desc Get the entity index of the entity.
258 |
259 | @modification -
260 | ]]
261 | function ability:GetEntityIndex()
262 | return globalAbility:GetEntityIndex()
263 | end
264 |
265 | --[[[
266 | @func entity:GetName()
267 | @desc Get the name of the entity.
268 |
269 | @modification -
270 | ]]
271 | function ability:GetName()
272 | return globalAbility:GetName()
273 | end
274 | end
--------------------------------------------------------------------------------
/Game/scripts/vscripts/AI/UnitWrapper.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | Unit Wrapper.
3 |
4 | This file contains the wrapper for dota 2 units. A facade is created to
5 | provide only certain unit functionality to AI.
6 |
7 | Code: Perry
8 | Date: October, 2015
9 | ]]
10 |
11 | --Determine if a unit is in a team's vision
12 | function InVision( unit, team )
13 | return AIManager.visionDummies[ team ]:CanEntityBeSeenByMyTeam( unit )
14 | end
15 |
16 | --Wrap a unit for a set team
17 | function WrapUnit( unit, team )
18 | --If the unit is nil just return
19 | if unit == nil then return nil end
20 |
21 | --Check if we wrapped already
22 | if unit.wrapped ~= nil then
23 | return unit.wrapped
24 | end
25 |
26 | --Create facade unit
27 | local u = {}
28 |
29 | --Add functionality to the unit
30 | UnitSetup( u, unit, team )
31 |
32 | --Store the wrapped unit
33 | unit.wrapped = u
34 |
35 | --Return the wrapped unit
36 | return u
37 | end
38 |
39 | --====================================================================================================
40 | -- Define Unit functionality
41 | --====================================================================================================
42 | function UnitSetup( unit, globalUnit, team )
43 |
44 | --[[[
45 | @func unit:IsInVision()
46 | @desc Return if the unit is in vision or not
47 |
48 | @modification Does not exist in regular API.
49 | ]]
50 | function unit:IsInVision()
51 | return InVision( globalUnit, team )
52 | end
53 |
54 | --[[[
55 | @func unit:HasBuyback()
56 | @desc Return if the unit has buyback or not. Only for allies.
57 |
58 | @modification Does not exist in regular API.
59 | ]]
60 | function unit:HasBuyback()
61 | if globalUnit:GetTeamNumber() == team then
62 | local gold = globalUnit:GetGold() - globalUnit:GetDeathGoldCost()
63 | local noScythe = globalUnit:IsBuybackDisabledByReapersScythe() == false
64 | return ( gold >= globalUnit:GetBuybackCost() and noScythe )
65 | else
66 | return false
67 | end
68 | end
69 |
70 | --[[[
71 | @func unit:GetAbsOrigin()
72 | @desc Get the position of the unit.
73 |
74 | @modification Return nil when unit is in fog of war.
75 | ]]
76 | function unit:GetAbsOrigin()
77 | if InVision( globalUnit, team ) then
78 | return globalUnit:GetAbsOrigin()
79 | else
80 | return nil
81 | end
82 | end
83 |
84 | --[[[
85 | @func unit:GetOrigin()
86 | @desc Get the position of the unit.
87 |
88 | @modification Return nil when unit is in fog of war.
89 | ]]
90 | function unit:GetOrigin()
91 | if InVision( globalUnit, team ) then
92 | return globalUnit:GetOrigin()
93 | else
94 | return nil
95 | end
96 | end
97 |
98 | --[[[
99 | @func unit:GetPlayerOwnerID()
100 | @desc Get the ID of the player owning this unit.
101 |
102 | @modification -
103 | ]]
104 | function unit:GetPlayerOwnerID()
105 | return globalUnit:GetPlayerOwnerID()
106 | end
107 |
108 | --[[[
109 | @func unit:GetHealth()
110 | @desc Get the health of the unit.
111 |
112 | @modification Return nil when unit is in fog of war.
113 | ]]
114 | function unit:GetHealth()
115 | if InVision( globalUnit, team ) then
116 | return globalUnit:GetHealth()
117 | else
118 | return nil
119 | end
120 | end
121 |
122 | --[[[
123 | @func unit:GetMaxHealth()
124 | @desc Get the maximum health of the unit.
125 |
126 | @modification Return nil when unit is in fog of war.
127 | ]]
128 | function unit:GetMaxHealth()
129 | if InVision( globalUnit, team ) then
130 | return globalUnit:GetMaxHealth()
131 | else
132 | return nil
133 | end
134 | end
135 |
136 | --[[[
137 | @func unit:GetModelName()
138 | @desc Get the model name of the unit.
139 |
140 | @modification Return nil when unit is in fog of war.
141 | ]]
142 | function unit:GetModelName()
143 | if InVision( globalUnit, team ) then
144 | return globalUnit:GetModelName()
145 | else
146 | return nil
147 | end
148 | end
149 |
150 | --[[[
151 | @func unit:GetOwner()
152 | @desc Get the owner of the unit.
153 |
154 | @modification Wrap result.
155 | ]]
156 | function unit:GetOwner()
157 | return WrapUnit( globalUnit:GetOwner(), team )
158 | end
159 |
160 | --[[[
161 | @func unit:GetOwnerEntity()
162 | @desc Get the owner of the unit.
163 |
164 | @modification Wrap result.
165 | ]]
166 | function unit:GetOwnerEntity()
167 | return WrapUnit( globalUnit:GetOwnerEntity(), team )
168 | end
169 |
170 | --[[[
171 | @func unit:GetTeam()
172 | @desc Get the team of the unit.
173 |
174 | @modification -
175 | ]]
176 | function unit:GetTeam()
177 | return globalUnit:GetTeam()
178 | end
179 |
180 | --[[[
181 | @func unit:GetTeamNumber()
182 | @desc Get the team of the unit.
183 |
184 | @modification None.
185 | ]]
186 | function unit:GetTeamNumber()
187 | return globalUnit:GetTeamNumber()
188 | end
189 |
190 | --[[[
191 | @func unit:IsAlive()
192 | @desc Return if the unit is alive or not.
193 |
194 | @modification -
195 | ]]
196 | function unit:IsAlive()
197 | if InVision( globalUnit, team ) then
198 | return globalUnit:IsAlive()
199 | else
200 | return false
201 | end
202 | end
203 |
204 | --[[[
205 | @func unit:GetAbilityByIndex( index )
206 | @desc Retrieve an ability by index from the unit.
207 |
208 | @modification Wrap the result, only works if unit is in vision, nil otherwise.
209 | @param {number} index The index of the ability on the unit.
210 | ]]
211 | function unit:GetAbilityByIndex( index )
212 | if InVision( globalUnit, team ) then
213 | return WrapAbility( globalUnit:GetAbilityByIndex( index ), team )
214 | else
215 | return nil
216 | end
217 | end
218 |
219 | --[[[
220 | @func unit:FindAbilityByName( name )
221 | @desc Retrieve an ability by index from the unit.
222 |
223 | @modification Only works if the unit is in vision and wraps result.
224 | @param {string} name The name of the ability to look up.
225 | ]]
226 | function unit:FindAbilityByName( name )
227 | if InVision( globalUnit, team ) then
228 | return WrapAbility( globalUnit:FindAbilityByName( name ), team )
229 | else
230 | return nil
231 | end
232 | end
233 |
234 | --[[[
235 | @func unit:GetItemInSlot( slot )
236 | @desc Retrieve an item by slot from the unit.
237 |
238 | @modification Only works if the unit is in vision and wraps the result.
239 | @param {number} slot The name of the ability to look up.
240 | ]]
241 | function unit:GetItemInSlot( slot )
242 | if InVision( globalUnit, team ) then
243 | return WrapAbility( globalUnit:GetItemInSlot( slot ), team )
244 | else
245 | return nil
246 | end
247 | end
248 |
249 | --[[[
250 | @func unit:GetAbilityPoints()
251 | @desc Retrieve the amount of ability points a hero has.
252 |
253 | @modification Only works for allies.
254 | ]]
255 | function unit:GetAbilityPoints()
256 | if globalUnit:GetTeamNumber() == team and globalUnit.GetAbilityPoints ~= nil then
257 | return globalUnit:GetAbilityPoints()
258 | else
259 | return 0
260 | end
261 | end
262 |
263 | --[[[
264 | @func unit:GetLevel()
265 | @desc Get the level of a unit.
266 |
267 | @modification Modification: Only works for units in vision.
268 | ]]
269 | function unit:GetLevel()
270 | if InVision( globalUnit, team ) then
271 | return globalUnit:GetLevel()
272 | else
273 | return 0
274 | end
275 | end
276 |
277 | --[[[
278 | @func unit:GetGold()
279 | @desc Get the gold of a unit.
280 |
281 | @modification Modification: Only works for allies.
282 | ]]
283 | function unit:GetGold()
284 | if globalUnit:GetTeamNumber() == team then
285 | return globalUnit:GetGold()
286 | else
287 | return 0
288 | end
289 | end
290 |
291 | --[[[
292 | @func unit:IsHero()
293 | @desc See if the hero is a unit or not.
294 |
295 | @modification Only works in vision.
296 | ]]
297 | function unit:IsHero()
298 | if InVision( globalUnit, team ) then
299 | return globalUnit:IsHero()
300 | else
301 | return false
302 | end
303 | end
304 |
305 | --[[[
306 | @func unit:IsTower()
307 | @desc See if the hero is a unit or not.
308 |
309 | @modification Only works in vision.
310 | ]]
311 | function unit:IsTower()
312 | if InVision( globalUnit, team ) then
313 | return globalUnit:IsTower()
314 | else
315 | return false
316 | end
317 | end
318 |
319 | --[[[
320 | @func unit:GetForwardVector()
321 | @desc Get the forward vector of the unit.
322 |
323 | @modification Only works in vision.
324 | ]]
325 | function unit:GetForwardVector()
326 | if InVision( globalUnit, team ) then
327 | return globalUnit:GetForwardVector()
328 | else
329 | return Vector( 1, 0, 0 )
330 | end
331 | end
332 |
333 | --[[[
334 | @func unit:GetAttackRange()
335 | @desc Get the unit's attack range.
336 |
337 | @modification: Only works in vision.
338 | ]]
339 | function unit:GetAttackRange()
340 | if InVision( globalUnit, team ) then
341 | return globalUnit:GetAttackRange()
342 | else
343 | return 0
344 | end
345 | end
346 |
347 | --[[[
348 | @func unit:GetAttackAnimationPoint()
349 | @desc Get the unit's attack animation point.
350 |
351 | @modification: Only works in vision.
352 | ]]
353 | function unit:GetAttackRange()
354 | if InVision( globalUnit, team ) then
355 | return globalUnit:GetAttackAnimationPoint()
356 | else
357 | return 0
358 | end
359 | end
360 |
361 | --[[[
362 | @func unit:GetAttackCapability()
363 | @desc Get the unit's attack capability.
364 |
365 | @modification: Only works in vision.
366 | ]]
367 | function unit:GetAttackCapability()
368 | if InVision( globalUnit, team ) then
369 | return globalUnit:GetAttackCapability()
370 | else
371 | return 0
372 | end
373 | end
374 |
375 | --[[[
376 | @func unit:GetAttackRange()
377 | @desc Get the unit's attack range.
378 |
379 | @modification: Only works in vision.
380 | ]]
381 | function unit:GetAttackRange()
382 | if InVision( globalUnit, team ) then
383 | return globalUnit:GetForwardVector()
384 | else
385 | return 0
386 | end
387 | end
388 |
389 | --[[[
390 | @func unit:GetAttackSpeed()
391 | @desc Get the unit's attack speed.
392 |
393 | @modification: Only works in vision.
394 | ]]
395 | function unit:GetAttackSpeed()
396 | if InVision( globalUnit, team ) then
397 | return globalUnit:GetAttackSpeed()
398 | else
399 | return 0
400 | end
401 | end
402 |
403 | --[[[
404 | @func unit:GetAttacksPerSecond()
405 | @desc Get the amount of attacks the unit does per second based on its attack speed.
406 |
407 | @modification: Only works in vision.
408 | ]]
409 | function unit:GetAttacksPerSecond()
410 | if InVision( globalUnit, team ) then
411 | return globalUnit:GetAttacksPerSecond()
412 | else
413 | return 0
414 | end
415 | end
416 |
417 | --[[[
418 | @func unit:GetIdealSpeed()
419 | @desc Get the unit's movespeed.
420 |
421 | @modification: Only works in vision.
422 | ]]
423 | function unit:GetIdealSpeed()
424 | if InVision( globalUnit, team ) then
425 | return globalUnit:GetIdealSpeed()
426 | else
427 | return 0
428 | end
429 | end
430 |
431 | --[[[
432 | @func unit:GetProjectileSpeed()
433 | @desc Get the unit's projectile speed.
434 |
435 | @modification: Only works in vision.
436 | ]]
437 | function unit:GetProjectileSpeed()
438 | if InVision( globalUnit, team ) then
439 | return globalUnit:GetProjectileSpeed()
440 | else
441 | return 0
442 | end
443 | end
444 |
445 | --[[[
446 | @func unit:GetAverageTrueDamage()
447 | @desc Get the average value of the unit's minimum and maximum damage values.
448 |
449 | @modification: Only works in vision.
450 | ]]
451 | function unit:GetAverageTrueDamage()
452 | if InVision( globalUnit, team ) then
453 | return globalUnit:GetForwardVector()
454 | else
455 | return 0
456 | end
457 | end
458 |
459 | --[[[
460 | @func unit:GetUnitName()
461 | @desc Get the name of the unit.
462 |
463 | @modification -
464 | ]]
465 | function unit:GetUnitName()
466 | return globalUnit:GetUnitName()
467 | end
468 |
469 | --Entity functions
470 | --==========================================================================
471 |
472 | --[[[
473 | @func entity:GetClassname()
474 | @desc Get the classname of the entity.
475 |
476 | @modification -
477 | ]]
478 | function unit:GetClassname()
479 | return globalUnit:GetClassname()
480 | end
481 |
482 | --[[[
483 | @func entity:GetEntityHandle()
484 | @desc Get the entity handle of the entity.
485 |
486 | @modification -
487 | ]]
488 | function unit:GetEntityHandle()
489 | return globalUnit:GetEntityHandle()
490 | end
491 |
492 | --[[[
493 | @func entity:GetEntityIndex()
494 | @desc Get the entity index of the entity.
495 |
496 | @modification -
497 | ]]
498 | function unit:GetEntityIndex()
499 | return globalUnit:GetEntityIndex()
500 | end
501 |
502 | --[[[
503 | @func entity:GetName()
504 | @desc Get the name of the entity.
505 |
506 | @modification -
507 | ]]
508 | function unit:GetName()
509 | return globalUnit:GetName()
510 | end
511 | end
--------------------------------------------------------------------------------
/Game/scripts/vscripts/AI/UserAI/1v1SF/AIStateCode.lua:
--------------------------------------------------------------------------------
1 | --=====================================================================
2 | -- State code
3 | --=====================================================================
4 | function AI:Pushing( state )
5 | if ( self.hero:GetHealth()/self.hero:GetMaxHealth() ) < 0.3 then
6 | self.mainStM:GotoState( 'Backing' )
7 | MoveUnitTo( self.hero, self.FOUNTAIN_POS )
8 | AI_Log( 'Going back go base!' )
9 | return
10 | end
11 |
12 | if not self.hero:IsAlive() then
13 | self.mainStM:GotoState( 'Buying' )
14 | return
15 | end
16 |
17 | self.pushStates:Think()
18 | end
19 |
20 | function AI:Backing( state )
21 | if DistanceUnitTo( self.hero, self.FOUNTAIN_POS ) < 50 then
22 | self.mainStM:GotoState( 'Buying' )
23 | return
24 | end
25 |
26 | if not self.hero:IsAlive() then
27 | self.mainStM:GotoState( 'Buying' )
28 | return
29 | end
30 |
31 | MoveUnitTo( self.hero, self.FOUNTAIN_POS )
32 | end
33 |
34 | function AI:Buying( state )
35 | if ( self.hero:GetHealth()/self.hero:GetMaxHealth() ) > 0.85 then
36 | self.mainStM:GotoState( 'ToLane' )
37 | AI_Log('Going to lane now.')
38 |
39 | self:BuyItems()
40 |
41 | self:GoToLane()
42 |
43 | return
44 | end
45 | end
46 |
47 | function AI:ToLane( state )
48 | if DistanceUnitTo( self.hero, self.NEAR_TOWER_POS ) < 200 then
49 | self.mainStM:GotoState( 'Pushing' )
50 | self.pushStates:GotoState( 'Attacking' )
51 | return
52 | end
53 |
54 | MoveUnitTo( self.hero, self.NEAR_TOWER_POS, true )
55 |
56 | if not self.hero:IsAlive() then
57 | self.mainStM:GotoState( 'Buying' )
58 | return
59 | end
60 | end
61 |
62 | --========================================================================================================
63 | --Push states
64 | --========================================================================================================
65 | function AI:Attacking( state )
66 | local friendlyCreeps = FindCreeps( self.LANE_CENTER, 1500, DOTA_UNIT_TARGET_TEAM_FRIENDLY, FIND_ANY_ORDER )
67 | local targets = FindTargets( self.hero:GetAbsOrigin(), 4000, DOTA_UNIT_TARGET_TEAM_ENEMY, FIND_CLOSEST )
68 |
69 | if #friendlyCreeps < 1 then
70 | self.pushStates:GotoState( 'Waiting' )
71 | MoveUnitTo( self.hero, self.NEAR_TOWER_POS )
72 | return
73 | end
74 |
75 | --Attack the closest target
76 | if #targets > 0 and state.attackTarget ~= targets[1] then
77 | UnitAttackTarget( self.hero, targets[1] )
78 | state.attackTarget = targets[1]
79 | end
80 | end
81 |
82 | function AI:Waiting( state )
83 | local friendlyCreeps = FindCreeps( self.LANE_CENTER, 1500, DOTA_UNIT_TARGET_TEAM_FRIENDLY, FIND_ANY_ORDER )
84 | local enemyCreeps = FindCreeps( self.LANE_CENTER, 1500, DOTA_UNIT_TARGET_TEAM_ENEMY, FIND_ANY_ORDER )
85 |
86 | local friendlyWavePos = AverageUnitPos( friendlyCreeps )
87 | local enemyWavePos = AverageUnitPos( enemyCreeps )
88 |
89 | if DistanceUnitTo( self.hero, friendlyWavePos ) < DistanceUnitTo( self.hero, enemyWavePos ) then
90 | self.pushStates:GotoState( 'Attacking' )
91 | return
92 | end
93 |
94 | AggressiveMoveUnitTo( self.hero, self.NEAR_TOWER_POS, true )
95 | end
96 |
97 | function AI:SafeRegen( state )
98 | if AI_GetGameTime() - state.entryTime > 1.5 then
99 | self.pushStates:GotoState( 'Attacking' )
100 | return
101 | end
102 |
103 | if ( AI_GetGameTime() - state.entryTime ) > 1 and ( self.hero:GetHealth()/self.hero:GetMaxHealth() ) <= 0.7 then
104 | local bottle = FindItemByName( self.hero, 'item_bottle' )
105 | if bottle ~= nil and bottle:GetCurrentCharges() > 0 and state.bottled == nil then
106 | state.bottled = true
107 | CastNoTarget( self.hero, bottle )
108 | end
109 | end
110 | end
--------------------------------------------------------------------------------
/Game/scripts/vscripts/AI/UserAI/1v1SF/AIUtil.lua:
--------------------------------------------------------------------------------
1 | function DistanceUnitTo( unit, position )
2 | return (unit:GetAbsOrigin() - position):Length2D()
3 | end
4 |
5 | function FindCreeps( position, radius, team, order )
6 | return AI_FindUnitsInRadius( position, radius, team,
7 | DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_FLAG_NONE,
8 | order, false )
9 | end
10 |
11 | function FindTargets( position, radius, team, order )
12 | return AI_FindUnitsInRadius( position, radius, team,
13 | DOTA_UNIT_TARGET_BASIC + DOTA_UNIT_TARGET_BUILDING + DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NONE,
14 | order, false )
15 | end
16 |
17 | function MoveUnitTo( unit, position, queue )
18 | if queue == nil then queue = false end
19 | AI_ExecuteOrderFromTable({
20 | UnitIndex = unit:GetEntityIndex(),
21 | OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
22 | Position = position,
23 | Queue = queue
24 | })
25 | end
26 |
27 | function AggressiveMoveUnitTo( unit, position, queue )
28 | if queue == nil then queue = false end
29 | AI_ExecuteOrderFromTable({
30 | UnitIndex = unit:GetEntityIndex(),
31 | OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
32 | Position = position,
33 | Queue = queue
34 | })
35 | end
36 |
37 | function UnitAttackTarget( unit, target, queue )
38 | if queue == nil then queue = false end
39 | AI_ExecuteOrderFromTable({
40 | UnitIndex = unit:GetEntityIndex(),
41 | OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
42 | TargetIndex = target:GetEntityIndex(),
43 | Queue = queue
44 | })
45 | end
46 |
47 | function UnitLevelUpAbility( unit, ability, queue )
48 | if queue == nil then queue = false end
49 | AI_ExecuteOrderFromTable({
50 | UnitIndex = unit:GetEntityIndex(),
51 | OrderType = DOTA_UNIT_ORDER_TRAIN_ABILITY,
52 | AbilityIndex = ability:GetEntityIndex(),
53 | Queue = queue
54 | })
55 | end
56 |
57 | function CastNoTarget( unit, ability, queue )
58 | if queue == nil then queue = false end
59 | AI_ExecuteOrderFromTable({
60 | UnitIndex = unit:GetEntityIndex(),
61 | OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
62 | AbilityIndex = ability:GetEntityIndex(),
63 | Queue = queue
64 | })
65 | end
66 |
67 | function AverageUnitPos( units )
68 | local n = 0
69 | local pos = Vector( 0, 0, 0 )
70 |
71 | for _,unit in pairs( units ) do
72 | pos = pos + unit:GetAbsOrigin()
73 | n = n + 1
74 | end
75 |
76 | if n > 1 then pos = pos * (1/n) end
77 |
78 | return pos
79 | end
80 |
81 | function FindItemByName( unit, itemName )
82 | local item = nil
83 | for i=0,5 do
84 | local slot = unit:GetItemInSlot( i )
85 | if slot ~= nil and slot:GetAbilityName() == itemName then
86 | item = slot
87 | break
88 | end
89 | end
90 | return item
91 | end
92 |
93 | function UnitBuyItem( unit, itemName )
94 | AI_BuyItem( unit, itemName )
95 | end
96 |
97 | function TryAndReport( f, context )
98 | local status, error = pcall( f, context )
99 | if status == false then
100 | print( error )
101 | print( debug.traceback() )
102 | end
103 | end
104 |
--------------------------------------------------------------------------------
/Game/scripts/vscripts/AI/UserAI/1v1SF/Documentation/Pushbot design.txt:
--------------------------------------------------------------------------------
1 | General description:
2 | The design for the 1v1 push bot is to try to push down the enemy tower as quickly as possible.
3 | The bot ignores the enemy hero and just autoattacks the creeps in the lane.
4 |
5 | State-based design:
6 | When the bot's hero gets to a certain HP threshold it will return to base to regen and buy items.
7 | Once the hero's HP is full it will return back to lane. When a hero dies it will also go to its
8 | shopping state. Pushing contains another state machine to prevent tanking enemy creeps.
9 |
10 | A formal description of the statespace can be found in 'Pushbot statespace.png'
11 |
12 | State implementation:
13 | The buying state will be implemented by adding a decision tree in combination with a list of
14 | desired items, which the bot will just try to purchase one by one if it has the money. The
15 | attacking creeps or tower state also consists of a decision tree to determine what to do.
16 | The implementation of the other states is trivial.
--------------------------------------------------------------------------------
/Game/scripts/vscripts/AI/UserAI/1v1SF/Documentation/Pushbot statespace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ModDota/Dota2AIFramework/56498981a6d199df9790e8e19f6bf204e00b7f3f/Game/scripts/vscripts/AI/UserAI/1v1SF/Documentation/Pushbot statespace.png
--------------------------------------------------------------------------------
/Game/scripts/vscripts/AI/UserAI/1v1SF/StateMachine.lua:
--------------------------------------------------------------------------------
1 | StateMachine = class({})
2 |
3 | function StateMachine:constructor( context )
4 | self.states = {}
5 | self.currentState = nil
6 | self.context = context
7 | end
8 |
9 | function StateMachine:AddState( name, state )
10 | --Store state
11 | self.states[ name ] = { func = state, name = name, context = {} }
12 |
13 | --Set a default state in case no starting state is given
14 | if self.currentState == nil then
15 | self.currentState = self.states[ name ]
16 | end
17 | end
18 |
19 | function StateMachine:GotoState( name, context )
20 | --Set default context
21 | if context == nil then
22 | context = {}
23 | end
24 |
25 | if self.states[ name ] ~= nil then
26 | self.currentState = self.states[ name ]
27 | self.currentState.context = context
28 | else
29 | Warning( string.format( 'State %s not found!', name ) )
30 | end
31 | end
32 |
33 | function StateMachine:GetCurrentState()
34 | return self.currentState
35 | end
36 |
37 | function StateMachine:Think()
38 | self.currentState.func( self.context, self.currentState.context )
39 | end
--------------------------------------------------------------------------------
/Game/scripts/vscripts/AI/UserAI/1v1SF/ai_init.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | Sample AI
3 |
4 | Sample AI to demonstrate and test the AI competition framework.
5 |
6 | Code: Perry
7 | Date: October, 2015
8 | ]]
9 |
10 | --Define AI object
11 | AI = {}
12 |
13 | require( 'StateMachine' )
14 | require( 'AIStateCode' )
15 | require( 'AIUtil' )
16 |
17 | --Initialisation function, called by the framework with parameters
18 | function AI:Init( params )
19 | AI_Log( 'Hello world!' )
20 |
21 | --Save params
22 | self.team = params.team
23 | self.hero = params.heroes[1]
24 | self.heroIndex = self.hero:GetEntityIndex()
25 | self.data = params.data
26 |
27 | --Define some team-based parameters the AI needs
28 | if params.team == DOTA_TEAM_GOODGUYS then
29 | self.LANE_CENTER = Vector( -647, -287, 17 )
30 | self.HIGH_GROUND_POS = Vector( -1041, -585, 128 )
31 | self.NEAR_TOWER_POS = Vector( -1640, -1228, 128 )
32 | self.FOUNTAIN_POS = Vector( -6817, -6347, 385 )
33 | else
34 | self.LANE_CENTER = Vector( -647, -287, 17 )
35 | self.HIGH_GROUND_POS = Vector( -168, 85, 128 )
36 | self.NEAR_TOWER_POS = Vector( 610, 386, 128 )
37 | self.FOUNTAIN_POS = Vector( 6733, 6116, 385 )
38 | end
39 |
40 | --Register event listeners
41 | AI:RegisterEventListeners()
42 |
43 | --Start thinker
44 | Timers:CreateTimer( function()
45 | return self:Think()
46 | end)
47 |
48 | --Go to
49 | self.mainStM = self:SetUpStateMachine()
50 | self.mainStM:GotoState( 'Buying' )
51 | self.itemProgression = 0
52 | MoveUnitTo( self.hero, self.HIGH_GROUND_POS )
53 | end
54 |
55 | function AI:RegisterEventListeners()
56 | --Listen to the entity hurt event for the AI hero
57 | AIEvents:RegisterEventListener( 'entity_hurt', function( event )
58 | if event.entindex_killed == self.heroIndex then
59 | AI:OnTakeDamage( event )
60 | end
61 | end)
62 | end
63 |
64 | function AI:SetUpStateMachine()
65 | local statemachine = StateMachine( self )
66 |
67 | self.pushStates = self:SetUpPushingStateMachine( self )
68 |
69 | statemachine:AddState( 'Pushing', Dynamic_Wrap( AI, 'Pushing' ) )
70 | statemachine:AddState( 'Backing', Dynamic_Wrap( AI, 'Backing' ) )
71 | statemachine:AddState( 'Buying', Dynamic_Wrap( AI, 'Buying' ) )
72 | statemachine:AddState( 'ToLane', Dynamic_Wrap( AI, 'ToLane' ) )
73 |
74 | return statemachine
75 | end
76 |
77 | function AI:SetUpPushingStateMachine()
78 | local statemachine = StateMachine( self )
79 |
80 | statemachine:AddState( 'Attacking', Dynamic_Wrap( AI, 'Attacking' ) )
81 | statemachine:AddState( 'Waiting', Dynamic_Wrap( AI, 'Waiting' ) )
82 | statemachine:AddState( 'SafeRegen', Dynamic_Wrap( AI, 'SafeRegen' ) )
83 |
84 | return statemachine
85 | end
86 |
87 | --AI think function
88 | function AI:Think()
89 |
90 | DebugDrawCircle( self.hero:GetAbsOrigin() + self.hero:GetForwardVector() * 200, Vector( 0, 255, 0 ), 10, 250, true, 0.5 )
91 | DebugDrawCircle( self.hero:GetAbsOrigin() + self.hero:GetForwardVector() * 450, Vector( 0, 255, 0 ), 10, 250, true, 0.5 )
92 | DebugDrawCircle( self.hero:GetAbsOrigin() + self.hero:GetForwardVector() * 700, Vector( 0, 255, 0 ), 10, 250, true, 0.5 )
93 |
94 | TryAndReport( self.AbilityPointThink, self )
95 | TryAndReport( self.mainStM.Think, self.mainStM )
96 | --pcall(AI.AbilityPointThink, self)
97 | --pcall(self.mainStM.Think, self.mainStM)
98 |
99 | return 0.5
100 | end
101 |
102 | --Think about ability points
103 | function AI:AbilityPointThink()
104 |
105 | local levelTable = {
106 | 'nevermore_necromastery', --1
107 | 'nevermore_dark_lord', --2
108 | 'nevermore_dark_lord', --3
109 | 'nevermore_necromastery', --4
110 | 'nevermore_dark_lord', --5
111 | 'nevermore_necromastery', --6
112 | 'nevermore_dark_lord', --7
113 | 'nevermore_necromastery', --8
114 | 'nevermore_shadowraze1', --9
115 | 'nevermore_shadowraze1', --10
116 | 'nevermore_shadowraze1', --11
117 | 'nevermore_shadowraze1', --12
118 | 'attribute_bonus', --13
119 | 'attribute_bonus', --14
120 | 'attribute_bonus', --15
121 | 'attribute_bonus', --16
122 | 'attribute_bonus', --17
123 | 'attribute_bonus', --18
124 | 'attribute_bonus', --19
125 | 'attribute_bonus', --20
126 | 'attribute_bonus', --21
127 | 'attribute_bonus', --22
128 | 'nevermore_requiem', --23
129 | 'nevermore_requiem', --24
130 | 'nevermore_requiem' --25
131 | }
132 |
133 | local abilityPoints = self.hero:GetAbilityPoints()
134 |
135 | if abilityPoints > 0 then
136 | for i=1,abilityPoints do
137 | AI_Log('leveling '..levelTable[ self.hero:GetLevel() - i + 1 ] )
138 | UnitLevelUpAbility( self.hero, self.hero:FindAbilityByName( levelTable[ self.hero:GetLevel() - i + 1 ] ) )
139 | end
140 | end
141 | end
142 |
143 | --Buy items
144 | function AI:BuyItems()
145 | local itemTable = {
146 | 'item_tango',
147 | 'item_wraith_band',
148 | 'item_bottle',
149 | 'item_boots',
150 | 'item_belt_of_strength',
151 | 'item_gloves',
152 | 'item_ring_of_basilius'
153 | }
154 |
155 | while self.itemProgression < #itemTable and
156 | self.hero:GetGold() > GetItemCost( itemTable[self.itemProgression + 1] ) do
157 | UnitBuyItem( self.hero, itemTable[self.itemProgression + 1] )
158 | self.itemProgression = self.itemProgression + 1
159 | end
160 | end
161 |
162 | --Decide how to go to lane ( walking/TP )
163 | function AI:GoToLane()
164 | MoveUnitTo( self.hero, self.HIGH_GROUND_POS )
165 | end
166 |
167 | function AI:OnTakeDamage( event )
168 | local attacker = AI_EntIndexToHScript( event.entindex_attacker )
169 | if attacker:IsTower() or attacker:IsHero() or ( self.hero:GetHealth()/self.hero:GetMaxHealth() ) < 0.6 then
170 | self.pushStates:GotoState( 'SafeRegen', { entryTime = AI_GetGameTime() } )
171 | if DistanceUnitTo( self.hero, self.HIGH_GROUND_POS ) < DistanceUnitTo( self.hero, self.LANE_CENTER ) then
172 | MoveUnitTo( self.hero, self.NEAR_TOWER_POS )
173 | else
174 | MoveUnitTo( self.hero, self.NEAR_TOWER_POS )
175 | end
176 | end
177 | end
178 |
179 | --Return the AI object <-- IMPORTANT
180 | return AI
--------------------------------------------------------------------------------
/Game/scripts/vscripts/AI/UserAI/DotaSample/ai_init.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | Sample AI
3 |
4 | Sample AI to demonstrate and test the AI competition framework.
5 |
6 | Code: Perry
7 | Date: October, 2015
8 | ]]
9 |
10 | --Define AI object
11 | local AI = {}
12 |
13 | --Initialisation function, called by the framework with parameters
14 | function AI:Init( params )
15 | AI_Log( 'Sample DotA AI: Hello world!' )
16 |
17 | --Save team
18 | self.team = params.team
19 | self.data = params.data
20 |
21 | --List assigned heroes
22 | for _, hero in pairs(params.heroes) do
23 | print("Found assigned hero: "..hero:GetUnitName())
24 | end
25 |
26 | --Start thinker
27 | Timers:CreateTimer( function()
28 | return self:Think()
29 | end)
30 |
31 | self.state = 0
32 | end
33 |
34 | --AI think function
35 | function AI:Think()
36 | -- Think - do something here
37 |
38 | return 2
39 | end
40 |
41 | --Return the AI object <-- IMPORTANT
42 | return AI
--------------------------------------------------------------------------------
/Game/scripts/vscripts/AI/UserAI/sample_ai/ai_init.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | Sample AI
3 |
4 | Sample AI to demonstrate and test the AI competition framework.
5 |
6 | Code: Perry
7 | Date: October, 2015
8 | ]]
9 |
10 | --Define AI object
11 | local AI = {}
12 |
13 | --Initialisation function, called by the framework with parameters
14 | function AI:Init( params )
15 | AI_Log( 'Sample AI: Hello world!' )
16 |
17 | --Save team
18 | self.team = params.team
19 | self.hero = params.heroes[1]
20 | self.data = params.data
21 |
22 | --Start thinker
23 | Timers:CreateTimer( function()
24 | return self:Think()
25 | end)
26 |
27 | self.state = 0
28 |
29 | AIUnitTests:Run( _G, self.hero, self.hero:GetAbilityByIndex( 0 ), AIPlayerResource )
30 | end
31 |
32 | --AI think function
33 | function AI:Think()
34 | --Check if we're at the move target yet
35 | AI_ExecuteOrderFromTable({
36 | UnitIndex = self.hero:GetEntityIndex(),
37 | OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
38 | Position = Vector( -2500, 1000, 0 )
39 | })
40 |
41 | return 2
42 | end
43 |
44 | --Return the AI object <-- IMPORTANT
45 | return AI
--------------------------------------------------------------------------------
/Game/scripts/vscripts/AIFramework.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | AI Competition framework.
3 |
4 | This is the main class of the Dota 2 AI competition framework developed for the purpose of holding
5 | AI competitions where AI players are presented with tasks in Dota 2 to accomplish as well as possible
6 | without a human intervening.
7 |
8 | Code: Perry
9 | Date: October, 2015
10 | ]]
11 |
12 | --Include AI
13 | require( 'AI.AIManager' )
14 |
15 | --Require game mode logic
16 | require( 'AIGameModes.BaseAIGameMode' )
17 |
18 | --Class definition
19 | if AIFramework == nil then
20 | AIFramework = class({})
21 | end
22 |
23 | --Initialisation
24 | function AIFramework:Init()
25 | print( 'Initialising AI framework.' )
26 |
27 | --Make table to store vision dummies in
28 | AIFramework.visionDummies = {}
29 |
30 | --GameRules:FinishCustomGameSetup()
31 | GameRules:SetCustomGameTeamMaxPlayers( 1, 5 )
32 |
33 | Convars:SetInt( 'dota_auto_surrender_all_disconnected_timeout', 7200 )
34 | SendToServerConsole( 'customgamesetup_set_auto_launch_delay 300' )
35 |
36 | --Initialise the AI manager
37 | AIManager:Init()
38 |
39 | --Register event listeners
40 | ListenToGameEvent( 'player_connect_full', Dynamic_Wrap( AIFramework, 'OnPlayerConnect' ), self )
41 | ListenToGameEvent( 'game_rules_state_change', Dynamic_Wrap( AIFramework, 'OnGameStateChange' ), self )
42 | CustomGameEventManager:RegisterListener( 'spawn_ai', function(...) self:SpawnAI(...) end )
43 |
44 | --Read in gamemode config
45 | self.config = LoadKeyValues("scripts/config/gamemode_ai.kv")
46 | --Send gamemode info to nettable for UI
47 | local nettable = {}
48 | for _, gamemode in pairs(self.config) do
49 | table.insert(nettable, {name = gamemode.Name, name, AI = gamemode.AI})
50 | end
51 | CustomNetTables:SetTableValue("config", "gamemodes", nettable)
52 | end
53 |
54 | --player_connect_full event handler
55 | function AIFramework:OnPlayerConnect( event )
56 | PlayerResource:SetCustomTeamAssignment( event.index, 1 )
57 |
58 | AIManager.numPlayers = AIManager.numPlayers + 1
59 | end
60 |
61 | --game_rules_state_changed event handler
62 | function AIFramework:OnGameStateChange( event )
63 | local state = GameRules:State_Get()
64 | if state == DOTA_GAMERULES_STATE_PRE_GAME then
65 | self:OnGameLoaded()
66 | end
67 | end
68 |
69 | --Called once the game gets to the PRE_GAME state
70 | function AIFramework:OnGameLoaded()
71 | local t = 1
72 | Timers:CreateTimer( 1, function()
73 | if t < 4 then
74 | --Count down
75 | ShowCenterMessage( 4 - t, 1 )
76 | else
77 | ShowCenterMessage( 'Start!', 2 )
78 | --Initialise Radiant AI
79 | AIManager:InitAllAI( self.gameMode )
80 |
81 | --Initialise gamemode
82 | self.gameMode:OnGameStart( AIManager:GetAllHeroes() )
83 |
84 | return nil
85 | end
86 |
87 | t = t + 1
88 | return 1
89 | end)
90 | end
91 |
92 | function AIFramework:SpawnAI( source, args )
93 | --Load gamemode
94 | local modeConfig = self.config[args.game_mode]
95 | local gameMode = require( 'AIGameModes.'..modeConfig.Path )
96 |
97 | --Set up the game mode
98 | gameMode:Setup()
99 |
100 | --Load ai for the gamemode
101 | local heroes = {}
102 | if gameMode.FixedHeroes then
103 | heroes = gameMode.Heroes
104 | end
105 |
106 | --Load in AI
107 | if args.ai1 ~= "0" and args.ai1 ~= 0 then
108 | AIManager:AddAI( modeConfig.AI[args.ai1], DOTA_TEAM_GOODGUYS, heroes )
109 | end
110 | if args.ai2 ~= "0" and args.ai2 ~= 0 then
111 | AIManager:AddAI( modeConfig.AI[args.ai2], DOTA_TEAM_BADGUYS, heroes )
112 | end
113 |
114 | --Save gamemode for later
115 | self.gameMode = gameMode
116 | end
117 |
118 | --Show a center message for some duration
119 | function ShowCenterMessage( msg, dur )
120 | local centerMessage = {
121 | message = msg,
122 | duration = dur or 3
123 | }
124 | FireGameEvent( "show_center_message", centerMessage )
125 | end
126 |
--------------------------------------------------------------------------------
/Game/scripts/vscripts/AIGameModes/1v1Mid.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | AI Framework gamemode.
3 | This file contains the rules for a full AI game mode.
4 |
5 | Game mode: 1v1 Mid.
6 | Idea: Two AI players battle at mid lane. The winner is the AI that kills the other twice or
7 | destroys the first tower.
8 |
9 | FixedDuration: No
10 | Duration: -
11 |
12 | FixedHeroes: Yes
13 | Heroes: npc_dota_hero_nevermore
14 |
15 | Starting level: 1
16 | Starting gold: 625
17 |
18 | Win condition: The player that kills the other player twice or pushes mid tower first.
19 | ]]
20 |
21 | --Create a gamemode object from the default gamemode object
22 | local AIGameMode = BaseAIGameMode()
23 |
24 | AIGameMode.Heroes = {'npc_dota_hero_nevermore'}
25 |
26 | AIGameMode.StartingLevel = 1
27 | AIGameMode.StartingGold = 625
28 |
29 | function AIGameMode:Setup()
30 | print('1v1Mid Setup')
31 |
32 | --Set pre game time
33 | GameRules:SetPreGameTime( 35.0 )
34 |
35 | --Disable side lanes
36 | Convars:SetBool( 'dota_disable_bot_lane', true )
37 | Convars:SetBool( 'dota_disable_top_lane', true )
38 | end
39 |
40 | function AIGameMode:OnGameStart( teamHeroes )
41 | --Call to BaseAIGameMode setting up starting gold/level
42 | self:InitHeroes( teamHeroes )
43 |
44 | --Save the only hero on each team for later
45 | self.team1Hero = teamHeroes[ DOTA_TEAM_GOODGUYS ][1]
46 | self.team2Hero = teamHeroes[ DOTA_TEAM_BADGUYS ][1]
47 |
48 | --Save the tower handles
49 | self.team1Tower = Entities:FindByName( nil, 'dota_goodguys_tower1_mid' )
50 | self.team2Tower = Entities:FindByName( nil, 'dota_badguys_tower1_mid' )
51 |
52 | --Listen to entity kills
53 | ListenToGameEvent( 'entity_killed', Dynamic_Wrap( AIGameMode, 'OnEntityKilled' ), self )
54 | end
55 |
56 | --entity_killed event handler
57 | function AIGameMode:OnEntityKilled( event )
58 | local killedUnit = EntIndexToHScript( event.entindex_killed )
59 |
60 | --Check for towers
61 | if killedUnit == self.team1Tower then
62 | --Call BaseAIGameMode functionality
63 | self:SetTeamWin( DOTA_TEAM_BADGUYS )
64 | elseif killedUnit == self.team2Tower then
65 | --Call BaseAIGameMode functionality
66 | self:SetTeamWin( DOTA_TEAM_GOODGUYS )
67 | end
68 |
69 | --Check hero kills
70 | if killedUnit == self.team1Hero or killedUnit == self.team2Hero then
71 | if PlayerResource:GetKills( self.team1Hero:GetPlayerOwnerID() ) >= 2 then
72 | --Call BaseAIGameMode functionality
73 | self:SetTeamWin( DOTA_TEAM_GOODGUYS )
74 | elseif PlayerResource:GetKills( self.team2Hero:GetPlayerOwnerID() ) >= 2 then
75 | --Call BaseAIGameMode functionality
76 | self:SetTeamWin( DOTA_TEAM_BADGUYS )
77 | end
78 | end
79 | end
80 |
81 | --Get extra data the AI can/needs to use for this challenge
82 | function AIGameMode:GetExtraData( team )
83 | --No extra data
84 | return {}
85 | end
86 |
87 | return AIGameMode
--------------------------------------------------------------------------------
/Game/scripts/vscripts/AIGameModes/BaseAIGameMode.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | AI Framework gamemode.
3 | This file contains the base AI Game mode with default settings. These settings are then overriden in every
4 | specified gamemode.
5 |
6 | Defaults:
7 | FixedDuration: No
8 | Duration: 0
9 |
10 | NumPlayers: 1
11 | FixedHeroes: Yes
12 | Heroes: npc_dota_hero_sven
13 |
14 | Starting level: 1
15 | Starting gold: 625
16 | ]]
17 | BaseAIGameMode = class({})
18 |
19 | function BaseAIGameMode:constructor()
20 |
21 | --Set defaults
22 | self.NumTeams = 2
23 | self.Teams = { DOTA_TEAM_GOODGUYS, DOTA_TEAM_BADGUYS }
24 |
25 | self.FixedDuration = false
26 | self.Duration = 0
27 |
28 | self.NumPlayers = 1
29 | self.FixedHeroes = true
30 | self.Heroes = {'npc_dota_hero_sven'}
31 |
32 | self.StartingLevel = 1
33 | self.StartingGold = 625
34 |
35 | return self
36 | end
37 |
38 | --Set up the gamemode before loading in
39 | function BaseAIGameMode:Setup()
40 | --Empty default
41 | end
42 |
43 | --Get extra data for the AI (like a description of the map) for a team
44 | function BaseAIGameMode:GetExtraData( team )
45 | return {}
46 | end
47 |
48 | --Make a team win the game
49 | function BaseAIGameMode:SetTeamWin( team )
50 | GameRules:SetGameWinner( team )
51 | end
52 |
53 | function BaseAIGameMode:InitHeroes( teamHeroes )
54 | for team, heroes in pairs( teamHeroes ) do
55 | self:SetupHeroes( heroes )
56 | end
57 | end
58 |
59 | function BaseAIGameMode:SetupHeroes( heroes )
60 | for _, hero in ipairs( heroes ) do
61 | --Give starting gold
62 | hero:SetGold( self.StartingGold, false )
63 |
64 | --Level up to the starting level
65 | while hero:GetLevel() < self.StartingLevel do
66 | if hero:GetLevel() == 1 then
67 | hero:HeroLevelUp( true )
68 | else
69 | hero:HeroLevelUp( false )
70 | end
71 | end
72 | end
73 | end
--------------------------------------------------------------------------------
/Game/scripts/vscripts/AIGameModes/DotA.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | AI Framework gamemode.
3 | This file contains the rules for a full AI game mode.
4 |
5 | Game mode: Default DotA.
6 | Idea: A regular game of dota.
7 |
8 | FixedDuration: No
9 | Duration: -
10 |
11 | FixedHeroes: Yes
12 | Heroes:
13 | npc_dota_hero_nevermore
14 | npc_dota_hero_shadow_demon
15 | npc_dota_hero_sven
16 | npc_dota_hero_mirana
17 | npc_dota_hero_nyx_assassin
18 |
19 | Starting level: 1
20 | Starting gold: 625
21 |
22 | Win condition: Destroy the enemy ancient.
23 | ]]
24 |
25 | --Create a gamemode object from the default gamemode object
26 | local AIGameMode = BaseAIGameMode()
27 |
28 | AIGameMode.NumPlayers = 5
29 | AIGameMode.Heroes = {
30 | 'npc_dota_hero_nevermore',
31 | 'npc_dota_hero_shadow_demon',
32 | 'npc_dota_hero_sven',
33 | 'npc_dota_hero_mirana',
34 | 'npc_dota_hero_nyx_assassin'
35 | }
36 |
37 | AIGameMode.StartingLevel = 1
38 | AIGameMode.StartingGold = 625
39 |
40 | function AIGameMode:Setup()
41 | print('DotA AI gamemode setup')
42 | end
43 |
44 | function AIGameMode:OnGameStart( teamHeroes )
45 | --Call to BaseAIGameMode setting up starting gold/level
46 | self:InitHeroes( teamHeroes )
47 |
48 | --Save the tower handles
49 | self.team1Ancient = Entities:FindByName( nil, 'dota_goodguys_fort' )
50 | self.team2Ancient = Entities:FindByName( nil, 'dota_badguys_fort' )
51 |
52 | --Listen to entity kills
53 | ListenToGameEvent( 'entity_killed', Dynamic_Wrap( AIGameMode, 'OnEntityKilled' ), self )
54 | end
55 |
56 | --entity_killed event handler
57 | function AIGameMode:OnEntityKilled( event )
58 | local killedUnit = EntIndexToHScript( event.entindex_killed )
59 |
60 | --Check for ancient kills
61 | if killedUnit == self.team1Ancient then
62 | --Call BaseAIGameMode functionality
63 | self:SetTeamWin( DOTA_TEAM_BADGUYS )
64 | elseif killedUnit == self.team2Ancient then
65 | --Call BaseAIGameMode functionality
66 | self:SetTeamWin( DOTA_TEAM_GOODGUYS )
67 | end
68 | end
69 |
70 | --Get extra data the AI can/needs to use for this challenge
71 | function AIGameMode:GetExtraData( team )
72 | --No extra data
73 | return {}
74 | end
75 |
76 | return AIGameMode
--------------------------------------------------------------------------------
/Game/scripts/vscripts/AIGameModes/FarmOptimizer.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | AI Framework gamemode.
3 | This file contains the rules for a full AI game mode.
4 |
5 | Game mode: Farm Optimizer.
6 | Idea: Two AIs race on identical (but mirrored?) parts of an unknown map to farm the neutrals as fast as possible.
7 |
8 | FixedDuration: Yes
9 | Duration: 10m
10 |
11 | FixedHeroes: Yes
12 | Heroes: npc_dota_hero_antimage
13 |
14 | Starting level: 5
15 | Starting gold: 1500
16 |
17 | Win condition: Player with highest net worth at the end of the duration.
18 | ]]
19 |
20 | --Create a gamemode object from the default gamemode object
21 | local AIGameMode = BaseAIGameMode()
22 |
23 | AIGameMode.FixedDuration = true
24 | AIGameMode.Duration = 600
25 |
26 | AIGameMode.Heroes = {'npc_dota_hero_antimage'}
27 |
28 | AIGameMode.StartingLevel = 5
29 | AIGameMode.StartingGold = 1500
30 |
31 | function AIGameMode:OnGameStart( teamHeroes )
32 | --Call to BaseAIGameMode setting up starting gold/level
33 | self:InitHeroes( teamHeroes )
34 |
35 | --Save the only hero on each team for later
36 | self.team1Hero = teamHeroes[ DOTA_TEAM_GOODGUYS ][1]
37 | self.team2Hero = teamHeroes[ DOTA_TEAM_BADGUYS ][1]
38 |
39 | --Force the start of the game
40 | Tutorial:ForceGameStart()
41 | end
42 |
43 | --Get extra data the AI can/needs to use for this challenge
44 | function AIGameMode:GetExtraData( team )
45 | local m = 1
46 | if team == DOTA_TEAM_GOODGUYS then
47 | m = -1
48 | end
49 |
50 | return {
51 | shop = Vector( 0, 0, 0 ),
52 | camps = {
53 | Vector( m * 500, 0, 0 ),
54 | Vector( m * 100, 500, 0 ),
55 | Vector( m * 1000, 1000, 0 )
56 | }
57 | }
58 | end
59 |
60 | return AIGameMode
--------------------------------------------------------------------------------
/Game/scripts/vscripts/Libraries/Timers.lua:
--------------------------------------------------------------------------------
1 | TIMERS_VERSION = "1.03"
2 |
3 | --[[
4 | -- A timer running every second that starts immediately on the next frame, respects pauses
5 | Timers:CreateTimer(function()
6 | print ("Hello. I'm running immediately and then every second thereafter.")
7 | return 1.0
8 | end
9 | )
10 | -- A timer which calls a function with a table context
11 | Timers:CreateTimer(GameMode.someFunction, GameMode)
12 | -- A timer running every second that starts 5 seconds in the future, respects pauses
13 | Timers:CreateTimer(5, function()
14 | print ("Hello. I'm running 5 seconds after you called me and then every second thereafter.")
15 | return 1.0
16 | end
17 | )
18 | -- 10 second delayed, run once using gametime (respect pauses)
19 | Timers:CreateTimer({
20 | endTime = 10, -- when this timer should first execute, you can omit this if you want it to run first on the next frame
21 | callback = function()
22 | print ("Hello. I'm running 10 seconds after when I was started.")
23 | end
24 | })
25 | -- 10 second delayed, run once regardless of pauses
26 | Timers:CreateTimer({
27 | useGameTime = false,
28 | endTime = 10, -- when this timer should first execute, you can omit this if you want it to run first on the next frame
29 | callback = function()
30 | print ("Hello. I'm running 10 seconds after I was started even if someone paused the game.")
31 | end
32 | })
33 | -- A timer running every second that starts after 2 minutes regardless of pauses
34 | Timers:CreateTimer("uniqueTimerString3", {
35 | useGameTime = false,
36 | endTime = 120,
37 | callback = function()
38 | print ("Hello. I'm running after 2 minutes and then every second thereafter.")
39 | return 1
40 | end
41 | })
42 | -- A timer using the old style to repeat every second starting 5 seconds ahead
43 | Timers:CreateTimer("uniqueTimerString3", {
44 | useOldStyle = true,
45 | endTime = GameRules:GetGameTime() + 5,
46 | callback = function()
47 | print ("Hello. I'm running after 5 seconds and then every second thereafter.")
48 | return GameRules:GetGameTime() + 1
49 | end
50 | })
51 | ]]
52 |
53 |
54 |
55 | TIMERS_THINK = 0.01
56 |
57 | if Timers == nil then
58 | print ( '[Timers] creating Timers' )
59 | Timers = {}
60 | Timers.__index = Timers
61 | end
62 |
63 | function Timers:new( o )
64 | o = o or {}
65 | setmetatable( o, Timers )
66 | return o
67 | end
68 |
69 | function Timers:_xpcall (f, ...)
70 | print(f)
71 | print({...})
72 | PrintTable({...})
73 | local result = xpcall (function () return f(unpack(arg)) end,
74 | function (msg)
75 | -- build the error message
76 | return msg..'\n'..debug.traceback()..'\n'
77 | end)
78 |
79 | print(result)
80 | PrintTable(result)
81 | if not result[1] then
82 | -- throw an error
83 | end
84 | -- remove status code
85 | table.remove (result, 1)
86 | return unpack (result)
87 | end
88 |
89 | function Timers:start()
90 | Timers = self
91 | self.timers = {}
92 |
93 | local ent = Entities:CreateByClassname("info_target") -- Entities:FindByClassname(nil, 'CWorld')
94 | ent:SetThink("Think", self, "timers", TIMERS_THINK)
95 | end
96 |
97 | function Timers:Think()
98 | if GameRules:State_Get() >= DOTA_GAMERULES_STATE_POST_GAME then
99 | return
100 | end
101 |
102 | -- Track game time, since the dt passed in to think is actually wall-clock time not simulation time.
103 | local now = GameRules:GetGameTime()
104 |
105 | -- Process timers
106 | for k,v in pairs(Timers.timers) do
107 | local bUseGameTime = true
108 | if v.useGameTime ~= nil and v.useGameTime == false then
109 | bUseGameTime = false
110 | end
111 | local bOldStyle = false
112 | if v.useOldStyle ~= nil and v.useOldStyle == true then
113 | bOldStyle = true
114 | end
115 |
116 | local now = GameRules:GetGameTime()
117 | if not bUseGameTime then
118 | now = Time()
119 | end
120 |
121 | if v.endTime == nil then
122 | v.endTime = now
123 | end
124 | -- Check if the timer has finished
125 | if now >= v.endTime then
126 | -- Remove from timers list
127 | Timers.timers[k] = nil
128 |
129 | -- Run the callback
130 | local status, nextCall
131 | if v.context then
132 | status, nextCall = xpcall(function() return v.callback(v.context, v) end, function (msg)
133 | return msg..'\n'..debug.traceback()..'\n'
134 | end)
135 | else
136 | status, nextCall = xpcall(function() return v.callback(v) end, function (msg)
137 | return msg..'\n'..debug.traceback()..'\n'
138 | end)
139 | end
140 |
141 | -- Make sure it worked
142 | if status then
143 | -- Check if it needs to loop
144 | if nextCall then
145 | -- Change its end time
146 |
147 | if bOldStyle then
148 | v.endTime = v.endTime + nextCall - now
149 | else
150 | v.endTime = v.endTime + nextCall
151 | end
152 |
153 | Timers.timers[k] = v
154 | end
155 |
156 | -- Update timer data
157 | --self:UpdateTimerData()
158 | else
159 | -- Nope, handle the error
160 | Timers:HandleEventError('Timer', k, nextCall)
161 | end
162 | end
163 | end
164 |
165 | return TIMERS_THINK
166 | end
167 |
168 | function Timers:HandleEventError(name, event, err)
169 | print(err)
170 |
171 | -- Ensure we have data
172 | name = tostring(name or 'unknown')
173 | event = tostring(event or 'unknown')
174 | err = tostring(err or 'unknown')
175 |
176 | -- Tell everyone there was an error
177 | --Say(nil, name .. ' threw an error on event '..event, false)
178 | --Say(nil, err, false)
179 |
180 | -- Prevent loop arounds
181 | if not self.errorHandled then
182 | -- Store that we handled an error
183 | self.errorHandled = true
184 | end
185 | end
186 |
187 | function Timers:CreateTimer(name, args, context)
188 | if type(name) == "function" then
189 | if args ~= nil then
190 | context = args
191 | end
192 | args = {callback = name}
193 | name = DoUniqueString("timer")
194 | elseif type(name) == "table" then
195 | args = name
196 | name = DoUniqueString("timer")
197 | elseif type(name) == "number" then
198 | args = {endTime = name, callback = args}
199 | name = DoUniqueString("timer")
200 | end
201 | if not args.callback then
202 | print("Invalid timer created: "..name)
203 | return
204 | end
205 |
206 |
207 | local now = GameRules:GetGameTime()
208 | if args.useGameTime ~= nil and args.useGameTime == false then
209 | now = Time()
210 | end
211 |
212 | if args.endTime == nil then
213 | args.endTime = now
214 | elseif args.useOldStyle == nil or args.useOldStyle == false then
215 | args.endTime = now + args.endTime
216 | end
217 |
218 | args.context = context
219 |
220 | Timers.timers[name] = args
221 |
222 | return name
223 | end
224 |
225 | function Timers:RemoveTimer(name)
226 | Timers.timers[name] = nil
227 | end
228 |
229 | function Timers:RemoveTimers(killAll)
230 | local timers = {}
231 |
232 | if not killAll then
233 | for k,v in pairs(Timers.timers) do
234 | if v.persist then
235 | timers[k] = v
236 | end
237 | end
238 | end
239 |
240 | Timers.timers = timers
241 | end
242 |
243 | if not Timers.timers then Timers:start() end
--------------------------------------------------------------------------------
/Game/scripts/vscripts/LuaModifiers/modifier_dummy.lua:
--------------------------------------------------------------------------------
1 | modifier_dummy = class({})
2 |
3 | --Set the dummy out of the game
4 | function modifier_dummy:CheckState()
5 | local state = {
6 | [MODIFIER_STATE_OUT_OF_GAME] = true,
7 | [MODIFIER_STATE_INVULNERABLE] = true,
8 | [MODIFIER_STATE_NO_UNIT_COLLISION] = true,
9 | [MODIFIER_STATE_UNSELECTABLE] = true
10 | }
11 |
12 | return state
13 | end
--------------------------------------------------------------------------------
/Game/scripts/vscripts/addon_game_mode.lua:
--------------------------------------------------------------------------------
1 | --Libraries
2 | require( 'Libraries.Timers' )
3 |
4 | --Core files
5 | require( 'AIFramework' )
6 |
7 | --Precache, not using this atm
8 | function Precache( context ) end
9 |
10 | --Activate the game mode
11 | function Activate()
12 | --Initialise AI framework
13 | GameRules.Addon = AIFramework()
14 | GameRules.Addon:Init()
15 | end
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DotA 2 AI Competition Framework
2 | The purpose of this framework is to provide a platform for AI competitions in DotA 2. It handles AI setup as well as wrapping the regular DotA 2 lua API to prevent AI scripts from accessing data or performing actions, to emulate the AI playing as a human player.
3 |
4 | [Preview Video](https://www.youtube.com/watch?v=lKfmYQgBC8o)
5 |
6 | ### Goals of the framework:
7 | + Encourage the development of Lua AI for dota custom games.
8 | + Provide a starting point for developers that want AI in their games.
9 | + Eventually having decent AI for bot matches.
10 |
11 | ## Challenges
12 | Different challenges will drive development in different directions. Therefore the AI framework provides different challenges for AI to deal with. The challenges currently supported are:
13 | * 1v1 Mid - Two AI face off 1v1 mid on identical heroes on the default dota map. The first AI to kill a tower or get two kills on the other AI wins.
14 |
15 | Possible future challenges are:
16 | * Farm optimization challenge
17 | * Three versus three mid and jungle.
18 | * 1v1v1v1
19 | * Last hit challenge
20 |
21 | ## Documentation
22 | Framework AI only has access to a limited subset of the regular dota 2 lua AI. The available functions can be found here:
23 | * [Global functions](https://github.com/ModDota/Dota2AIFramework/wiki/Global-AI-API)
24 | * [Unit functions](https://github.com/ModDota/Dota2AIFramework/wiki/Unit-AI-API)
25 | * [Ability functions](https://github.com/ModDota/Dota2AIFramework/wiki/Ability-AI-AI)
26 | * AIEvents
27 | * AIPlayerResource
28 |
29 | ## Using framework AI in a custom game
30 | To use AI from this framework in a custom game, simply copy the entire scripts/vscripts/AI/ directory, then require AIManager in your gamemode. An existing AI can then be attached to an existing unit using:
31 | ```lua
32 | AIManager:AttachAI( 'ai_name', unit )
33 | ```
34 | This will load the AI named ai_name from AI/UserAI/ai_name and attach it to unit.
35 |
--------------------------------------------------------------------------------