WarCraft 3: Dummy cast

Содержание:
В прошлой статье мы только привели наше заклинание в читаемую форму. Теперь же на его примере рассмотрим принцип Dummy cast.

Идея

Наше заклинание не затрагивает дружестенную нежить, враждебнуюю ненежить и самого кастера. Так наколдуем на все дружественные войска Духовное пламя, на врагов Замедление, а кастеру добавим 25% здоровья от излеченного. Если он никого не излечил, наколдуем Рёв.

Dummy cast

Так как Warcraft требует юнита, для того что-бы накладывать заклинания, то дадим ему его. И не нужно слушать лукавых, которые подбивают указывать в качестве модели .mdx. Плодить костыли, даже если движок вам об этом напрямую не говорит, это плохо. Дамми каст придуман не вчера и найти нужную модель не так уже и сложно.

После сих нехитрых действий можно приступать к написанию кода. В прошлый раз я привёл такую конструкцию.
library SpellMassHeal initializer onInit
    function onInit takes nothing returns nothing
        call BJDebugMsg("Мой первый vJass")
    endfunction
endlibrary
Теперь же мы сделаем практически тоже самое но с объяснениями.
library Dummy {
    function onInit(){
        BJDebugMsg("Дамми каст не так уж и сложен!");
    }
}
Когда я только начал изучать редактор то соответсвенно начал с триггеров. И в них мне срзу непонравился подход к переменным, сложенным в одну кучу. Потом перейдя на JASS, мне тоже не нравилось, что функции находятся в одной области видимости и приходилось изобретать хитроумные префиксы, чтоб в итоге не заблудиться в своём коде. Наконец-то придумали vJASS и его строгий синтаксис ZINC. Вот тогда то я и вздохнул с облегчением, потому что там появились библиотеки.
library Dummy {
}
Они позволяли изолировать одни функции от других и разбить код на логические модули. Вот и сейчас мы, вместо того, чтобы засунуть работу с дамми в заклинание, а потом копировать одни и теже строки в каждое заклинание, создадим себе универсальную библиотеку.
library Dummy {
    integer DummyID = 'u000'; // равкод дамми
}
Теперь вспомним, какие заклинания нам нужно наложить. Это Рёв, Духовное пламя и Замедление. Создадим для этого функции.
library Dummy {
    integer DummyID = 'u000'; // равкод дамми
    
    public {
        // Наложение бафов наподобии Духовное пламя, Замедление
        function DummyCastBuff(player caster, unit target, integer abilityId, string order, real lifetime){
        
        }
        
        // Наложение бафов наподобии Рёв
        function DummyCastAOE(player caster, real x, real y, integer abilityId, string order, real lifetime){
        
        }
    }
}
Блок public означает, что эти функции будут доступны из других библиотек. Осталось дописать необходимые действия.
library Dummy {
    integer DummyID = 'u000'; // равкод дамми
    
    public {
        // Наложение бафов наподобии Духовное пламя, Замедление
        function DummyCastBuff(player caster, unit target, integer abilityId, string order, real lifetime){
            unit dummy;
            
            // Небудем указывать мёртвых юнитов в качестве цели;
            if (GetUnitState(target, UNIT_STATE_LIFE) > 0.405){
                dummy = CreateUnit(caster, DummyID, GetUnitX(target), GetUnitY(target), 0.); // Создадим дамми в координатах цели
                UnitApplyTimedLife(dummy, 'BTLF', lifetime); // Укажем время жизни дамми
                UnitAddAbility(dummy, abilityId);
                IssueTargetOrder(dummy, order, target);
                dummy = null;
            }
        }
        
        // Наложение бафов наподобии Рёв
        function DummyCastAOE(player caster, real x, real y, integer abilityId, string order, real lifetime){
            unit dummy = CreateUnit(caster, DummyID, x, y, 0.); // Создадим дамми переданных координатах
            UnitApplyTimedLife(dummy, 'BTLF',lifetime); // Укажем время жизни дамми
            UnitAddAbility(dummy, abilityId);
            IssueImmediateOrder(dummy, order);
            dummy = null;
        }
    }
}

Доработка заклинания

Закончив с подготовкой, можно переходить к самому вкусному.
library SpellMassHeal requires Dummy {
В блоке requires мы указываем, какие библиотеки использует наше заклинание. При компиляции в JASS код заклинания будет расположен ниже кода используемых библиотек и не вызовет ошибок. На сей раз не будем создавать нестандартные способности, а просто уберём требования из стандартных и поднастроим наше заклинание.
library SpellMassHeal requires Dummy {    
    integer AllyBuffID = 'Ainf'; // равкод способности Духовное пламя 
    string AllyBuffOrder = "innerfire"; // приказ способности Духовное пламя
    integer EnemyBuffID = 'Aslo'; // равкод способности Замедление
    string EnemyBuffOrder = "slow"; // приказ способности Замедление
    integer CasterBuffID = 'Aroa'; // равкод способности Рёв
    string CasterBuffOrder = "roar"; // приказ способности Рёв
Когда наш код влазил в монитор, то переменные
unit tu = GetTriggerUnit(); // Сократим для читабельности
unit u; // Заводим локального юнита для перебора сразу
не вызывали ужаса и сокращали код, но с его расширением нужно дать им более понятное название и заодно наложить бафы на юнитов.
// Перебираем юнитов в группе
            while (true) {
                target = FirstOfGroup(g); // Выбираем первого юнита в группе
                if (target == null) { break; } // Если группа пустая, юнит не выберется и мы выйдем из цикла
                
                // Если юнит не сам кастер
                if (caster != target) {
                
                    // Дружественные войска
                    if (IsPlayerAlly(GetOwningPlayer(caster), GetOwningPlayer(target))){
                        // Кастуем Духовное пламя
                        DummyCastBuff(GetOwningPlayer(caster), target, AllyBuffID, AllyBuffOrder, 0.1);
                        // Лечим не нежить
                        if (IsUnitType(target, UNIT_TYPE_UNDEAD) == false && GetUnitState(target, UNIT_STATE_LIFE) <  GetUnitState(target, UNIT_STATE_MAX_LIFE)){
                            // Добавим здоровья в размере: уровень способности * HealAlly
                            SetUnitState(target, UNIT_STATE_LIFE, GetUnitState(target, UNIT_STATE_LIFE) + AbilityLevel * HealAlly);
                            // Сразу уничтожим проигранный эффект
                            DestroyEffect(AddSpecialEffectTarget(HealEffect, target, "origin"));
                        }
                    }
                    
                    // Враждебные войска
                    if (IsPlayerEnemy(GetOwningPlayer(caster), GetOwningPlayer(target))){
                        // Кастуем Замедление
                        DummyCastBuff(GetOwningPlayer(caster), target, EnemyBuffID, EnemyBuffOrder, 0.1);
                        
                        // Наносим урон нежити
                        if (IsUnitType(target, UNIT_TYPE_UNDEAD)){
                            // Наносим урон от лица кастера в размере: уровень способности * DamageEnemy
                            UnitDamageTarget(caster, target, AbilityLevel * DamageEnemy, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS);
                            // Сразу уничтожим проигранный эффект
                            DestroyEffect(AddSpecialEffectTarget(DamageEffect, target, "origin"));   
                        }
                    }
                }
                
                GroupRemoveUnit(g, target); // Убираем юнита из группы
            }

С кастером так просто не получится ибо нам нужно посчитать количество вылеченного здоровья. Так сделаем это
//! zinc
library SpellMassHeal requires Dummy {
    // Вне функции переменные будут приватными и глобальными. Нет смысла лишний раз это указывать
    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";
    
    real HealCaster = 0.25; // На сколько будем умножать вылеченное здоровье, чтобы вылечить кастера
    integer AllyBuffID = 'Ainf'; // равкод способности Духовное пламя 
    string AllyBuffOrder = "innerfire"; // приказ способности Духовное пламя
    integer EnemyBuffID = 'Aslo'; // равкод способности Замедление
    string EnemyBuffOrder = "slow"; // приказ способности Замедление
    integer CasterBuffID = 'Aroa'; // равкод способности Рёв
    string CasterBuffOrder = "roar"; // приказ способности Рёв
    
        
    //функция 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 caster = GetTriggerUnit(); // Сократим для читабельности
            unit target; // Заводим локального юнита для перебора сразу
            real x = GetUnitX(caster); // По возможности используем координаты,
            real y = GetUnitY(caster); // что-бы не плодить лишние location
            real range = 400.0; // Устанавливаем радиус способности
            real AbilityLevel = I2R(GetUnitAbilityLevel(caster, AbilityID));
            real CountCasterHeal = 0.0; // Заведём счётчик вылеченного здоровья
            real CountMustHeal = 0.0; // Она нам понадобится чуть ниже
            
            // Добавляем в группу живых юнитов вокруг кастера
            GroupEnumUnitsInRange(g, x, y, range, Condition( function() -> boolean {
                // Проверяем жив ли юнит
                return GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > 0.405;
            }));
            
            // Перебираем юнитов в группе
            while (true) {
                target = FirstOfGroup(g); // Выбираем первого юнита в группе
                if (target == null) { break; } // Если группа пустая, юнит не выберется и мы выйдем из цикла
                
                // Если юнит не сам кастер
                if (caster != target) {
                
                    // Дружественные войска
                    if (IsPlayerAlly(GetOwningPlayer(caster), GetOwningPlayer(target))){
                        // Кастуем Духовное пламя
                        DummyCastBuff(GetOwningPlayer(caster), target, AllyBuffID, AllyBuffOrder, 0.1);
                        // Лечим не нежить
                        if (IsUnitType(target, UNIT_TYPE_UNDEAD) == false && GetUnitState(target, UNIT_STATE_LIFE) <  GetUnitState(target, UNIT_STATE_MAX_LIFE)){
                            // Добавим здоровья в размере: уровень способности * HealAlly
                            CountMustHeal = AbilityLevel * HealAlly; // Сколько мы должны вылечить
                            SetUnitState(target, UNIT_STATE_LIFE, GetUnitState(target, UNIT_STATE_LIFE) + CountMustHeal);
                            // Добавим вылеченное здоровье в общий счётчик
                            CountCasterHeal = CountCasterHeal + RMinBJ(CountMustHeal, GetUnitState(target, UNIT_STATE_MAX_LIFE) - GetUnitState(target, UNIT_STATE_LIFE));
                            // Сразу уничтожим проигранный эффект
                            DestroyEffect(AddSpecialEffectTarget(HealEffect, target, "origin"));
                        }
                    }
                    
                    // Враждебные войска
                    if (IsPlayerEnemy(GetOwningPlayer(caster), GetOwningPlayer(target))){
                        // Кастуем Замедление
                        DummyCastBuff(GetOwningPlayer(caster), target, EnemyBuffID, EnemyBuffOrder, 0.1);
                        
                        // Наносим урон нежити
                        if (IsUnitType(target, UNIT_TYPE_UNDEAD)){
                            // Наносим урон от лица кастера в размере: уровень способности * DamageEnemy
                            UnitDamageTarget(caster, target, AbilityLevel * DamageEnemy, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS);
                            // Сразу уничтожим проигранный эффект
                            DestroyEffect(AddSpecialEffectTarget(DamageEffect, target, "origin"));   
                        }
                    }
                }
                
                GroupRemoveUnit(g, target); // Убираем юнита из группы
            }
            DestroyGroup(g); // Удаляем группу
            
            // Лечим кастера или кастуем Рёв
            if (CountCasterHeal > 0.0) {
                // Добавим здоровья кастеру
                SetUnitState(caster, UNIT_STATE_LIFE, GetUnitState(caster, UNIT_STATE_LIFE) + CountCasterHeal * HealCaster);
                // Сразу уничтожим проигранный эффект
                DestroyEffect(AddSpecialEffectTarget(HealEffect, caster, "origin"));
            } else {
                // Кастуем Рёв, а так как анимация заклинания крепится к origin даммика,
                // дадим даммику пожить подольше чем 0.1 секунды
                DummyCastAOE(GetOwningPlayer(caster), GetUnitX(caster), GetUnitY(caster), CasterBuffID, CasterBuffOrder, 1.5);
            }
            
            /*
            По идее мы должны ещё вызвать
            
            GroupClear(g)
            target = null
            
            но мы это уже сделали при переборе
           */
           caster = null;
           g = null; // Очищаем переменную группы
        });
        t = null; // Так как триггер у нас в локальной переменной, то нужно её очистить.
    }
}
//! endzinc
Придумать красивое описание к заклинанию я снова оставлю в качестве домашнего задания.


Views: 958

sLIL MID #1 - 6 months ago 1
Голосов: +1 / -0
Очень полезно, спасибо.
Bergi_Bear #2 - 6 months ago 1
Голосов: +1 / -0
sLIL MID, Тут кстати больше инфы
Особенно есть настройка самого даммика в РО, чего нет в этой статье