Урок Универсальное поведение нейтралов АИ. Детальный разбор.

vulkantsk

Администратор
Команда форума
21 Июн 2017
1,247
205
www.dotabuff.com
Проект
Roshan defense
Всегда думал о том, что было бы неплохо если бы кто-нибудь сделал такое поведение юнитам, чтобы они агрились как нейтралы и кастовали свои способности в автоматическом режиме...
Ну и каким то магическим образом этим кем-то стал я :( . . .
Поведение юнита подключается в файле юнитов в строчке "vscripts" "ai/neutral"
Чтобы поведение работало корректно, нужно, чтобы у юнита не было поведения нейтрала "UseNeutralCreepBehavior" или было бы "0"

Поведение нейтрала

В общем начнем с простого, поведение нейтрала который агрится вместе со своими братанами и возвращается на стартовую точку в случае, если отойдет далеко от своей стартовой позиции :
Код:
function Spawn( entityKeyValues )
    if not IsServer() then
        return
    end

    if thisEntity == nil then
        return
    end

    thisEntity:SetContextThink( "NeutralThink", NeutralThink, 1 )
end

function NeutralThink()
    if ( not thisEntity:IsAlive() ) then        --если юнит мертв
        return -1
    end

    if GameRules:IsGamePaused() == true then    --если игра приостановлена
        return 1
    end

    if thisEntity:IsChanneling() then    -- если юнит кастует скил
        return 1
    end

    if thisEntity:IsControllableByAnyPlayer() then    -- если юнит принадлежит игроку, то поведение отключается
        return -1
    end

    local npc = thisEntity

    if not thisEntity.bInitialized then
        npc.vInitialSpawnPos = npc:GetOrigin()        -- точка спавна юнита
        npc.fMaxDist = npc:GetAcquisitionRange()    -- радиус агра
        npc.bInitialized = true                        -- флаг инициализации
        npc.agro = false                            -- флаг агра
    
    end

    local search_radius                             -- радиус поиска зависит от того, имеет ли юнит агр
    if npc.agro then
        search_radius = npc.fMaxDist * 1.5            -- расшираяется
    else
        search_radius = npc.fMaxDist                -- становится обычным
    end

    -- Как далеко юнит находится от своей точки спавна ?
    local fDist = ( npc:GetOrigin() - npc.vInitialSpawnPos ):Length2D()
    if fDist > search_radius then
        RetreatHome()            -- если юнит слишком далеко, то идет на точку спавна
        return 3
    end

    local enemies = FindUnitsInRadius(
                        npc:GetTeamNumber(),        --команда юнита
                        npc.vInitialSpawnPos,        --местоположение юнита
                        nil,    --айди юнита (необязательно)
                        search_radius + 50,    --радиус поиска
                        DOTA_UNIT_TARGET_TEAM_ENEMY,    -- юнитов чьей команды ищем вражеской/дружественной
                        DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC,    --юнитов какого типа ищем
                        DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE,    --поиск по флагам
                        FIND_CLOSEST,    --сортировка от ближнего к дальнему
                        false )

    if #enemies == 0 then    -- если найденных юнитов нету
        if npc.agro then
            RetreatHome()    -- если юнит под действием агра
        end   
        return 0.5
    end

    local enemy = enemies[1]    -- врагом выбирается первый близжайший


    if npc.agro then    -- если юнит находится под действием агра
    
        AttackMove(npc, enemy)
--        npc:MoveToPositionAggressive(enemy:GetAbsOrigin())

    else
        local allies = FindUnitsInRadius(    -- ищет всех союзных братков в радиусе
                npc:GetTeamNumber(),
                npc.vInitialSpawnPos,
                nil,
                npc.fMaxDist,
                DOTA_UNIT_TARGET_TEAM_FRIENDLY,
                DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC,
                DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE,
                FIND_CLOSEST,
                false )
            
        for i=1,#allies do    -- заставляет братков быть агрессивными и атаковать врага
            local ally = allies[i]
            ally.agro = true    -- накладывает действие агра
            AttackMove(ally, enemy)
        end
    end
    return 1

end

function AttackMove( unit, enemy )
    if enemy == nil then
        return
    end
--    print("ATTACK MOVE")
    ExecuteOrderFromTable({
        UnitIndex = unit:entindex(),                --индекс кастера
        OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,    -- тип приказа атака
        Position = enemy:GetOrigin(),                -- пощиция врага
        Queue = false,
    })

    return 1
end

function RetreatHome()
    thisEntity.agro = false    -- снимается действие агра

    ExecuteOrderFromTable({
        UnitIndex = thisEntity:entindex(),
        OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
        Position = thisEntity.vInitialSpawnPos   
    })
end

Поведение автокастера
Теперь разберем поведение автокастера, который использует свои способности, если подойти к нему на расстояние агра:
Код:
function Spawn( entityKeyValues )
    if not IsServer() then
        return
    end

    if thisEntity == nil then
        return
    end
   
    thisEntity:SetContextThink( "AutoCasterThink", AutoCasterThink, 1 )
end

function AutoCasterThink()
    if ( not thisEntity:IsAlive() ) then        --если юнит мертв
        return -1  
    end
   
    if GameRules:IsGamePaused() == true then    --если игра приостановлена
        return 1  
    end

    if thisEntity:IsChanneling() then    -- если юнит кастует скил
        return 1  
    end
   
    if thisEntity:IsControllableByAnyPlayer() then    -- если юнит принадлежит игроку, то поведение отключается
        return -1
    end
   
    local npc = thisEntity

    if not thisEntity.bInitialized then
        npc.fMaxDist = npc:GetAcquisitionRange()    -- радиус агра
        npc.bInitialized = true                        -- флаг инициализации
       
        npc.ability0 = FindAbility(npc, 0)            -- ищет способность по индексу
        npc.ability1 = FindAbility(npc, 1)            -- ищет способность по индексу
        npc.ability2 = FindAbility(npc, 2)            -- ищет способность по индексу
        npc.ability3 = FindAbility(npc, 3)            -- ищет способность по индексу
        npc.ability4 = FindAbility(npc, 4)            -- ищет способность по индексу
        npc.ability5 = FindAbility(npc, 5)            -- ищет способность по индексу
       
    end

    local search_radius = npc.fMaxDist        -- радиус поиска зависит от того, имеет ли юнит агр
   
    local enemies = FindUnitsInRadius(
                        npc:GetTeamNumber(),        --команда юнита
                        npc:GetAbsOrigin(),        --местоположение юнита
                        nil,    --айди юнита (необязательно)
                        search_radius + 50,    --радиус поиска
                        DOTA_UNIT_TARGET_TEAM_ENEMY,    -- юнитов чьей команды ищем вражеской/дружественной
                        DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC,    --юнитов какого типа ищем
                        DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE,    --поиск по флагам
                        FIND_CLOSEST,    --сортировка от ближнего к дальнему
                        false )

    local enemy = enemies[1]    -- врагом выбирается первый близжайший
   
    TryCastAbility(npc.ability0, npc, enemy)    -- попытка использовать способность
    TryCastAbility(npc.ability1, npc, enemy)    -- попытка использовать способность
    TryCastAbility(npc.ability2, npc, enemy)    -- попытка использовать способность
    TryCastAbility(npc.ability3, npc, enemy)    -- попытка использовать способность
    TryCastAbility(npc.ability4, npc, enemy)    -- попытка использовать способность
    TryCastAbility(npc.ability5, npc, enemy)    -- попытка использовать способность

    return 1
   
end

function FindAbility(unit, index)
    local ability = unit:GetAbilityByIndex(index)
   
    if ability then
        local ability_behavior = ability:GetBehaviorInt()
       
        if bit.band( ability_behavior, DOTA_ABILITY_BEHAVIOR_PASSIVE ) == DOTA_ABILITY_BEHAVIOR_PASSIVE then
            ability.behavior = "passive"    -- способность пассивна
        elseif bit.band( ability_behavior, DOTA_ABILITY_BEHAVIOR_UNIT_TARGET ) == DOTA_ABILITY_BEHAVIOR_UNIT_TARGET then
            ability.behavior = "target"        -- способность направлена на юнита
        elseif bit.band( ability_behavior, DOTA_ABILITY_BEHAVIOR_NO_TARGET ) == DOTA_ABILITY_BEHAVIOR_NO_TARGET then
            ability.behavior = "no_target"    -- способность без цели
        elseif bit.band( ability_behavior, DOTA_ABILITY_BEHAVIOR_POINT ) == DOTA_ABILITY_BEHAVIOR_POINT then
            ability.behavior = "point"        -- способность направлена на точку
        end
--        print("ability #"..index.." name = "..ability:GetAbilityName())
--        print("ability behavior = "..ability.behavior)
       
        return ability
    else
--        print("ability #"..index.."not found !!!")
        return nil
    end
   
end

function TryCastAbility(ability, caster, enemy)
   
    if ability == nil -- способность существует ?
    or  not ability:IsFullyCastable()     -- способность можно использовать?
    or ability.behavior == "passive"     -- способность пассивна ?
    or  enemy:IsMagicImmune()  then        -- цель имеет уммунитет к магии ?
        return
    end
   
    local order_type
--[[  
    print("CAST ABIITY")
    print("ability behavior = "..ability.behavior)
    print("enemy = "..enemy:GetUnitName())
    print("caster = "..caster:GetUnitName())
]]  
    -- теперь определяется каким образом будет использованна способность
   
    if ability.behavior == "target" then
        order_type = DOTA_UNIT_ORDER_CAST_TARGET    -- на цель
    elseif ability.behavior == "no_target" then
        order_type = DOTA_UNIT_ORDER_CAST_NO_TARGET    -- без цели
    elseif ability.behavior == "point" then
        order_type = DOTA_UNIT_ORDER_CAST_POSITION    -- на точку
    elseif ability.behavior == "passive" then
        return
    end
   
    ExecuteOrderFromTable({
        UnitIndex = caster:entindex(),        -- индекс кастера
        OrderType = order_type,                -- тип приказа
        AbilityIndex = ability:entindex(),    -- индекс способности
        TargetIndex = enemy:entindex(),     -- индекс врага
        Position = enemy:GetOrigin(),         -- положение врага
        Queue = false,                        -- ждать очереди ?
    })
    caster:SetContextThink( "AutoCasterThink", AutoCasterThink, 1 ) -- если способность использована, то поведение начинается заного
   
end

А теперь мы делаем волшебство, соединяя поведение нейтрала и автокастера ...
cqlDeeZ.png


Поведение нейтрала автокастера
Венец эволюции двух видов , который содержит в себе лучшие особенности предков и справляется со всеми задачами обоих (или по крайней мере так думает). Он далеко не идеален , но как говорится "Хороша ложка к обеду". Так и поведение сглаживает углы неидеальности цифрового мира...
Код:
function Spawn( entityKeyValues )
    if not IsServer() then
        return
    end

    if thisEntity == nil then
        return
    end
   
    thisEntity:SetContextThink( "NeutralAutoCasterThink", NeutralAutoCasterThink, 1 )
end

function NeutralAutoCasterThink()
    if ( not thisEntity:IsAlive() ) then        --если юнит мертв
        return -1  
    end
   
    if GameRules:IsGamePaused() == true then    --если игра приостановлена
        return 1  
    end

    if thisEntity:IsChanneling() then    -- если юнит кастует скил
        return 1  
    end
   
    if thisEntity:IsControllableByAnyPlayer() then    -- если юнит принадлежит игроку, то поведение отключается
        return -1
    end
   
    local npc = thisEntity

    if not thisEntity.bInitialized then
        npc.vInitialSpawnPos = npc:GetOrigin()        -- точка спавна юнита
        npc.fMaxDist = npc:GetAcquisitionRange()    -- радиус агра
        npc.bInitialized = true                        -- флаг инициализации
        npc.agro = false                            -- флаг агра
       
        npc.ability0 = FindAbility(npc, 0)            -- ищет способность по индексу
        npc.ability1 = FindAbility(npc, 1)            -- ищет способность по индексу
        npc.ability2 = FindAbility(npc, 2)            -- ищет способность по индексу
        npc.ability3 = FindAbility(npc, 3)            -- ищет способность по индексу
        npc.ability4 = FindAbility(npc, 4)            -- ищет способность по индексу
        npc.ability5 = FindAbility(npc, 5)            -- ищет способность по индексу
       
    end

    local search_radius                             -- радиус поиска зависит от того, имеет ли юнит агр
    if npc.agro then
        search_radius = npc.fMaxDist * 1.5            -- расшираяется
    else
        search_radius = npc.fMaxDist                -- становится обычным
    end
   
    -- Как далеко юнит находится от своей точки спавна ?
    local fDist = ( npc:GetOrigin() - npc.vInitialSpawnPos ):Length2D()
    if fDist > search_radius then
        RetreatHome()            -- если юнит слишком далеко, то идет на точку спавна
        return 3
    end
   
    local enemies = FindUnitsInRadius(
                        npc:GetTeamNumber(),        --команда юнита
                        npc.vInitialSpawnPos,        --местоположение юнита
                        nil,    --айди юнита (необязательно)
                        search_radius + 50,    --радиус поиска
                        DOTA_UNIT_TARGET_TEAM_ENEMY,    -- юнитов чьей команды ищем вражеской/дружественной
                        DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC,    --юнитов какого типа ищем
                        DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE,    --поиск по флагам
                        FIND_CLOSEST,    --сортировка от ближнего к дальнему
                        false )

    if #enemies == 0 then    -- если найденных юнитов нету
        if npc.agro then
            RetreatHome()    -- если юнит под действием агра
        end      
        return 0.5
    end
   
    local enemy = enemies[1]    -- врагом выбирается первый близжайший
   
   
    if npc.agro then    -- если юнит находится под действием агра
       
        AttackMove(npc, enemy)
--        npc:MoveToPositionAggressive(enemy:GetAbsOrigin())

        TryCastAbility(npc.ability0, npc, enemy)    -- попытка использовать способность
        TryCastAbility(npc.ability1, npc, enemy)    -- попытка использовать способность
        TryCastAbility(npc.ability2, npc, enemy)    -- попытка использовать способность
        TryCastAbility(npc.ability3, npc, enemy)    -- попытка использовать способность
        TryCastAbility(npc.ability4, npc, enemy)    -- попытка использовать способность
        TryCastAbility(npc.ability5, npc, enemy)    -- попытка использовать способность
    else
        local allies = FindUnitsInRadius(    -- ищет всех союзных братков в радиусе
                npc:GetTeamNumber(),
                npc.vInitialSpawnPos,
                nil,
                npc.fMaxDist,
                DOTA_UNIT_TARGET_TEAM_FRIENDLY,
                DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC,
                DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE,
                FIND_CLOSEST,
                false )
               
        for i=1,#allies do    -- заставляет братков быть агрессивными и атаковать врага
            local ally = allies[i]
            ally.agro = true    -- накладывает действие агра
            AttackMove(ally, enemy)  
        end  
    end  
    return 1
   
end

function FindAbility(unit, index)
    local ability = unit:GetAbilityByIndex(index)
   
    if ability then
        local ability_behavior = ability:GetBehaviorInt()
       
        if bit.band( ability_behavior, DOTA_ABILITY_BEHAVIOR_PASSIVE ) == DOTA_ABILITY_BEHAVIOR_PASSIVE then
            ability.behavior = "passive"    -- способность пассивна
        elseif bit.band( ability_behavior, DOTA_ABILITY_BEHAVIOR_UNIT_TARGET ) == DOTA_ABILITY_BEHAVIOR_UNIT_TARGET then
            ability.behavior = "target"        -- способность направлена на юнита
        elseif bit.band( ability_behavior, DOTA_ABILITY_BEHAVIOR_NO_TARGET ) == DOTA_ABILITY_BEHAVIOR_NO_TARGET then
            ability.behavior = "no_target"    -- способность без цели
        elseif bit.band( ability_behavior, DOTA_ABILITY_BEHAVIOR_POINT ) == DOTA_ABILITY_BEHAVIOR_POINT then
            ability.behavior = "point"        -- способность направлена на точку
        end
--        print("ability #"..index.." name = "..ability:GetAbilityName())
--        print("ability behavior = "..ability.behavior)
       
        return ability
    else
--        print("ability #"..index.."not found !!!")
        return nil
    end
   
end

function TryCastAbility(ability, caster, enemy)
   
    if ability == nil -- способность существует ?
    or  not ability:IsFullyCastable()     -- способность можно использовать?
    or ability.behavior == "passive"     -- способность пассивна ?
    or  enemy:IsMagicImmune()  then        -- цель имеет уммунитет к магии ?
        return
    end
   
    local order_type
--[[  
    print("CAST ABIITY")
    print("ability behavior = "..ability.behavior)
    print("enemy = "..enemy:GetUnitName())
    print("caster = "..caster:GetUnitName())
]]  
    -- теперь определяется каким образом будет использованна способность
   
    if ability.behavior == "target" then
        order_type = DOTA_UNIT_ORDER_CAST_TARGET    -- на цель
    elseif ability.behavior == "no_target" then
        order_type = DOTA_UNIT_ORDER_CAST_NO_TARGET    -- без цели
    elseif ability.behavior == "point" then
        order_type = DOTA_UNIT_ORDER_CAST_POSITION    -- на точку
    elseif ability.behavior == "passive" then
        return
    end
   
    ExecuteOrderFromTable({
        UnitIndex = caster:entindex(),        -- индекс кастера
        OrderType = order_type,                -- тип приказа
        AbilityIndex = ability:entindex(),    -- индекс способности
        TargetIndex = enemy:entindex(),     -- индекс врага
        Position = enemy:GetOrigin(),         -- положение врага
        Queue = false,                        -- ждать очереди ?
    })
    caster:SetContextThink( "NeutralAutoCasterThink", NeutralAutoCasterThink, 1 ) -- если способность использована, то поведение начинается заного
   
end

function AttackMove( unit, enemy )
    if enemy == nil then
        return
    end
--    print("ATTACK MOVE")
    ExecuteOrderFromTable({
        UnitIndex = unit:entindex(),                --индекс кастера
        OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,    -- тип приказа атака
        Position = enemy:GetOrigin(),                -- пощиция врага
        Queue = false,
    })

    return 1
end

function RetreatHome()
    thisEntity.agro = false    -- снимается действие агра

    ExecuteOrderFromTable({
        UnitIndex = thisEntity:entindex(),
        OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
        Position = thisEntity.vInitialSpawnPos      
    })
end

Надеюсь этот гайд обзора на пуджа вам поможет в будущем, а я пожалуй пойду и доем...
 
Последнее редактирование:
  • Нравится
  • Влюблен
Реакции: EHOT_Art и Alex_Inc_
Также есть остается под вопросом, как лучше описать поведение при касте абилки без цели, тк некоторые имеют маленький рендж .
Или способности кастующиеся на на юнитов с иммунитетом магии , в общем жду ваших советов по улучшению эффективности/гибкости данного алгоритма ))
 
Есть как бы GetAggroTarget и можно некоторое поменять именно на это у тебя
Что например ?
По моему вариант с движением агрессивным вариант самый нормальный
Лучше скажи , с настройкой абилок как лучше поступить
 
if bit.band( ability_behavior, DOTA_ABILITY_BEHAVIOR_PASSIVE ) == DOTA_ABILITY_BEHAVIOR_PASSIVE then ability.behavior = "passive" -- способность пассивна elseif bit.band( ability_behavior, DOTA_ABILITY_BEHAVIOR_UNIT_TARGET ) == DOTA_ABILITY_BEHAVIOR_UNIT_TARGET then ability.behavior = "target" -- способность направлена на юнита elseif bit.band( ability_behavior, DOTA_ABILITY_BEHAVIOR_NO_TARGET ) == DOTA_ABILITY_BEHAVIOR_NO_TARGET then ability.behavior = "no_target" -- способность без цели elseif bit.band( ability_behavior, DOTA_ABILITY_BEHAVIOR_POINT ) == DOTA_ABILITY_BEHAVIOR_POINT then ability.behavior = "point" -- способность направлена на точку end
Этот behavior потом используется только для того чтобы определить order_type. Так не легче сразу записывать в способность order_type, чтобы не писать миллион условий по 2 раза?

Да и вообще для таких вещей вместо небоскребов из elseif намного удобнее использовать таблицу
 
Последнее редактирование:
Кусок из моей либы, который позволяет определить может ли юнит-таргет способность быть скастована на цель. Там проверяется все что можно, не только невосприимчивость к магии.
Таблица в начале нужна для персональной обработки способностей доты, у которых используется DOTA_UNIT_TARGET_CUSTOM, потому что эта херня в UnitFilter всегда возвращает феил.

Lua:
tCustomAbilityTargetTypes = {
    morphling_replicate = DOTA_UNIT_TARGET_HERO,
    morphling_hybrid = DOTA_UNIT_TARGET_HERO,
    tiny_toss = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP,
    tiny_craggy_exterior = DOTA_UNIT_TARGET_NONE,
    vengefulspirit_nether_swap = function( ability, target )
        return self:GetCaster():HasScepter() and DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC or DOTA_UNIT_TARGET_HERO, true
    end,
    riki_blink_strike = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP,
    enigma_demonic_conversion = DOTA_UNIT_TARGET_CREEP,
    pugna_decrepify = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP,
    life_stealer_infest = function( ability, target )
        return DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP, ( target:IsCreep() or target:GetTeam() == ability:GetCaster():GetTeam() )
    end,
    doom_bringer_devour = DOTA_UNIT_TARGET_CREEP,
    undying_soul_rip = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP,
    keeper_of_the_light_recall = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC,
    wisp_tether = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP,
    earth_spirit_petrify = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP,
    terrorblade_sunder = DOTA_UNIT_TARGET_HERO,
    item_quelling_blade = DOTA_UNIT_TARGET_NONE,
    item_moon_shard = DOTA_UNIT_TARGET_HERO,
    item_tango = DOTA_UNIT_TARGET_NONE,
    item_tango_single = DOTA_UNIT_TARGET_NONE,
    item_cyclone = function( ability, target )
        return DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP, ( target == ability:GetCaster() or target:GetTeam() ~= ability:GetCaster():GetTeam() )
    end,
    item_force_staff = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP,
    item_hurricane_pike = function( ability, target )
        return DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP, ( target ~= ability:GetCaster() )
    end,
    item_bfury = DOTA_UNIT_TARGET_NONE,
    item_iron_talon = DOTA_UNIT_TARGET_NONE,
}

function HasFlags( source, flags )
    local tFlags = {}
    local i_flag = 1
    while flags > 0    do
        if flags % 2 == 1 then
            table.insert( tFlags, i_flag )
        end
        i_flag = i_flag * 2
        flags = math.floor( flags / 2 )
    end
   
    for _, flag in pairs( tFlags ) do
        if math.floor( source / flag ) % 2 == 0 then
            return false
        end
    end
    return true
end

function CDOTABaseAbility:IsCorrectTarget( target )
    local filter_team = self:GetAbilityTargetTeam()
    local filter_type = self:GetAbilityTargetType()
    local filter_flag = self:GetAbilityTargetFlags()
   
    if HasFlags( filter_team, DOTA_UNIT_TARGET_TEAM_CUSTOM ) then
        filter_team = filter_team - DOTA_UNIT_TARGET_TEAM_CUSTOM + DOTA_UNIT_TARGET_TEAM_BOTH
    end
   
    if HasFlags( filter_type, DOTA_UNIT_TARGET_CUSTOM ) then
        filter_type = tCustomAbilityTargetTypes[ self:GetName() ] or ( DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP )
        if type( filter_type ) == 'function' then
            filter_type, bSuccess = filter_type( self, target )
            if not bSuccess then
                return false
            end
        end
    end
   
    return ( UF_SUCCESS == UnitFilter( target, filter_team, filter_type, filter_flag, self:GetCaster():GetTeam() ) )
end
 
Последнее редактирование:
  • Нравится
Реакции: vulkantsk
Кусок из моей либы, который позволяет определить может ли юнит-таргет способность быть скастована на цель. Там проверяется все что можно, не только невосприимчивость к магии.
Таблица в начале нужна для персональной обработки способностей доты, у которых используется DOTA_UNIT_TARGET_CUSTOM, потому что эта херня в UnitFilter всегда возвращает феил.

Lua:
tCustomAbilityTargetTypes = {
    morphling_replicate = DOTA_UNIT_TARGET_HERO,
    morphling_hybrid = DOTA_UNIT_TARGET_HERO,
    tiny_toss = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP,
    tiny_craggy_exterior = DOTA_UNIT_TARGET_NONE,
    vengefulspirit_nether_swap = function( ability, target )
        return self:GetCaster():HasScepter() and DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC or DOTA_UNIT_TARGET_HERO, true
    end,
    riki_blink_strike = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP,
    enigma_demonic_conversion = DOTA_UNIT_TARGET_CREEP,
    pugna_decrepify = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP,
    life_stealer_infest = function( ability, target )
        return DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP, ( target:IsCreep() or target:GetTeam() == ability:GetCaster():GetTeam() )
    end,
    doom_bringer_devour = DOTA_UNIT_TARGET_CREEP,
    undying_soul_rip = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP,
    keeper_of_the_light_recall = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC,
    wisp_tether = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP,
    earth_spirit_petrify = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP,
    terrorblade_sunder = DOTA_UNIT_TARGET_HERO,
    item_quelling_blade = DOTA_UNIT_TARGET_NONE,
    item_moon_shard = DOTA_UNIT_TARGET_HERO,
    item_tango = DOTA_UNIT_TARGET_NONE,
    item_tango_single = DOTA_UNIT_TARGET_NONE,
    item_cyclone = function( ability, target )
        return DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP, ( target == ability:GetCaster() or target:GetTeam() ~= ability:GetCaster():GetTeam() )
    end,
    item_force_staff = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP,
    item_hurricane_pike = function( ability, target )
        return DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP, ( target ~= ability:GetCaster() )
    end,
    item_bfury = DOTA_UNIT_TARGET_NONE,
    item_iron_talon = DOTA_UNIT_TARGET_NONE,
}

function HasFlags( source, flags )
    local tFlags = {}
    local i_flag = 1
    while flags > 0    do
        if flags % 2 == 1 then
            table.insert( tFlags, i_flag )
        end
        i_flag = i_flag * 2
        flags = math.floor( flags / 2 )
    end
  
    for _, flag in pairs( tFlags ) do
        if math.floor( source / flag ) % 2 == 0 then
            return false
        end
    end
    return true
end

function CDOTABaseAbility:IsCorrectTarget( target )
    local filter_team = self:GetAbilityTargetTeam()
    local filter_type = self:GetAbilityTargetType()
    local filter_flag = self:GetAbilityTargetFlags()
  
    if HasFlags( filter_team, DOTA_UNIT_TARGET_TEAM_CUSTOM ) then
        filter_team = filter_team - DOTA_UNIT_TARGET_TEAM_CUSTOM + DOTA_UNIT_TARGET_TEAM_BOTH
    end
  
    if HasFlags( filter_type, DOTA_UNIT_TARGET_CUSTOM ) then
        filter_type = tCustomAbilityTargetTypes[ self:GetName() ] or ( DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP )
        if type( filter_type ) == 'function' then
            filter_type, bSuccess = filter_type( self, target )
            if not bSuccess then
                return false
            end
        end
    end
  
    return ( UF_SUCCESS == UnitFilter( target, filter_team, filter_type, filter_flag, self:GetCaster():GetTeam() ) )
end
А для чего тебе такая либа ?
 
А для чего тебе такая либа ?
для рофлов по большей части.
ее оставшаяся часть триггерит эффект активной способности. А эта проверка нужна чисто чтобы нельзя было со своего леса мидаснуть вражеский трон при всех стоящих лайнах. А доминатор на вражеского героя, так от этого доту вообще взрывает нахер
 
Последнее редактирование:
для рофлов по большей части.
ее оставшаяся часть триггерит эффект активной способности. А эта проверка нужна чисто чтобы нельзя было со своего леса мидаснуть вражеский трон при всех стоящих лайнах. А доминатор на вражеского героя, так от этого доту вообще взрывает нахер
А как думаешь лучше настроить спелы без цели ?
Некоторые просто кастуются на себя типа берсерка,а некоторые по площади (стан кентавра/панды/тайда)
 
А как думаешь лучше настроить спелы без цели ?
Некоторые просто кастуются на себя типа берсерка,а некоторые по площади (стан кентавра/панды/тайда)
Прописать для каждого скила тип и радиус, ну а как еще то? Причем радиус лучше прописывать меньше чем по факту, иначе будет очень легко эвейдить, достаточно просто идти от врага.
 
Кста а в кв тоже можна вроде заставлять юнита кастовать
 
вот тут посмотри как сделано
 
Всегда думал о том, что было бы неплохо если бы кто-нибудь сделал такое поведение юнитам, чтобы они агрились как нейтралы и кастовали свои способности в автоматическом режиме...
Ну и каким то магическим образом этим кем-то стал я :( . . .
Поведение юнита подключается в файле юнитов в строчке "vscripts" "ai/neutral"
Чтобы поведение работало корректно, нужно, чтобы у юнита не было поведения нейтрала "UseNeutralCreepBehavior" или было бы "0"

Поведение нейтрала

В общем начнем с простого, поведение нейтрала который агрится вместе со своими братанами и возвращается на стартовую точку в случае, если отойдет далеко от своей стартовой позиции :
Код:
function Spawn( entityKeyValues )
    if not IsServer() then
        return
    end

    if thisEntity == nil then
        return
    end

    thisEntity:SetContextThink( "NeutralThink", NeutralThink, 1 )
end

function NeutralThink()
    if ( not thisEntity:IsAlive() ) then        --если юнит мертв
        return -1
    end

    if GameRules:IsGamePaused() == true then    --если игра приостановлена
        return 1
    end

    if thisEntity:IsChanneling() then    -- если юнит кастует скил
        return 1
    end

    if thisEntity:IsControllableByAnyPlayer() then    -- если юнит принадлежит игроку, то поведение отключается
        return -1
    end

    local npc = thisEntity

    if not thisEntity.bInitialized then
        npc.vInitialSpawnPos = npc:GetOrigin()        -- точка спавна юнита
        npc.fMaxDist = npc:GetAcquisitionRange()    -- радиус агра
        npc.bInitialized = true                        -- флаг инициализации
        npc.agro = false                            -- флаг агра
   
    end

    local search_radius                             -- радиус поиска зависит от того, имеет ли юнит агр
    if npc.agro then
        search_radius = npc.fMaxDist * 1.5            -- расшираяется
    else
        search_radius = npc.fMaxDist                -- становится обычным
    end

    -- Как далеко юнит находится от своей точки спавна ?
    local fDist = ( npc:GetOrigin() - npc.vInitialSpawnPos ):Length2D()
    if fDist > search_radius then
        RetreatHome()            -- если юнит слишком далеко, то идет на точку спавна
        return 3
    end

    local enemies = FindUnitsInRadius(
                        npc:GetTeamNumber(),        --команда юнита
                        npc.vInitialSpawnPos,        --местоположение юнита
                        nil,    --айди юнита (необязательно)
                        search_radius + 50,    --радиус поиска
                        DOTA_UNIT_TARGET_TEAM_ENEMY,    -- юнитов чьей команды ищем вражеской/дружественной
                        DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC,    --юнитов какого типа ищем
                        DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE,    --поиск по флагам
                        FIND_CLOSEST,    --сортировка от ближнего к дальнему
                        false )

    if #enemies == 0 then    -- если найденных юнитов нету
        if npc.agro then
            RetreatHome()    -- если юнит под действием агра
        end  
        return 0.5
    end

    local enemy = enemies[1]    -- врагом выбирается первый близжайший


    if npc.agro then    -- если юнит находится под действием агра
   
        AttackMove(npc, enemy)
--        npc:MoveToPositionAggressive(enemy:GetAbsOrigin())

    else
        local allies = FindUnitsInRadius(    -- ищет всех союзных братков в радиусе
                npc:GetTeamNumber(),
                npc.vInitialSpawnPos,
                nil,
                npc.fMaxDist,
                DOTA_UNIT_TARGET_TEAM_FRIENDLY,
                DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC,
                DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE,
                FIND_CLOSEST,
                false )
           
        for i=1,#allies do    -- заставляет братков быть агрессивными и атаковать врага
            local ally = allies[i]
            ally.agro = true    -- накладывает действие агра
            AttackMove(ally, enemy)
        end
    end
    return 1

end

function AttackMove( unit, enemy )
    if enemy == nil then
        return
    end
--    print("ATTACK MOVE")
    ExecuteOrderFromTable({
        UnitIndex = unit:entindex(),                --индекс кастера
        OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,    -- тип приказа атака
        Position = enemy:GetOrigin(),                -- пощиция врага
        Queue = false,
    })

    return 1
end

function RetreatHome()
    thisEntity.agro = false    -- снимается действие агра

    ExecuteOrderFromTable({
        UnitIndex = thisEntity:entindex(),
        OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
        Position = thisEntity.vInitialSpawnPos  
    })
end

Поведение автокастера
Теперь разберем поведение автокастера, который использует свои способности, если подойти к нему на расстояние агра:
Код:
function Spawn( entityKeyValues )
    if not IsServer() then
        return
    end

    if thisEntity == nil then
        return
    end
  
    thisEntity:SetContextThink( "AutoCasterThink", AutoCasterThink, 1 )
end

function AutoCasterThink()
    if ( not thisEntity:IsAlive() ) then        --если юнит мертв
        return -1 
    end
  
    if GameRules:IsGamePaused() == true then    --если игра приостановлена
        return 1 
    end

    if thisEntity:IsChanneling() then    -- если юнит кастует скил
        return 1 
    end
  
    if thisEntity:IsControllableByAnyPlayer() then    -- если юнит принадлежит игроку, то поведение отключается
        return -1
    end
  
    local npc = thisEntity

    if not thisEntity.bInitialized then
        npc.fMaxDist = npc:GetAcquisitionRange()    -- радиус агра
        npc.bInitialized = true                        -- флаг инициализации
      
        npc.ability0 = FindAbility(npc, 0)            -- ищет способность по индексу
        npc.ability1 = FindAbility(npc, 1)            -- ищет способность по индексу
        npc.ability2 = FindAbility(npc, 2)            -- ищет способность по индексу
        npc.ability3 = FindAbility(npc, 3)            -- ищет способность по индексу
        npc.ability4 = FindAbility(npc, 4)            -- ищет способность по индексу
        npc.ability5 = FindAbility(npc, 5)            -- ищет способность по индексу
      
    end

    local search_radius = npc.fMaxDist        -- радиус поиска зависит от того, имеет ли юнит агр
  
    local enemies = FindUnitsInRadius(
                        npc:GetTeamNumber(),        --команда юнита
                        npc:GetAbsOrigin(),        --местоположение юнита
                        nil,    --айди юнита (необязательно)
                        search_radius + 50,    --радиус поиска
                        DOTA_UNIT_TARGET_TEAM_ENEMY,    -- юнитов чьей команды ищем вражеской/дружественной
                        DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC,    --юнитов какого типа ищем
                        DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE,    --поиск по флагам
                        FIND_CLOSEST,    --сортировка от ближнего к дальнему
                        false )

    local enemy = enemies[1]    -- врагом выбирается первый близжайший
  
    TryCastAbility(npc.ability0, npc, enemy)    -- попытка использовать способность
    TryCastAbility(npc.ability1, npc, enemy)    -- попытка использовать способность
    TryCastAbility(npc.ability2, npc, enemy)    -- попытка использовать способность
    TryCastAbility(npc.ability3, npc, enemy)    -- попытка использовать способность
    TryCastAbility(npc.ability4, npc, enemy)    -- попытка использовать способность
    TryCastAbility(npc.ability5, npc, enemy)    -- попытка использовать способность

    return 1
  
end

function FindAbility(unit, index)
    local ability = unit:GetAbilityByIndex(index)
  
    if ability then
        local ability_behavior = ability:GetBehaviorInt()
      
        if bit.band( ability_behavior, DOTA_ABILITY_BEHAVIOR_PASSIVE ) == DOTA_ABILITY_BEHAVIOR_PASSIVE then
            ability.behavior = "passive"    -- способность пассивна
        elseif bit.band( ability_behavior, DOTA_ABILITY_BEHAVIOR_UNIT_TARGET ) == DOTA_ABILITY_BEHAVIOR_UNIT_TARGET then
            ability.behavior = "target"        -- способность направлена на юнита
        elseif bit.band( ability_behavior, DOTA_ABILITY_BEHAVIOR_NO_TARGET ) == DOTA_ABILITY_BEHAVIOR_NO_TARGET then
            ability.behavior = "no_target"    -- способность без цели
        elseif bit.band( ability_behavior, DOTA_ABILITY_BEHAVIOR_POINT ) == DOTA_ABILITY_BEHAVIOR_POINT then
            ability.behavior = "point"        -- способность направлена на точку
        end
--        print("ability #"..index.." name = "..ability:GetAbilityName())
--        print("ability behavior = "..ability.behavior)
      
        return ability
    else
--        print("ability #"..index.."not found !!!")
        return nil
    end
  
end

function TryCastAbility(ability, caster, enemy)
  
    if ability == nil -- способность существует ?
    or  not ability:IsFullyCastable()     -- способность можно использовать?
    or ability.behavior == "passive"     -- способность пассивна ?
    or  enemy:IsMagicImmune()  then        -- цель имеет уммунитет к магии ?
        return
    end
  
    local order_type
--[[ 
    print("CAST ABIITY")
    print("ability behavior = "..ability.behavior)
    print("enemy = "..enemy:GetUnitName())
    print("caster = "..caster:GetUnitName())
]] 
    -- теперь определяется каким образом будет использованна способность
  
    if ability.behavior == "target" then
        order_type = DOTA_UNIT_ORDER_CAST_TARGET    -- на цель
    elseif ability.behavior == "no_target" then
        order_type = DOTA_UNIT_ORDER_CAST_NO_TARGET    -- без цели
    elseif ability.behavior == "point" then
        order_type = DOTA_UNIT_ORDER_CAST_POSITION    -- на точку
    elseif ability.behavior == "passive" then
        return
    end
  
    ExecuteOrderFromTable({
        UnitIndex = caster:entindex(),        -- индекс кастера
        OrderType = order_type,                -- тип приказа
        AbilityIndex = ability:entindex(),    -- индекс способности
        TargetIndex = enemy:entindex(),     -- индекс врага
        Position = enemy:GetOrigin(),         -- положение врага
        Queue = false,                        -- ждать очереди ?
    })
    caster:SetContextThink( "AutoCasterThink", AutoCasterThink, 1 ) -- если способность использована, то поведение начинается заного
  
end

А теперь мы делаем волшебство, соединяя поведение нейтрала и автокастера ...
cqlDeeZ.png


Поведение нейтрала автокастера
Венец эволюции двух видов , который содержит в себе лучшие особенности предков и справляется со всеми задачами обоих (или по крайней мере так думает). Он далеко не идеален , но как говорится "Хороша ложка к обеду". Так и поведение сглаживает углы неидеальности цифрового мира...
Код:
function Spawn( entityKeyValues )
    if not IsServer() then
        return
    end

    if thisEntity == nil then
        return
    end
  
    thisEntity:SetContextThink( "NeutralAutoCasterThink", NeutralAutoCasterThink, 1 )
end

function NeutralAutoCasterThink()
    if ( not thisEntity:IsAlive() ) then        --если юнит мертв
        return -1 
    end
  
    if GameRules:IsGamePaused() == true then    --если игра приостановлена
        return 1 
    end

    if thisEntity:IsChanneling() then    -- если юнит кастует скил
        return 1 
    end
  
    if thisEntity:IsControllableByAnyPlayer() then    -- если юнит принадлежит игроку, то поведение отключается
        return -1
    end
  
    local npc = thisEntity

    if not thisEntity.bInitialized then
        npc.vInitialSpawnPos = npc:GetOrigin()        -- точка спавна юнита
        npc.fMaxDist = npc:GetAcquisitionRange()    -- радиус агра
        npc.bInitialized = true                        -- флаг инициализации
        npc.agro = false                            -- флаг агра
      
        npc.ability0 = FindAbility(npc, 0)            -- ищет способность по индексу
        npc.ability1 = FindAbility(npc, 1)            -- ищет способность по индексу
        npc.ability2 = FindAbility(npc, 2)            -- ищет способность по индексу
        npc.ability3 = FindAbility(npc, 3)            -- ищет способность по индексу
        npc.ability4 = FindAbility(npc, 4)            -- ищет способность по индексу
        npc.ability5 = FindAbility(npc, 5)            -- ищет способность по индексу
      
    end

    local search_radius                             -- радиус поиска зависит от того, имеет ли юнит агр
    if npc.agro then
        search_radius = npc.fMaxDist * 1.5            -- расшираяется
    else
        search_radius = npc.fMaxDist                -- становится обычным
    end
  
    -- Как далеко юнит находится от своей точки спавна ?
    local fDist = ( npc:GetOrigin() - npc.vInitialSpawnPos ):Length2D()
    if fDist > search_radius then
        RetreatHome()            -- если юнит слишком далеко, то идет на точку спавна
        return 3
    end
  
    local enemies = FindUnitsInRadius(
                        npc:GetTeamNumber(),        --команда юнита
                        npc.vInitialSpawnPos,        --местоположение юнита
                        nil,    --айди юнита (необязательно)
                        search_radius + 50,    --радиус поиска
                        DOTA_UNIT_TARGET_TEAM_ENEMY,    -- юнитов чьей команды ищем вражеской/дружественной
                        DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC,    --юнитов какого типа ищем
                        DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE,    --поиск по флагам
                        FIND_CLOSEST,    --сортировка от ближнего к дальнему
                        false )

    if #enemies == 0 then    -- если найденных юнитов нету
        if npc.agro then
            RetreatHome()    -- если юнит под действием агра
        end     
        return 0.5
    end
  
    local enemy = enemies[1]    -- врагом выбирается первый близжайший
  
  
    if npc.agro then    -- если юнит находится под действием агра
      
        AttackMove(npc, enemy)
--        npc:MoveToPositionAggressive(enemy:GetAbsOrigin())

        TryCastAbility(npc.ability0, npc, enemy)    -- попытка использовать способность
        TryCastAbility(npc.ability1, npc, enemy)    -- попытка использовать способность
        TryCastAbility(npc.ability2, npc, enemy)    -- попытка использовать способность
        TryCastAbility(npc.ability3, npc, enemy)    -- попытка использовать способность
        TryCastAbility(npc.ability4, npc, enemy)    -- попытка использовать способность
        TryCastAbility(npc.ability5, npc, enemy)    -- попытка использовать способность
    else
        local allies = FindUnitsInRadius(    -- ищет всех союзных братков в радиусе
                npc:GetTeamNumber(),
                npc.vInitialSpawnPos,
                nil,
                npc.fMaxDist,
                DOTA_UNIT_TARGET_TEAM_FRIENDLY,
                DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC,
                DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE,
                FIND_CLOSEST,
                false )
              
        for i=1,#allies do    -- заставляет братков быть агрессивными и атаковать врага
            local ally = allies[i]
            ally.agro = true    -- накладывает действие агра
            AttackMove(ally, enemy) 
        end 
    end 
    return 1
  
end

function FindAbility(unit, index)
    local ability = unit:GetAbilityByIndex(index)
  
    if ability then
        local ability_behavior = ability:GetBehaviorInt()
      
        if bit.band( ability_behavior, DOTA_ABILITY_BEHAVIOR_PASSIVE ) == DOTA_ABILITY_BEHAVIOR_PASSIVE then
            ability.behavior = "passive"    -- способность пассивна
        elseif bit.band( ability_behavior, DOTA_ABILITY_BEHAVIOR_UNIT_TARGET ) == DOTA_ABILITY_BEHAVIOR_UNIT_TARGET then
            ability.behavior = "target"        -- способность направлена на юнита
        elseif bit.band( ability_behavior, DOTA_ABILITY_BEHAVIOR_NO_TARGET ) == DOTA_ABILITY_BEHAVIOR_NO_TARGET then
            ability.behavior = "no_target"    -- способность без цели
        elseif bit.band( ability_behavior, DOTA_ABILITY_BEHAVIOR_POINT ) == DOTA_ABILITY_BEHAVIOR_POINT then
            ability.behavior = "point"        -- способность направлена на точку
        end
--        print("ability #"..index.." name = "..ability:GetAbilityName())
--        print("ability behavior = "..ability.behavior)
      
        return ability
    else
--        print("ability #"..index.."not found !!!")
        return nil
    end
  
end

function TryCastAbility(ability, caster, enemy)
  
    if ability == nil -- способность существует ?
    or  not ability:IsFullyCastable()     -- способность можно использовать?
    or ability.behavior == "passive"     -- способность пассивна ?
    or  enemy:IsMagicImmune()  then        -- цель имеет уммунитет к магии ?
        return
    end
  
    local order_type
--[[ 
    print("CAST ABIITY")
    print("ability behavior = "..ability.behavior)
    print("enemy = "..enemy:GetUnitName())
    print("caster = "..caster:GetUnitName())
]] 
    -- теперь определяется каким образом будет использованна способность
  
    if ability.behavior == "target" then
        order_type = DOTA_UNIT_ORDER_CAST_TARGET    -- на цель
    elseif ability.behavior == "no_target" then
        order_type = DOTA_UNIT_ORDER_CAST_NO_TARGET    -- без цели
    elseif ability.behavior == "point" then
        order_type = DOTA_UNIT_ORDER_CAST_POSITION    -- на точку
    elseif ability.behavior == "passive" then
        return
    end
  
    ExecuteOrderFromTable({
        UnitIndex = caster:entindex(),        -- индекс кастера
        OrderType = order_type,                -- тип приказа
        AbilityIndex = ability:entindex(),    -- индекс способности
        TargetIndex = enemy:entindex(),     -- индекс врага
        Position = enemy:GetOrigin(),         -- положение врага
        Queue = false,                        -- ждать очереди ?
    })
    caster:SetContextThink( "NeutralAutoCasterThink", NeutralAutoCasterThink, 1 ) -- если способность использована, то поведение начинается заного
  
end

function AttackMove( unit, enemy )
    if enemy == nil then
        return
    end
--    print("ATTACK MOVE")
    ExecuteOrderFromTable({
        UnitIndex = unit:entindex(),                --индекс кастера
        OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,    -- тип приказа атака
        Position = enemy:GetOrigin(),                -- пощиция врага
        Queue = false,
    })

    return 1
end

function RetreatHome()
    thisEntity.agro = false    -- снимается действие агра

    ExecuteOrderFromTable({
        UnitIndex = thisEntity:entindex(),
        OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
        Position = thisEntity.vInitialSpawnPos     
    })
end

Надеюсь этот гайд обзора на пуджа вам поможет в будущем, а я пожалуй пойду и доем...
скажите пожалуйста что делать если крип не использует скилл на точку и как сделать так чтобы он применя скилл на себя
 
Значит я не понимаю как написать правильно для поинт скилла и как сделать применение на себя
Ты попробовал использовать дотовские абилки ?
Например топок кентавра или волна магнуса
 
Реклама: