在第一篇教程中,我们主要介绍了Dota2 AI的基本情况,在这篇文章中,我们将介绍如何为AI选择阵容和技能使用(以宙斯为例)。
在官方开发者维基中有着这样的说明:
如果你想控制英雄选择和分路,你可以在文件名为hero_selection.lua的文件中实现如下函数:
Think() - 每帧被调用。负责机器人选择英雄。
UpdateLaneAssignments() - 在游戏开始前的每一帧被调用。返回玩家ID-分路的值对。
GetBotNames() - 调用一次,返回一个包含玩家名称的表。
在dota 2 beta\game\dota\scripts\vscripts\bots_example
V社的示例文件中,我们可以找到hero_selection.lua
文件,这就是为AI选择阵容的脚本文件。
如果我们要开始制作自己的AI,那么首先需要创建dota 2 beta\game\dota\scripts\vscripts\bots
文件夹,之后建立hero_selection.lua
文件,在其中编写选择阵容的部分,这样的话AI就会选择自定义阵容了。
打开这个文件,我们可以看到如下内容
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()是主函数,游戏选择阵容时就会调用这个函数,然后判断是天辉方还是夜魇方,之后分别选择各自的阵容。那么我们如何找到英雄对应的名字呢?在这里便可找到英雄的实际名称。
例如宙斯的实际名称是"npc_dota_hero_zuus"
,那么我们将夜魇方的SelectHero( 5, "npc_dota_hero_drow_ranger" );
替换为SelectHero( 5, "npc_dota_hero_zuus" );
,那么AI便会选择在5号玩家的位置上选择宙斯。需要注意的是,玩家的ID是会随着人类玩家的加入而发生改变的,将玩家ID硬编码是一种不好的做法,比较好的方法是通过循环遍历出玩家的ID,这样无论人类玩家有多少,AI都能正常选择阵容。
下面便是能够自动适应玩家变化选择阵容的代码。
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,可以用作数组)保存可以选择的英雄列表,然后随机出一个序号,选出表中的英雄。
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++端调用,所以需要返回一个分路情况的表。
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楼上路。当然,如果要根据英雄的特性分路,那么需要加入更多判断。如果没有自己写好分路系统的话,可以暂时启用默认分路系统,即不编写这个函数。
在官方开发者维基中有着这样的说明:
如果你只想重写在技能和物品使用时的决策,你可以在文件名为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\npc\npc_heroes.txt
中获取。
例如宙斯的技能为
--以下为宙斯的技能名称
"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",
如果通过原始方法记录,则需要排列组合以上的技能天赋。比较好的方法则是用两个表分别保存技能和天赋的名称,然后在一个统一的表中引用技能天赋名称,以存储加点顺序。
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],
}
至此,英雄的技能天赋名称和加点顺序都已完成存储,接下来只需要在函数中引用加点顺序即可完成加点。
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中获取技能天赋名称也有点麻烦,那么有什么更简便的方法呢?
当然有,比如通过遍历英雄的技能槽来获取每一个技能和天赋的名称。
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
,我们可以看到如下代码
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个部分:
1. 主思考函数
2. 判断技能能否对目标使用
3. 考虑每一个技能
调用关系为:主思考函数->考虑每一个技能->判断技能能否对目标使用
在看完了示例代码后,我们应该对英雄的技能使用有了一个初步的了解,那么接下来便介绍一下如何从零开始开发一个新的英雄。(以宙斯为例)
首先先编写主思考函数,每一个英雄的主思考函数都是相似的,只需要处理每个技能不同类型的目标即可。
在函数开头用local npcBot = GetBot()
记录此英雄的句柄,方便后面的调用。
之后需要判断英雄能否使用技能,是正在施法,还是被沉默了。
if ( npcBot:IsUsingAbility() or npcBot:IsChanneling() or npcBot:IsSilenced() )
then
return
end
然后获取一些常用的量
AttackRange=npcBot:GetAttackRange() --获取攻击距离
ManaPercentage=npcBot:GetMana()/npcBot:GetMaxMana() --获取当前魔法百分比
HealthPercentage=npcBot:GetHealth()/npcBot:GetMaxHealth() --获取当前生命百分比
然后考虑每一个技能的使用,Lua语言支持多重返回值,所以能够方便地从函数获取结果。
cast1Desire, cast1Target = Consider1(); --1技能为单位目标技能
cast2Desire, cast2Target, cast2TargetType = Consider2(); --2技能为单位目标技能或地点目标技能
cast5Desire = Consider5(); --大招为无目标技能
cast4Desire, cast4Location = Consider4(); --5技能(A杖技能)是地点目标技能
最后使用每个技能,从上到下,为技能使用的优先级。当然我们也可以通过比较每个技能的使用欲望,选择欲望值最高的技能使用。
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];
记录此技能的句柄。
然后需要判断这个技能能否使用,即魔法够不够,冷却有没有好。
if not ability:IsFullyCastable() then
return BOT_ACTION_DESIRE_NONE, 0;
end
之后也是记录一些常用的量,以免重复获取降低效率。
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) 团战使用
当英雄模式处于攻击时,判断蓝量是否足够,敌方单位数量是否足够,然后找到最脆弱的敌方单位或者英雄,判断它们能不能被技能作用,最后将这个单位的句柄返回给上层主思考函数。
--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) 线上正补
--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) 发育打钱
-- 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) 推进防守
-- 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) 通用
对模式决定的当前目标使用技能
-- 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,和空单位,以防出现错误。
return BOT_ACTION_DESIRE_NONE, 0;
2.宙斯2技能与之前类似的部分便不再详细指出。
(1)打断目标
本技能有打断功能,应打断敌方英雄的吟唱。
-- 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)击杀附近可以击杀的敌人
-- 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)保护自己,消耗靠近英雄的敌人
--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)消耗敌方英雄
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.宙斯雷云
放在防御塔附近,或敌方英雄附近。
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])500)
then
return BOT_ACTION_DESIRE_LOW,tower[1]:GetLocation()
end
if (WeakestEnemy~=nil)
then
return BOT_ACTION_DESIRE_LOW,WeakestEnemy:GetLocation()
end
其实这部分很简单,就是判断目标是否魔法免疫,是否无敌之类的,以防止英雄重复尝试对无效目标使用技能。
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)函数
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()then
LowestHealth=unit:GetHealth();
WeakestUnit=unit;
end
end
end
return WeakestUnit,LowestHealth
end
总算写完了,真的是很久没有写这么长一篇文章了,希望大家能有所启发。在下一篇文章中,我会为大家介绍如何配置AI出装。
所有代码已测试运行通过
1.示例代码