Добавлен , опубликован

История одного заклинания

Содержание:
Приступим к созданию простого заклинания, создав его сначала на GUI, а потом оптимизируем для дальнейшей разработки.

Инструментарий

Давайте сразу привыкать к хорошему и пользоваться нормальными интструментами разработки. Поэтому сразу категорично откажемся от WorldEdit и установим JGNP, а что-бы донести наше творение наибольшему кругу людей, будем использовать самую популярную версию Warcraft 1.26a. Из-за моих личных предпочтений, можете сразу выключить AdicParser и AdicOptimizer. Они нам не понадобятся.

Идея

В наш век высоких технологий, никого не удивишь заклинанием "нанести 100 урона тому типу с большими усами". C другой стороны, начинать с заклинания не вмещающегося в экран глупо. Поэтому схитрим и возьмём стандартное заклинание и сделаем его сами и чуть чуть подругому. Для этого прекрасно подойдёт Благодать, по иронии судьбы находящаяся первой в списке.
Вроди бы ничего сложного, а сдругой стороны на разных юнитов действует по разному.
Поток божественной энергии, исцеляющий дружественное живое существо или наносящий урон враждебной нежити.
Но что-бы сделать его чуть-чуть нестанадартным, она будет работать на всех юнитов вокруг героя.

Подготовка

Я всегда считал, что настоящий программист должен всегда разбираться в смежных с его работой областях. Поэтому пойдём на просторы интернета и скачаем модель героя, эффектов и иконку. После чего импортируем всё это в карту. Говорят, что к порядку нужно приучать себя с детства, так что пока не всё потеряно, избавимся от противного war3mapImported в путях и дадим им осмысленные названия.
Теперь осталось набросать убогий рельеф, чтоб после часов отладки и поиска багов глаза не истекали слезами от одинаковых тайлов и создать нестандартного героя на основе паладина.

Способность

Как и для всех триггерных способностей, мы будем использовать Канал. Подробно на его настройке останавливаться не будем, единственно на время разработки требования маны и перезарядку установим в 0.

GUI

Всех новичков на сайте пугают страшным словом утечки. И правильно делают. Играть в лагающе-десинкающее подобие игры и разребать кучу однотипных вопросов то ещё удовольствие. С другой стороны, на сайте есть только одна статья и база примеров о GUI. И всё это сомнительного качества. Посему, даже если никто после моего труда не напишет ни одной строчки кода, я всётаки попытаюсь показать хорошие практики.

Итак, наконец-то жмём F4 в редакторе и открываем редактор триггеров. Удаляем все триггеры, создаём папку Main и в ней содаём первый триггер Setting.
И сразу сталкиваемся с утечкой в Position of(Trgiggering Unit), которая создаёт точку (location) и не удаляет его. И если вы думайте, что лишние несколько байт памяти ничего не изменят, то вы сильно ошибаетесь. Без привычки недопускать ошибок, в голове плодится разруха, а в софте бардак. Поэтому схитрим заведём себе личную точку и будем её двигать.
Теперь дело за малым, отловить способность, перебрать всех юнитов, вылечить/нанести урон и показать эффекты.

JASS

Что-бы перейти к освоению JASS, нажмём "Правка->Конвертировать в текст".
В итоге пред нами предстанет куча нечитаемого текста.
раскрыть
function Trig_MassHeal_Conditions takes nothing returns boolean
    if ( not ( GetSpellAbilityId() == 'A000' ) ) then
        return false
    endif
    return true
endfunction

function Trig_MassHeal_Func003Func001Func002C takes nothing returns boolean
    if ( not ( IsPlayerAlly(GetOwningPlayer(GetTriggerUnit()), GetOwningPlayer(GetEnumUnit())) == true ) ) then
        return false
    endif
    if ( not ( IsUnitType(GetEnumUnit(), UNIT_TYPE_UNDEAD) == false ) ) then
        return false
    endif
    if ( not ( GetUnitStateSwap(UNIT_STATE_LIFE, GetEnumUnit()) < GetUnitStateSwap(UNIT_STATE_MAX_LIFE, GetEnumUnit()) ) ) then
        return false
    endif
    return true
endfunction

function Trig_MassHeal_Func003Func001Func004C takes nothing returns boolean
    if ( not ( IsPlayerEnemy(GetOwningPlayer(GetTriggerUnit()), GetOwningPlayer(GetEnumUnit())) == true ) ) then
        return false
    endif
    if ( not ( IsUnitType(GetEnumUnit(), UNIT_TYPE_UNDEAD) == true ) ) then
        return false
    endif
    return true
endfunction

function Trig_MassHeal_Func003Func001C takes nothing returns boolean
    if ( not ( IsUnitAliveBJ(GetEnumUnit()) == true ) ) then
        return false
    endif
    if ( not ( GetTriggerUnit() != GetEnumUnit() ) ) then
        return false
    endif
    return true
endfunction

function Trig_MassHeal_Func003A takes nothing returns nothing
    if ( Trig_MassHeal_Func003Func001C() ) then
        // Дружественная не нежить
        if ( Trig_MassHeal_Func003Func001Func002C() ) then
            // Добавим здоровья в размере: уровень способности*100
            call SetUnitLifeBJ( GetEnumUnit(), ( GetUnitStateSwap(UNIT_STATE_LIFE, GetEnumUnit()) + ( I2R(GetUnitAbilityLevelSwapped('A000', GetTriggerUnit())) * 100.00 ) ) )
            // Проиграем спецэффект на юните
            call AddSpecialEffectTargetUnitBJ( "origin", GetEnumUnit(), "Effect\\Holy_Heal_Small.mdx" )
            // Не забываем уничтожать спецэффект, чтоб избежать утечек
            call DestroyEffect( GetLastCreatedEffectBJ() )
        else
        endif
        // Враждебная нежить
        if ( Trig_MassHeal_Func003Func001Func004C() ) then
            // Наносим урон от лица кастера в размере: уровень способности*50
            call UnitDamageTargetBJ( GetTriggerUnit(), GetEnumUnit(), ( I2R(GetUnitAbilityLevelSwapped('A000', GetTriggerUnit())) * 50.00 ), ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL )
            // Проиграем спецэффект на юните
            call AddSpecialEffectTargetUnitBJ( "origin", GetEnumUnit(), "Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl" )
            // Не забываем уничтожать спецэффект, чтоб избежать утечек
            call DestroyEffect( GetLastCreatedEffectBJ() )
        else
        endif
    else
    endif
endfunction

function Trig_MassHeal_Actions takes nothing returns nothing
    // Перебираем всех не мёртвых юнитов вокруг кастера, кроме самого кастера
    set bj_wantDestroyGroup = true // Перебор группы всегда создаёт утечку, всегда добавляйте эту строчку
    call ForGroupBJ( GetUnitsInRangeOfLocAll(400.00, GetUnitLoc(GetTriggerUnit())), function Trig_MassHeal_Func003A )
endfunction

//===========================================================================
function InitTrig_MassHeal takes nothing returns nothing
    set gg_trg_MassHeal = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_MassHeal, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_MassHeal, Condition( function Trig_MassHeal_Conditions ) )
    call TriggerAddAction( gg_trg_MassHeal, function Trig_MassHeal_Actions )
endfunction
Как видите, комментарии, которые мы оставляли в GUI перешли в код. Но появилася блок, который мы вродибы не объявляли.
//===========================================================================
function InitTrig_MassHeal takes nothing returns nothing
    set gg_trg_MassHeal = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_MassHeal, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_MassHeal, Condition( function Trig_MassHeal_Conditions ) )
    call TriggerAddAction( gg_trg_MassHeal, function Trig_MassHeal_Actions )
endfunction
Так что, вооружившись знанием об локальных переменных, приведём код в порядок. Для начала вооружившись автозаменой по _Ctrl+H_ избавимся от ненужных приставок "Trig_". Стало немного читаемей, но редактор почему-то подсвечивает некоторые функции красным шрифтом.
О брат, это жулики! В чём несложно убедиться поискав их в Function List.
Вооружившись этими знаниями, немного переделаем этот код.
function InitTrig_MassHeal takes nothing returns nothing
    local trigger t = CreateTrigger()
    local integer i = 0
    loop
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        set i = i + 1
        exitwhen i == bj_MAX_PLAYER_SLOTS
    endloop
    call TriggerAddCondition(t, Condition( function MassHeal_Conditions ) )
    call TriggerAddAction(t, function MassHeal_Actions )
    set t = null // Так как триггер у нас в локальной переменной, то нужно её очистить.
endfunction
Поняв принцип, можно оптимизировать весь триггер
раскрыть
// Проверяем жив ли юнит
function MassHeal_GroupEnumCondition takes nothing returns boolean
    return GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > 0.405
endfunction

// Основные действия триггера
function MassHeal_Actions takes nothing returns nothing
    local group g = CreateGroup() // Создаём группу
    local unit tu = GetTriggerUnit() // Сократим для читабельности
    local unit u // Заводим локального юнита для перебора сразу
    local real x = GetUnitX(tu) // По возможности используем координаты,
    local real y = GetUnitY(tu) // что-бы не плодить лишние location
    local real range = 400.0 // Устанавливаем радиус способности
    local real AbilityLevel = I2R(GetUnitAbilityLevel(tu, 'A000'))
    
    // Добавляем в группу живых юнитов вокруг кастера
    call GroupEnumUnitsInRange(g, x, y, range, Condition( function MassHeal_GroupEnumCondition))
    
    // Перебираем юнитов в группе
    loop
        set u = FirstOfGroup(g) // Выбираем первого юнита в группе
        exitwhen u == null // Если группа пустая, юнит не выберется и мы выйдем из цикла
        
        // Если юнит не сам кастер
        if ( not (tu == u)) then
        
            // Дружественная не нежить
            if (IsPlayerAlly(GetOwningPlayer(tu), GetOwningPlayer(u)) == true ) and /* В JASS нельзя использовать перенос строки в условии, но мы его закомментируем
                */  IsUnitType(u, UNIT_TYPE_UNDEAD) == false and /*
                */  GetUnitState(u, UNIT_STATE_LIFE) <  GetUnitState(u, UNIT_STATE_MAX_LIFE) /*
                */ then
                    // Добавим здоровья в размере: уровень способности * 100
                    call SetUnitState(u, UNIT_STATE_LIFE, GetUnitState(u, UNIT_STATE_LIFE) + AbilityLevel * 100)
                    // Сразу уничтожим проигранный эффект
                    call DestroyEffect(AddSpecialEffectTarget("Effect\\Holy_Heal_Small.mdx", u, "origin"))
            endif
            
            // Враждебная нежить
            if (IsPlayerEnemy(GetOwningPlayer(tu), GetOwningPlayer(u)) == true and /*
                */  IsUnitType(u, UNIT_TYPE_UNDEAD)) /*
                */ then
                    // Наносим урон от лица кастера в размере: уровень способности * 50
                    call UnitDamageTarget(tu, u, AbilityLevel * 50, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
                    // Сразу уничтожим проигранный эффект
                    call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl", u, "origin"))
            endif
            
        endif
        
        call GroupRemoveUnit(g, u) // Убираем юнита из группы
    endloop
   
    call DestroyGroup(g) // Удаляем группу
    /*
    По идее мы должны ещё вызвать
    
    call GroupClear(g)
    set u = null
    
    но мы это уже сделали при переборе
   */
   set tu = null
   set g = null // Очищаем переменную группы
endfunction

// Условие срабатывания триггера
function MassHeal_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A000'
endfunction

//===========================================================================
function InitTrig_MassHeal takes nothing returns nothing
    local trigger t = CreateTrigger()
    local integer i = 0
    loop
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        set i = i + 1
        exitwhen i == bj_MAX_PLAYER_SLOTS
    endloop
    call TriggerAddCondition(t, Condition( function MassHeal_Conditions ) )
    call TriggerAddAction(t, function MassHeal_Actions )
    set t = null // Так как триггер у нас в локальной переменной, то нужно её очистить.
endfunction

vJASS

На этом можно и остановиться, но мы не ищем лёгких путей, и будем улучшать код дальше. В этом нам поможет vJass. Для начала отключим наш триггер и создадим новый, с назавнием SpellMassHeal и содержанием
library SpellMassHeal initializer onInit
    function onInit takes nothing returns nothing
        call BJDebugMsg("Мой первый vJass")
    endfunction
endlibrary
Порадовавшись первому успеху, и не поняв как это работает, можно переписать заклинание попутно добавив ещё комментариев.
раскрыть
library SpellMassHeal initializer onInit
    // В этом блоке можно создавать глобальные переменные
    globals
        private integer AbilityID = 'A000' // Указываем равкод нашей способности, чтоб можно было удобно изменить
        private real HealAlly = 100.0 // На сколько умножить уровень способности, для лечения
        private real DamageEnemy = 50.0 // На сколько умножить уровень способности, для урона
        private string HealEffect = "Effect\\Holy_Heal_Small.mdx"
        private string DamageEffect = "Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl"
    endglobals

    // Проверяем жив ли юнит
    private function GroupEnumCondition takes nothing returns boolean
        return GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > 0.405
    endfunction

    // Основные действия триггера
    private function Actions takes nothing returns nothing
        local group g = CreateGroup() // Создаём группу
        local unit tu = GetTriggerUnit() // Сократим для читабельности
        local unit u // Заводим локального юнита для перебора сразу
        local real x = GetUnitX(tu) // По возможности используем координаты,
        local real y = GetUnitY(tu) // что-бы не плодить лишние location
        local real range = 400.0 // Устанавливаем радиус способности
        local real AbilityLevel = I2R(GetUnitAbilityLevel(tu, AbilityID))
        
        // Добавляем в группу живых юнитов вокруг кастера
        call GroupEnumUnitsInRange(g, x, y, range, Condition( function GroupEnumCondition))
        
        // Перебираем юнитов в группе
        loop
            set u = FirstOfGroup(g) // Выбираем первого юнита в группе
            exitwhen u == null // Если группа пустая, юнит не выберется и мы выйдем из цикла
            
            // Если юнит не сам кастер
            if ( not (tu == u)) then
            
                // Дружественная не нежить
                if (IsPlayerAlly(GetOwningPlayer(tu), GetOwningPlayer(u)) == true ) and /* В JASS нельзя использовать перенос строки в условии, но мы его закомментируем
                    */  IsUnitType(u, UNIT_TYPE_UNDEAD) == false and /*
                    */  GetUnitState(u, UNIT_STATE_LIFE) <  GetUnitState(u, UNIT_STATE_MAX_LIFE) /*
                    */ then
                        // Добавим здоровья в размере: уровень способности * 100
                        call SetUnitState(u, UNIT_STATE_LIFE, GetUnitState(u, UNIT_STATE_LIFE) + AbilityLevel * HealAlly)
                        // Сразу уничтожим проигранный эффект
                        call DestroyEffect(AddSpecialEffectTarget(HealEffect, u, "origin"))
                endif
                
                // Враждебная нежить
                if (IsPlayerEnemy(GetOwningPlayer(tu), GetOwningPlayer(u)) == true and /*
                    */  IsUnitType(u, UNIT_TYPE_UNDEAD)) /*
                    */ then
                        // Наносим урон от лица кастера в размере: уровень способности * 50
                        call UnitDamageTarget(tu, u, AbilityLevel * DamageEnemy, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
                        // Сразу уничтожим проигранный эффект
                        call DestroyEffect(AddSpecialEffectTarget(DamageEffect, u, "origin"))
                endif
                
            endif
            
            call GroupRemoveUnit(g, u) // Убираем юнита из группы
        endloop
       
        call DestroyGroup(g) // Удаляем группу
        /*
        По идее мы должны ещё вызвать
        
        call GroupClear(g)
        set u = null
        
        но мы это уже сделали при переборе
       */
       set tu = null
       set g = null // Очищаем переменную группы
    endfunction

    // Условие срабатывания триггера
    private function Conditions takes nothing returns boolean
        return GetSpellAbilityId() == AbilityID
    endfunction

    // эта функция будет вызвана автоматически, потому что указана в initializer onInit
    function onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        local integer i = 0
        loop
            call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
            set i = i + 1
            exitwhen i == bj_MAX_PLAYER_SLOTS
        endloop
        call TriggerAddCondition(t, Condition( function Conditions ) )
        call TriggerAddAction(t, function Actions )
        set t = null // Так как триггер у нас в локальной переменной, то нужно её очистить.
    endfunction
endlibrary

ZINC

Как бы мы ни старались, но глаза досихпор мозолят всякие local, get, set, call. На помощь приходит ZINC. Сейчас просто перешишем всё на нём и вы увидите разницу.
раскрыть
//! zinc
library SpellMassHeal {
    // Вне функции переменные будут приватными и глобальными. Нет смысла лишний раз это указывать
    integer AbilityID = 'A000'; // Указываем равкод нашей способности, чтоб можно было удобно изменить
    real HealAlly = 100.0; // На сколько умножить уровень способности, для лечения
    real DamageEnemy = 50.0; // На сколько умножить уровень способности, для урона
    string HealEffect = "Effect\\Holy_Heal_Small.mdx";
    string DamageEffect = "Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl";
    

    // Проверяем жив ли юнит
    function GroupEnumCondition() -> boolean {
        return GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > 0.405;
    }
    
    // Основные действия триггера
    function Actions() {
        group g = CreateGroup(); // Создаём группу
        unit tu = GetTriggerUnit(); // Сократим для читабельности
        unit u; // Заводим локального юнита для перебора сразу
        real x = GetUnitX(tu); // По возможности используем координаты,
        real y = GetUnitY(tu); // что-бы не плодить лишние location
        real range = 400.0; // Устанавливаем радиус способности
        real AbilityLevel = I2R(GetUnitAbilityLevel(tu, AbilityID));
        
        // Добавляем в группу живых юнитов вокруг кастера
        GroupEnumUnitsInRange(g, x, y, range, Condition( function GroupEnumCondition));
        
        // Перебираем юнитов в группе
        while (true) {
            u = FirstOfGroup(g); // Выбираем первого юнита в группе
            if (u == null) { break; } // Если группа пустая, юнит не выберется и мы выйдем из цикла
            
            // Если юнит не сам кастер
            if (tu != u) {
            
                // Дружественная не нежить
                if (
                        IsPlayerAlly(GetOwningPlayer(tu), GetOwningPlayer(u)) &&
                        IsUnitType(u, UNIT_TYPE_UNDEAD) == false &&
                        GetUnitState(u, UNIT_STATE_LIFE) <  GetUnitState(u, UNIT_STATE_MAX_LIFE)
                    ) {
                        // Добавим здоровья в размере: уровень способности * HealAlly
                        SetUnitState(u, UNIT_STATE_LIFE, GetUnitState(u, UNIT_STATE_LIFE) + AbilityLevel * HealAlly);
                        // Сразу уничтожим проигранный эффект
                        DestroyEffect(AddSpecialEffectTarget(HealEffect, u, "origin"));
                }
                
                // Враждебная нежить
                if (
                        IsPlayerEnemy(GetOwningPlayer(tu), GetOwningPlayer(u)) == true &&
                        IsUnitType(u, UNIT_TYPE_UNDEAD)
                    ) {
                        // Наносим урон от лица кастера в размере: уровень способности * DamageEnemy
                        UnitDamageTarget(tu, u, AbilityLevel * DamageEnemy, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS);
                        // Сразу уничтожим проигранный эффект
                        DestroyEffect(AddSpecialEffectTarget(DamageEffect, u, "origin"));
                }
                
            }
            
            GroupRemoveUnit(g, u); // Убираем юнита из группы
        }
       
        DestroyGroup(g); // Удаляем группу
        /*
        По идее мы должны ещё вызвать
        
        GroupClear(g)
        u = null
        
        но мы это уже сделали при переборе
       */
       tu = null;
       g = null; // Очищаем переменную группы
    }

    // Условие срабатывания триггера
    function Conditions() -> boolean {
        return GetSpellAbilityId() == AbilityID;
    }
    
	//функция onInit вызывается автоматически
    function onInit() {
        trigger t = CreateTrigger();
        integer i = 0;
        for (0 <= i < bj_MAX_PLAYER_SLOTS) {
            TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null);
        }
        TriggerAddCondition(t, Condition( function Conditions ) );
        TriggerAddAction(t, function Actions );
        t = null; // Так как триггер у нас в локальной переменной, то нужно её очистить.
    }
}
//! endzinc
Стало красивее, читабельней, но это ещё не всё. Воспользуемся всеми возможностями языка.
раскрыть
//! zinc
library SpellMassHeal {
    // Вне функции переменные будут приватными и глобальными. Нет смысла лишний раз это указывать
    integer AbilityID = 'A000'; // Указываем равкод нашей способности, чтоб можно было удобно изменить
    real HealAlly = 100.0; // На сколько умножить уровень способности, для лечения
    real DamageEnemy = 50.0; // На сколько умножить уровень способности, для урона
    string HealEffect = "Effect\\Holy_Heal_Small.mdx";
    string DamageEffect = "Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl";
        
    //функция onInit вызывается автоматически
    function onInit() {
        trigger t = CreateTrigger();
        integer i = 0;
        // Добавляем условие срабатывания триггера
        for (0 <= i < bj_MAX_PLAYER_SLOTS) {
            TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null);
        }
        
        // Условие срабатывания триггера
        TriggerAddCondition(t, Condition( function()-> boolean {
            return GetSpellAbilityId() == AbilityID;
        }));
        
        // Основные действия триггера
        TriggerAddAction(t, function() {
            group g = CreateGroup(); // Создаём группу
            unit tu = GetTriggerUnit(); // Сократим для читабельности
            unit u; // Заводим локального юнита для перебора сразу
            real x = GetUnitX(tu); // По возможности используем координаты,
            real y = GetUnitY(tu); // что-бы не плодить лишние location
            real range = 400.0; // Устанавливаем радиус способности
            real AbilityLevel = I2R(GetUnitAbilityLevel(tu, AbilityID));
            
            // Добавляем в группу живых юнитов вокруг кастера
            GroupEnumUnitsInRange(g, x, y, range, Condition( function() -> boolean {
                // Проверяем жив ли юнит
                return GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > 0.405;
            }));
            
            // Перебираем юнитов в группе
            while (true) {
                u = FirstOfGroup(g); // Выбираем первого юнита в группе
                if (u == null) { break; } // Если группа пустая, юнит не выберется и мы выйдем из цикла
                
                // Если юнит не сам кастер
                if (tu != u) {
                
                    // Дружественная не нежить
                    if (
                            IsPlayerAlly(GetOwningPlayer(tu), GetOwningPlayer(u)) &&
                            IsUnitType(u, UNIT_TYPE_UNDEAD) == false &&
                            GetUnitState(u, UNIT_STATE_LIFE) <  GetUnitState(u, UNIT_STATE_MAX_LIFE)
                        ) {
                            // Добавим здоровья в размере: уровень способности * HealAlly
                            SetUnitState(u, UNIT_STATE_LIFE, GetUnitState(u, UNIT_STATE_LIFE) + AbilityLevel * HealAlly);
                            // Сразу уничтожим проигранный эффект
                            DestroyEffect(AddSpecialEffectTarget(HealEffect, u, "origin"));
                    }
                    
                    // Враждебная нежить
                    if (
                            IsPlayerEnemy(GetOwningPlayer(tu), GetOwningPlayer(u)) == true &&
                            IsUnitType(u, UNIT_TYPE_UNDEAD)
                        ) {
                            // Наносим урон от лица кастера в размере: уровень способности * DamageEnemy
                            UnitDamageTarget(tu, u, AbilityLevel * DamageEnemy, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS);
                            // Сразу уничтожим проигранный эффект
                            DestroyEffect(AddSpecialEffectTarget(DamageEffect, u, "origin"));
                    }
                    
                }
                
                GroupRemoveUnit(g, u); // Убираем юнита из группы
            }
           
            DestroyGroup(g); // Удаляем группу
            /*
            По идее мы должны ещё вызвать
            
            GroupClear(g)
            u = null
            
            но мы это уже сделали при переборе
           */
           tu = null;
           g = null; // Очищаем переменную группы
        });
        t = null; // Так как триггер у нас в локальной переменной, то нужно её очистить.
    }
}
//! endzinc
Осталось написать касивое описание способнобности, но это я оставлю в качестве домашнего задания.

Содержание
`
ОЖИДАНИЕ РЕКЛАМЫ...
2
26
5 лет назад
2
Вместо создания точки Location из Position of Unit в JNGP удобно координаты юнита указать.
0
29
5 лет назад
0
Вместо создания точки Location из Position of Unit в JNGP удобно координаты юнита указать.
В коде я только с координатами и работаю, а вот на GUI сильно не забалуешь. Благо в JNGP добавили RemoveLocation, чтоб гуишников Custom Code не пугать.
Этот комментарий удален
0
16
5 лет назад
0
Пфф, новички от жасса-то бегут, а тут и vjass, и ZINC, которые созданы ну воообще для опытных программистов.
5
29
5 лет назад
5
Пфф, новички от жасса-то бегут, а тут и vjass, и ZINC
Я и сам от джасса бегу. А новички впервые видят джас в стандартном WorldEdit.
После чего у них развивается комплекс неполнценности, а опытных программистов они представляют ходячими компиляторами с прямым подключением к матрице.
Загруженные файлы
3
16
5 лет назад
3
Я работаю с чистым JASS уже 6 лет и точно заявляю, что это преувеличение. Стерпеть весь этот ужас вполне можно, пережить на других обертках типа ZINC и vJASS его тоже можно, но понимание приходит быстрее с чистого кода, имо
1
29
5 лет назад
1
но понимание приходит быстрее с чистого кода, имо
Тут согласен, если не понимать, какой джас будет после обёртки и как этот джас будет работать, то будет печально. А с другой стороны бросать новичка в омут чистого джаса, если есть более удобные интсрументы тоже не хочется.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.