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

vulkantsk

Друзья CG
21 Июн 2017
726
72
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:GetBehavior()
      
        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

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


Поведение нейтрала автокастера
Венец эволюции двух видов , который содержит в себе лучшие особенности предков и справляется со всеми задачами обоих (или по крайней мере так думает). Он далеко не идеален , но как говорится "Хороша ложка к обеду". Так и поведение сглаживает углы неидеальности цифрового мира...
Код:
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:GetBehavior()
      
        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

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

vulkantsk

Друзья CG
21 Июн 2017
726
72
www.dotabuff.com
Проект
Roshan defense
Также есть остается под вопросом, как лучше описать поведение при касте абилки без цели, тк некоторые имеют маленький рендж .
Или способности кастующиеся на на юнитов с иммунитетом магии , в общем жду ваших советов по улучшению эффективности/гибкости данного алгоритма ))
 

I_GRIN_I

Друзья CG
15 Мар 2016
1,334
99
Зачем вообще нужен бесполезный модификатор
 

vulkantsk

Друзья CG
21 Июн 2017
726
72
www.dotabuff.com
Проект
Roshan defense
Есть как бы GetAggroTarget и можно некоторое поменять именно на это у тебя
Что например ?
По моему вариант с движением агрессивным вариант самый нормальный
Лучше скажи , с настройкой абилок как лучше поступить
 
20 Дек 2016
791
120
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 намного удобнее использовать таблицу
 
Последнее редактирование:
20 Дек 2016
791
120
Кусок из моей либы, который позволяет определить может ли юнит-таргет способность быть скастована на цель. Там проверяется все что можно, не только невосприимчивость к магии.
Таблица в начале нужна для персональной обработки способностей доты, у которых используется 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

vulkantsk

Друзья CG
21 Июн 2017
726
72
www.dotabuff.com
Проект
Roshan defense
Кусок из моей либы, который позволяет определить может ли юнит-таргет способность быть скастована на цель. Там проверяется все что можно, не только невосприимчивость к магии.
Таблица в начале нужна для персональной обработки способностей доты, у которых используется 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
А для чего тебе такая либа ?
 
20 Дек 2016
791
120
А для чего тебе такая либа ?
для рофлов по большей части.
ее оставшаяся часть триггерит эффект активной способности. А эта проверка нужна чисто чтобы нельзя было со своего леса мидаснуть вражеский трон при всех стоящих лайнах. А доминатор на вражеского героя, так от этого доту вообще взрывает нахер
 
Последнее редактирование:

vulkantsk

Друзья CG
21 Июн 2017
726
72
www.dotabuff.com
Проект
Roshan defense
для рофлов по большей части.
ее оставшаяся часть триггерит эффект активной способности. А эта проверка нужна чисто чтобы нельзя было со своего леса мидаснуть вражеский трон при всех стоящих лайнах. А доминатор на вражеского героя, так от этого доту вообще взрывает нахер
А как думаешь лучше настроить спелы без цели ?
Некоторые просто кастуются на себя типа берсерка,а некоторые по площади (стан кентавра/панды/тайда)
 
20 Дек 2016
791
120
А как думаешь лучше настроить спелы без цели ?
Некоторые просто кастуются на себя типа берсерка,а некоторые по площади (стан кентавра/панды/тайда)
Прописать для каждого скила тип и радиус, ну а как еще то? Причем радиус лучше прописывать меньше чем по факту, иначе будет очень легко эвейдить, достаточно просто идти от врага.
 

DDSuper

Активный
31 Май 2019
140
10
Кста а в кв тоже можна вроде заставлять юнита кастовать
 
Реклама: