在[第一篇教程](https://www.adamqqq.com/ai/dota2-ai-devlopment-tutorial.html "第一篇教程")中,我们主要介绍了Dota2 AI的基本情况,在这篇文章中,我们将介绍如何为AI选择阵容和技能使用(以宙斯为例)。
选择阵容
在[官方开发者维基](https://developer.valvesoftware.com/wiki/Dota_Bot_Scripting "官方开发者维基")中有着这样的说明:
如果你想控制英雄选择和分路,你可以在文件名为hero_selection.lua的文件中实现如下函数:
Think() - 每帧被调用。负责机器人选择英雄。
UpdateLaneAssignments() - 在游戏开始前的每一帧被调用。返回玩家ID-分路的值对。
GetBotNames() - 调用一次,返回一个包含玩家名称的表。
在dota 2 beta\game\dota\scripts\vscripts\bots_exampleV社的示例文件中,我们可以找到hero_selection.lua文件,这就是为AI选择阵容的脚本文件。
如果我们要开始制作自己的AI,那么首先需要创建dota 2 beta\game\dota\scripts\vscripts\bots文件夹,之后建立hero_selection.lua文件,在其中编写选择阵容的部分,这样的话AI就会选择自定义阵容了。
打开这个文件,我们可以看到如下内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| function Think()
if ( GetTeam() == TEAM_RADIANT ) --处于天辉方时
then
print( \"selecting radiant\" ); --在控制台输出调试信息
SelectHero( 0, \"npc_dota_hero_antimage\" ); --为0号玩家选择敌法师
SelectHero( 1, \"npc_dota_hero_axe\" );
SelectHero( 2, \"npc_dota_hero_bane\" );
SelectHero( 3, \"npc_dota_hero_bloodseeker\" );
SelectHero( 4, \"npc_dota_hero_crystal_maiden\" );
elseif ( GetTeam() == TEAM_DIRE )
then
print( \"selecting dire\" );
SelectHero( 5, \"npc_dota_hero_drow_ranger\" );
SelectHero( 6, \"npc_dota_hero_earthshaker\" );
SelectHero( 7, \"npc_dota_hero_juggernaut\" );
SelectHero( 8, \"npc_dota_hero_mirana\" );
SelectHero( 9, \"npc_dota_hero_nevermore\" );
end
end
|
其中Think()是主函数,游戏选择阵容时就会调用这个函数,然后判断是天辉方还是夜魇方,之后分别选择各自的阵容。那么我们如何找到英雄对应的名字呢?在[这里](http://dota2-zh.gamepedia.com/作弊指令 "这里")便可找到英雄的实际名称。
例如宙斯的实际名称是\"npc_dota_hero_zuus\",那么我们将夜魇方的SelectHero( 5, \"npc_dota_hero_drow_ranger\" );替换为SelectHero( 5, \"npc_dota_hero_zuus\" );,那么AI便会选择在5号玩家的位置上选择宙斯。需要注意的是,玩家的ID是会随着人类玩家的加入而发生改变的,将玩家ID硬编码是一种不好的做法,比较好的方法是通过循环遍历出玩家的ID,这样无论人类玩家有多少,AI都能正常选择阵容。
下面便是能够自动适应玩家变化选择阵容的代码。
1
2
3
4
5
6
7
8
9
| function Think()
for i,id in pairs(GetTeamPlayers(GetTeam())) --Lua语言自带的迭代器pairs,能够依次读取表中的值。
do
if(IsPlayerBot(id)) --判断该玩家是否为电脑
then
SelectHero( id, \"npc_dota_hero_drow_ranger\" ); --为id号玩家选择黑暗游侠
end
end
end
|
如果我们要AI能够选择随机的阵容,而不是仅仅选择10个黑暗游侠,那该怎么办呢?很简单,通过Lua语言唯一的数据结构表(称作Table,可以用作数组)保存可以选择的英雄列表,然后随机出一个序号,选出表中的英雄。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| hero_pool_my=
{
'npc_dota_hero_zuus',
'npc_dota_hero_skywrath_mage',
'npc_dota_hero_ogre_magi',
'npc_dota_hero_chaos_knight',
'npc_dota_hero_viper',
\"npc_dota_hero_lina\",
\"npc_dota_hero_kunkka\",
\"npc_dota_hero_lich\",
\"npc_dota_hero_shadow_shaman\",
\"npc_dota_hero_centaur\",
'npc_dota_hero_crystal_maiden',
}
function Think()
for i,id in pairs(GetTeamPlayers(GetTeam()))
do
if(IsPlayerBot(id) and (GetSelectedHeroName(id)==\"\" or GetSelectedHeroName(id)==nil))
then
local num=hero_pool_my[RandomInt(1, #hero_pool_my)] --取随机数
SelectHero( id, num ); --在保存英雄名称的表中,随机选择出AI的英雄
table.remove(hero_pool_my,num) --移除这个英雄
end
end
end
|
以上代码会为两边的AI依次从hero_pool_my表中随机选出5个英雄。如果要让AI不选择重复的英雄,那么还需要进一步的修改。
选择分路
为AI分路的函数也是在hero_selection.lua中,如果我们想让AI遵循自定义的分路,那么需要在这个文件里新建一个UpdateLaneAssignments()函数。这个函数是用于游戏底层C++端调用,所以需要返回一个分路情况的表。
1
2
3
4
5
6
7
8
9
10
11
12
| function UpdateLaneAssignments()
local npcBot = GetBot()
local lanes=
{
[1]=LANE_MID,
[2]=LANE_BOT,
[3]=LANE_TOP,
[4]=LANE_BOT,
[5]=LANE_TOP,
}
return lanes;
end
|
以上函数根据AI的位置分路,1楼中路,2楼下路,3楼上路,4楼下路,5楼上路。当然,如果要根据英雄的特性分路,那么需要加入更多判断。如果没有自己写好分路系统的话,可以暂时启用默认分路系统,即不编写这个函数。
技能加点
在[官方开发者维基](https://developer.valvesoftware.com/wiki/Dota_Bot_Scripting "官方开发者维基")中有着这样的说明:
如果你只想重写在技能和物品使用时的决策,你可以在文件名为ability_item_usage_generic.lua的文件中实现如下函数:
ItemUsageThink() - 每帧被调用。负责发出物品使用行为。
AbilityUsageThink() - 每帧被调用。负责发出技能使用行为。
CourierUsageThink() - 每帧被调用。负责发出信使的相关命令。
BuybackUsageThink() - 每帧被调用。负责发出买活指令。
AbilityLevelUpThink() - 每帧被调用。负责升级技英雄能。
这些函数中未被重写的,会自动采用默认的C++实现。
你也可以仅重写某个特定英雄对技能/物品使用的逻辑,比如火女(Lina),写在文件名为ability_item_usage_lina.lua的文件中。如果你想在特定英雄对技能/物品使用的逻辑重写时调用泛用的逻辑代码,请参考附件A的实现细节。
我们要实现的第一个英雄为宙斯,那么就可以新建一个ability_item_usage_zuus.lua的文件。首先要实现AI的加点,就要在此文件中编写AbilityLevelUpThink()函数。然后通过 npcBot:ActionImmediate_LevelAbility(abilityname);升级名称为abilityname的技能。那么怎样获取升级技能所需的技能名称呢?比较原始的方法是用一个表记录从1到25级所有的技能名称。其中的数据要在dota 2 beta\game\dota\scripts\ pc\ pc_heroes.txt中获取。
例如宙斯的技能为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| --以下为宙斯的技能名称
\"zuus_arc_lightning\",
\"zuus_lightning_bolt\",
\"zuus_static_field\",
\"zuus_cloud\",
\"zuus_thundergods_wrath\",
--以下为某个版本的天赋,请注意天赋名称会随着版本更新而变化,所以以下部分可能在当前版本中无效
\"special_bonus_mp_regen_2\",
\"special_bonus_movement_speed_25\",
\"special_bonus_armor_7\",
\"special_bonus_magic_resistance_15\",
\"special_bonus_unique_zeus_2\",
\"special_bonus_unique_zeus_3\",
\"special_bonus_cast_range_200\",
\"special_bonus_unique_zeus\",
|
如果通过原始方法记录,则需要排列组合以上的技能天赋。比较好的方法则是用两个表分别保存技能和天赋的名称,然后在一个统一的表中引用技能天赋名称,以存储加点顺序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
| local Talents =
{
\"special_bonus_mp_regen_2\",
\"special_bonus_movement_speed_25\",
\"special_bonus_armor_7\",
\"special_bonus_magic_resistance_15\",
\"special_bonus_unique_zeus_2\",
\"special_bonus_unique_zeus_3\",
\"special_bonus_cast_range_200\",
\"special_bonus_unique_zeus\",
}
local Abilities =
{
\"zuus_arc_lightning\",
\"zuus_lightning_bolt\",
\"zuus_static_field\",
\"zuus_cloud\",
\"zuus_thundergods_wrath\",
}
--存储技能句柄,方便调用
local npcBot = GetBot()
local AbilitiesReal =
{
npcBot:GetAbilityByName(Abilities[1]),
npcBot:GetAbilityByName(Abilities[2]),
npcBot:GetAbilityByName(Abilities[3]),
npcBot:GetAbilityByName(Abilities[4]),
npcBot:GetAbilityByName(Abilities[5])
}
--存储加点顺序
local AbilityToLevelUp=
{
Abilities[1],
Abilities[3],
Abilities[2],
Abilities[2],
Abilities[2],
Abilities[5],
Abilities[2],
Abilities[1],
Abilities[1],
Talents[1],
Abilities[1],
Abilities[5],
Abilities[3],
Abilities[3],
Talents[3],
Abilities[3],
Abilities[5],
Talents[5],
Talents[7],
}
|
至此,英雄的技能天赋名称和加点顺序都已完成存储,接下来只需要在函数中引用加点顺序即可完成加点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| function AbilityLevelUpThink()
local npcBot = GetBot();
if npcBot:GetAbilityPoints()<1 or #AbilityToLevelUp==0--当没有技能点时,或没有技能可升时不继续执行。
then
return;
end
local ability=npcBot:GetAbilityByName(AbilityToLevelUp[1]); --获取技能句柄
if ability~=nil and ability:CanAbilityBeUpgraded() --判断技能可否升级
then
npcBot:ActionImmediate_LevelAbility(AbilityToLevelUp[1]); --升级技能
table.remove( AbilityToLevelUp, 1 ); --从表中移除刚刚升级的技能
end
end
|
我们之前说过,天赋名称会随着版本更新而不断变化,而且每次手动从npc_heroes.txt中获取技能天赋名称也有点麻烦,那么有什么更简便的方法呢?
当然有,比如通过遍历英雄的技能槽来获取每一个技能和天赋的名称。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| local Talents ={}
local Abilities ={}
local npcBot = GetBot()
for i=0,23,1 do --由于卡尔有24个技能,所以需要从0遍历至23
local ability=npcBot:GetAbilityInSlot(i)
if(ability~=nil)
then
if(ability:IsTalent()==true)
then
table.insert(Talents,ability:GetName())
else
table.insert(Abilities,ability:GetName())
end
end
end
|
这样的话,我们每次新编写一个英雄,就不用手动去复制技能名称了。
使用技能
示例代码
如果要重新编写一个英雄的技能使用,则需要编写AbilityUsageThink()函数。
打开示例文件ability_item_usage_lina.lua,我们可以看到如下代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
| castLBDesire = 0;
castLSADesire = 0;
castDSDesire = 0;
function AbilityUsageThink()
local npcBot = GetBot();
-- Check if we're already using an ability 检查我们是否正在使用技能
if ( npcBot:IsUsingAbility() ) then return end;
abilityLSA = npcBot:GetAbilityByName( \"lina_light_strike_array\" );
abilityDS = npcBot:GetAbilityByName( \"lina_dragon_slave\" );
abilityLB = npcBot:GetAbilityByName( \"lina_laguna_blade\" );
-- Consider using each ability 考虑使用每一个技能,得到使用技能的欲望值和技能目标
castLBDesire, castLBTarget = ConsiderLagunaBlade();
castLSADesire, castLSALocation = ConsiderLightStrikeArray();
castDSDesire, castDSLocation = ConsiderDragonSlave();
if ( castLBDesire > castLSADesire and castLBDesire > castDSDesire )
then
npcBot:Action_UseAbilityOnEntity( abilityLB, castLBTarget );
return;
end
if ( castLSADesire > 0 )
then
npcBot:Action_UseAbilityOnLocation( abilityLSA, castLSALocation );
return;
end
if ( castDSDesire > 0 )
then
npcBot:Action_UseAbilityOnLocation( abilityDS, castDSLocation );
return;
end
end
----------------------------------------------------------------------------------------------------
--判断技能能否对目标使用
function CanCastLightStrikeArrayOnTarget( npcTarget )
return npcTarget:CanBeSeen() and not npcTarget:IsMagicImmune() and not npcTarget:IsInvulnerable();
end
function CanCastDragonSlaveOnTarget( npcTarget )
return npcTarget:CanBeSeen() and not npcTarget:IsMagicImmune() and not npcTarget:IsInvulnerable();
end
function CanCastLagunaBladeOnTarget( npcTarget )
return npcTarget:CanBeSeen() and npcTarget:IsHero() and ( GetBot():HasScepter() or not npcTarget:IsMagicImmune() ) and not npcTarget:IsInvulnerable();
end
----------------------------------------------------------------------------------------------------
--考虑是否使用2技能
function ConsiderLightStrikeArray()
local npcBot = GetBot();
-- Make sure it's castable 确认技能能否使用
if ( not abilityLSA:IsFullyCastable() )
then
return BOT_ACTION_DESIRE_NONE, 0;
end;
-- If we want to cast Laguna Blade at all, bail 如果要使用大招,那么先不用这个技能
if ( castLBDesire > 0 )
then
return BOT_ACTION_DESIRE_NONE, 0;
end
-- Get some of its values 获取技能相关的参数
local nRadius = abilityLSA:GetSpecialValueInt( \"light_strike_array_aoe\" );
local nCastRange = abilityLSA:GetCastRange();
local nDamage = abilityLSA:GetAbilityDamage();
--------------------------------------
-- Global high-priorty usage
--------------------------------------
-- Check for a channeling enemy 打断正在吟唱的敌人
local tableNearbyEnemyHeroes = npcBot:GetNearbyHeroes( nCastRange + nRadius + 200, true, BOT_MODE_NONE );
for _,npcEnemy in pairs( tableNearbyEnemyHeroes )
do
if ( npcEnemy:IsChanneling() )
then
return BOT_ACTION_DESIRE_HIGH, npcEnemy:GetLocation();
end
end
--------------------------------------
-- Mode based usage
--------------------------------------
-- If we're farming and can kill 3+ creeps with LSA 收兵线打钱
if ( npcBot:GetActiveMode() == BOT_MODE_FARM ) then
local locationAoE = npcBot:FindAoELocation( true, false, npcBot:GetLocation(), nCastRange, nRadius, 0, nDamage );
if ( locationAoE.count >= 3 ) then
return BOT_ACTION_DESIRE_LOW, locationAoE.targetloc;
end
end
-- If we're pushing or defending a lane and can hit 4+ creeps, go for it 推进与防守
if ( npcBot:GetActiveMode() == BOT_MODE_PUSH_TOWER_TOP or
npcBot:GetActiveMode() == BOT_MODE_PUSH_TOWER_MID or
npcBot:GetActiveMode() == BOT_MODE_PUSH_TOWER_BOT or
npcBot:GetActiveMode() == BOT_MODE_DEFEND_TOWER_TOP or
npcBot:GetActiveMode() == BOT_MODE_DEFEND_TOWER_MID or
npcBot:GetActiveMode() == BOT_MODE_DEFEND_TOWER_BOT )
then
local locationAoE = npcBot:FindAoELocation( true, false, npcBot:GetLocation(), nCastRange, nRadius, 0, 0 );
if ( locationAoE.count >= 4 )
then
return BOT_ACTION_DESIRE_LOW, locationAoE.targetloc;
end
end
-- If we're seriously retreating, see if we can land a stun on someone who's damaged us recently 逃跑时阻止敌人追击
if ( npcBot:GetActiveMode() == BOT_MODE_RETREAT and npcBot:GetActiveModeDesire() >= BOT_MODE_DESIRE_HIGH )
then
local tableNearbyEnemyHeroes = npcBot:GetNearbyHeroes( nCastRange + nRadius + 200, true, BOT_MODE_NONE );
for _,npcEnemy in pairs( tableNearbyEnemyHeroes )
do
if ( npcBot:WasRecentlyDamagedByHero( npcEnemy, 2.0 ) )
then
if ( CanCastLightStrikeArrayOnTarget( npcEnemy ) )
then
return BOT_ACTION_DESIRE_MODERATE, npcEnemy:GetLocation();
end
end
end
end
-- If we're going after someone 对当前目标使用技能
if ( npcBot:GetActiveMode() == BOT_MODE_ROAM or
npcBot:GetActiveMode() == BOT_MODE_TEAM_ROAM or
npcBot:GetActiveMode() == BOT_MODE_GANK or
npcBot:GetActiveMode() == BOT_MODE_DEFEND_ALLY )
then
local npcTarget = npcBot:GetTarget();
if ( npcTarget ~= nil )
then
if ( CanCastLightStrikeArrayOnTarget( npcTarget ) )
then
return BOT_ACTION_DESIRE_HIGH, npcTarget:GetLocation();
end
end
end
return BOT_ACTION_DESIRE_NONE, 0; --不使用技能
end
function ConsiderDragonSlave()
--略,考虑是否使用1技能
end
function ConsiderLagunaBlade()
--略,考虑是否使用4技能
end
|
以上便是原版AI的火女技能使用源代码,当然这是由C++代码翻译过来的。我们要编写的代码结构也可以参考这一部分,主要分为3个部分:
- 主思考函数
- 判断技能能否对目标使用
- 考虑每一个技能
调用关系为:主思考函数->考虑每一个技能->判断技能能否对目标使用
实战
在看完了示例代码后,我们应该对英雄的技能使用有了一个初步的了解,那么接下来便介绍一下如何从零开始开发一个新的英雄。(以宙斯为例)
主思考函数
首先先编写主思考函数,每一个英雄的主思考函数都是相似的,只需要处理每个技能不同类型的目标即可。
在函数开头用local npcBot = GetBot()记录此英雄的句柄,方便后面的调用。
之后需要判断英雄能否使用技能,是正在施法,还是被沉默了。
1
2
3
4
| if ( npcBot:IsUsingAbility() or npcBot:IsChanneling() or npcBot:IsSilenced() )
then
return
end
|
然后获取一些常用的量
1
2
3
| AttackRange=npcBot:GetAttackRange() --获取攻击距离
ManaPercentage=npcBot:GetMana()/npcBot:GetMaxMana() --获取当前魔法百分比
HealthPercentage=npcBot:GetHealth()/npcBot:GetMaxHealth() --获取当前生命百分比
|
然后考虑每一个技能的使用,Lua语言支持多重返回值,所以能够方便地从函数获取结果。
1
2
3
4
| cast1Desire, cast1Target = Consider1(); --1技能为单位目标技能
cast2Desire, cast2Target, cast2TargetType = Consider2(); --2技能为单位目标技能或地点目标技能
cast5Desire = Consider5(); --大招为无目标技能
cast4Desire, cast4Location = Consider4(); --5技能(A杖技能)是地点目标技能
|
最后使用每个技能,从上到下,为技能使用的优先级。当然我们也可以通过比较每个技能的使用欲望,选择欲望值最高的技能使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| if ( cast4Desire > 0 )
then
npcBot:Action_UseAbility( AbilitiesReal[4] ); --使用大招
return
end
if ( cast5Desire > 0 )
then
npcBot:Action_UseAbilityOnLocation( AbilitiesReal[5], cast5Location ); --对目标点使用5技能
return
end
if ( cast2Desire > 0 )
then
if(cast2TargetType==\"target\")
then
npcBot:Action_UseAbilityOnEntity( AbilitiesReal[2], cast2Target ); --对单位使用2技能
return
elseif(cast2TargetType==\"location\")
then
npcBot:Action_UseAbilityOnLocation( AbilitiesReal[2], cast2Target ); --对目标点使用2技能
return
end
end
if ( cast1Desire > 0 )
then
npcBot:Action_UseAbilityOnEntity(AbilitiesReal[1], cast1Target ); --对目标单位使用1技能
return
end
|
至此,主思考函数编写完毕。
考虑使用每一个技能
在开始开发一个技能的使用前,我们应该先想想,这个技能有什么用?是一个单体爆发技能,还是一个范围技能?这个技能有没有控制效果?这样的话才好编写接下来的技能。
比如说宙斯的1技能,可以用来补兵,清兵,对英雄造成范围伤害等等。
首先我们也需要获取英雄和技能的句柄。在函数开头用local npcBot = GetBot()记录此英雄的句柄,用local ability=AbilitiesReal[1];记录此技能的句柄。
然后需要判断这个技能能否使用,即魔法够不够,冷却有没有好。
1
2
3
| if not ability:IsFullyCastable() then
return BOT_ACTION_DESIRE_NONE, 0;
end
|
之后也是记录一些常用的量,以免重复获取降低效率。
1
2
3
4
5
6
7
8
9
10
| local CastRange = ability:GetCastRange(); --获取施法距离
local Damage = ability:GetAbilityDamage(); --获取技能伤害,注意这个函数不一定有效...有些特殊的技能无法获取
local HeroHealth=10000
local CreepHealth=10000
local allys = npcBot:GetNearbyHeroes( 1200, false, BOT_MODE_NONE ); --获取周围友方英雄
local enemys = npcBot:GetNearbyHeroes(CastRange+300,true,BOT_MODE_NONE) --获取周围敌方英雄
local WeakestEnemy,HeroHealth=utility.GetWeakestUnit(enemys) --获取血量最低的敌方英雄
local creeps = npcBot:GetNearbyCreeps(CastRange+300,true) --获取周围的敌方小兵
local WeakestCreep,CreepHealth=utility.GetWeakestUnit(creeps) --获取血量最低的敌方小兵
|
注意,在这个地方我调用了utility函数库中的函数,调用函数库模块,需要在脚本开头加入require(GetScriptDirectory() .. \"/utility\")。当然这个utility名字是可以变化的。该函数的实际代码放在文末。
1.之后,便是宙斯1技能的实际用途,我分为了5个部分:
function Consider1()
(1) 团战使用
当英雄模式处于攻击时,判断蓝量是否足够,敌方单位数量是否足够,然后找到最脆弱的敌方单位或者英雄,判断它们能不能被技能作用,最后将这个单位的句柄返回给上层主思考函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| --teamfightUsing
if(npcBot:GetActiveMode() == BOT_MODE_ATTACK )
then
if(ManaPercentage>0.4 and #enemys+#creeps>2) --蓝量大于40%,敌方单位数>2
then
if(WeakestCreep~=nil and CanCast1( WeakestCreep )) --CanCast1是一个自定义函数,用于判断技能能不能对目标使用,请参考下一部分。
then
return BOT_ACTION_DESIRE_HIGH,WeakestCreep;
end
if(WeakestEnemy~=nil and CanCast1( WeakestEnemy ))
then
return BOT_ACTION_DESIRE_HIGH,WeakestEnemy;
end
end
end
|
(2) 线上正补
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| --Last hit
if ( npcBot:GetActiveMode() == BOT_MODE_LANING )
then
if(WeakestCreep~=nil)
then
if(ManaPercentage>0.4 and GetUnitToUnitDistance(npcBot,WeakestCreep)>=AttackRange-ManaPercentage) --蓝量大于40%,而且该单位远离英雄的攻击距离
then
if(CreepHealth<=WeakestCreep:GetActualIncomingDamage(Damage,DAMAGE_TYPE_MAGICAL)) --伤害是否足够
then
return BOT_ACTION_DESIRE_LOW,WeakestCreep;
end
end
end
end
|
(3) 发育打钱
1
2
3
4
5
6
7
8
9
10
11
| -- If we're farming and can hit 2+ creeps and kill 1+
if ( npcBot:GetActiveMode() == BOT_MODE_FARM )
then
if ( #creeps >= 2 )--单位是不是足够多
then
if (CreepHealth<=WeakestCreep:GetActualIncomingDamage(Damage,DAMAGE_TYPE_MAGICAL))--伤害是否足够
then
return BOT_ACTION_DESIRE_LOW, WeakestCreep;
end
end
end
|
(4) 推进防守
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| -- If we're pushing or defending a lane and can hit 3+ creeps, go for it
if ( npcBot:GetActiveMode() == BOT_MODE_PUSH_TOWER_TOP or
npcBot:GetActiveMode() == BOT_MODE_PUSH_TOWER_MID or
npcBot:GetActiveMode() == BOT_MODE_PUSH_TOWER_BOT or
npcBot:GetActiveMode() == BOT_MODE_DEFEND_TOWER_TOP or
npcBot:GetActiveMode() == BOT_MODE_DEFEND_TOWER_MID or
npcBot:GetActiveMode() == BOT_MODE_DEFEND_TOWER_BOT )
then
if ( #enemys+#creeps > 2 and ManaPercentage>0.4)--蓝量大于40%,敌方单位数>2
then
if (WeakestEnemy~=nil)
then
if ( CanCast1( WeakestEnemy )and GetUnitToUnitDistance(npcBot,WeakestEnemy)< CastRange + 75*#allys ) --判断目标,判断英雄的位置是否安全
then
return BOT_ACTION_DESIRE_LOW, WeakestEnemy;
end
end
if (WeakestCreep~=nil)
then
if ( CanCast1( WeakestCreep )and GetUnitToUnitDistance(npcBot,WeakestCreep)< CastRange + 75*#allys )
then
return BOT_ACTION_DESIRE_LOW, WeakestCreep;
end
end
end
end
|
(5) 通用
对模式决定的当前目标使用技能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| -- If we're going after someone
if ( npcBot:GetActiveMode() == BOT_MODE_ROAM or
npcBot:GetActiveMode() == BOT_MODE_TEAM_ROAM or
npcBot:GetActiveMode() == BOT_MODE_DEFEND_ALLY or
npcBot:GetActiveMode() == BOT_MODE_ATTACK )
then
local npcTarget = npcBot:GetTarget();
if(ManaPercentage>0.4)
then
if ( npcTarget ~= nil )
then
if ( CanCast1( npcTarget ) and GetUnitToUnitDistance(npcBot,npcTarget)< CastRange + 75*#allys) --距离是不是足够近
then
return BOT_ACTION_DESIRE_MODERATE, npcTarget;
end
end
end
end
|
(6)如果上面的部分没有获取到合适的目标,返回欲望0,和空单位,以防出现错误。
1
| return BOT_ACTION_DESIRE_NONE, 0;
|
2.宙斯2技能与之前类似的部分便不再详细指出。
(1)打断目标
本技能有打断功能,应打断敌方英雄的吟唱。
1
2
3
4
5
6
7
8
| -- Check for a channeling enemy
for _,enemy in pairs( enemys )
do
if ( enemy:IsChanneling() and CanCast2( enemy ))
then
return BOT_ACTION_DESIRE_HIGH, enemy,\"target\"; --由于本技能有多种目标类型,可以返回类型是什么。
end
end
|
(2)击杀附近可以击杀的敌人
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| -- Kill enemy
if(npcBot:GetActiveMode() ~= BOT_MODE_RETREAT )
then
if (WeakestEnemy~=nil)
then
if(HeroHealth<=WeakestEnemy:GetActualIncomingDamage(Damage,DAMAGE_TYPE_MAGICAL)) --伤害是否足够
then
if ( CanCast2( WeakestEnemy ) )
then
return BOT_ACTION_DESIRE_HIGH,WeakestEnemy,\"target\";
end
end
end
end
|
(3)保护自己,消耗靠近英雄的敌人
1
2
3
4
5
6
7
8
9
10
11
12
| --protect myself
local enemys2 = npcBot:GetNearbyHeroes( 500, true, BOT_MODE_NONE ); --获取500范围内的敌人
if(npcBot:WasRecentlyDamagedByAnyHero(5)) --最近是否被敌人伤害
then
for _,enemy in pairs( enemys2 )
do
if ( CanCast2( enemy ) )
then
return BOT_ACTION_DESIRE_HIGH, enemy,\"target\"
end
end
end
|
(4)消耗敌方英雄
1
2
3
4
5
6
7
8
9
| if(npcBot:GetActiveMode() ~= BOT_MODE_RETREAT )
if(ManaPercentage>0.4 and ability:GetLevel()>=2 ) --蓝量大于40%,技能大于或等于2级
then
if (WeakestEnemy~=nil and CanCast2( WeakestEnemy ))
then
return BOT_ACTION_DESIRE_LOW,WeakestEnemy,\"target\";
end
end
end
|
3.宙斯大招
检测所有敌方英雄的血量,如果能够击杀,且附近没有友方英雄,那么则使用技能。详细代码略。
4.宙斯雷云
放在防御塔附近,或敌方英雄附近。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| local tower=nil
if ( npcBot:GetActiveMode() == BOT_MODE_PUSH_TOWER_TOP or
npcBot:GetActiveMode() == BOT_MODE_PUSH_TOWER_MID or
npcBot:GetActiveMode() == BOT_MODE_PUSH_TOWER_BOT )
then
local tower=npcBot:GetNearbyTowers(1500,false)
end
if (npcBot:GetActiveMode() == BOT_MODE_DEFEND_TOWER_TOP or
npcBot:GetActiveMode() == BOT_MODE_DEFEND_TOWER_MID or
npcBot:GetActiveMode() == BOT_MODE_DEFEND_TOWER_BOT )
then
local tower=npcBot:GetNearbyTowers(1000,true)
end
if(tower~=nil and GetUnitToUnitDistance(npcBot,tower[1])<CastRange+500)
then
return BOT_ACTION_DESIRE_LOW,tower[1]:GetLocation()
end
if (WeakestEnemy~=nil)
then
return BOT_ACTION_DESIRE_LOW,WeakestEnemy:GetLocation()
end
|
判断技能能否对目标使用
其实这部分很简单,就是判断目标是否魔法免疫,是否无敌之类的,以防止英雄重复尝试对无效目标使用技能。
1
2
3
4
5
6
7
8
9
10
11
| function CanCast1( npcTarget )
return npcTarget:CanBeSeen() and not npcTarget:IsMagicImmune() and not npcTarget:IsInvulnerable();
end
function CanCast2( npcTarget )
return not npcTarget:IsMagicImmune() and not npcTarget:IsInvulnerable();
end
function CanCast5( npcTarget )
return npcTarget:IsHero() and not npcTarget:IsMagicImmune() and not npcTarget:IsInvulnerable();
end
|
至此,我们已经完成了一个英雄的技能使用设计,详细代码请参考附件。
参考通用函数
utility.GetWeakestUnit(EnemyUnits)函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| function GetWeakestUnit(EnemyUnits)
if EnemyUnits==nil or #EnemyUnits==0 then
return nil,10000;
end
local WeakestUnit=nil;
local LowestHealth=10000;
for _,unit in pairs(EnemyUnits)
do
if unit~=nil and unit:IsAlive()
then
if unit:GetHealth()<LowestHealth
then
LowestHealth=unit:GetHealth();
WeakestUnit=unit;
end
end
end
return WeakestUnit,LowestHealth
end
|
总结
总算写完了,真的是很久没有写这么长一篇文章了,希望大家能有所启发。在下一篇文章中,我会为大家介绍如何配置AI出装。
附件
所有代码已测试运行通过
1.[示例代码](http://www-10026452.cossh.myqcloud.com/bots_2017_06_07.7z "示例代码.7z")