├── .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 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 118 | 119 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 135 | 136 | 138 | 139 | 140 | 141 | 142 | 143 | 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 | --------------------------------------------------------------------------------