Добавлен , опубликован
Алгоритмы, Наработки и Способности
Способ реализации:
Zinc
Тип:
Способность
Версия Warcraft:
1.26+

Blink Strike

MUI: да
Импорт: иконка
Утечки: нет
Требования: JNGP
Описание: Герой телепортируется к каждой цели на своём пути и наносит ей 100*уровень заклинания магического урона.
  • Имеет баф, для работы с другими заклинаниями
  • Прерывается оглушением и приказами игрока
  • Учитывает неуязвимость к магии
  • Гнев деревьев блокирует заклинание
  • Не использует даммикаст
  • Не паузит героя
  • Использует хэштаблицу для хранения id таймера



Технические подробности

Перенос в свою карту
Способности
  • 'AEbs' Blink Strike (герой) - способность для героя
  • 'ABbs' Blink Strike (бафф) - способность для наложения бафа
Заклинания/Эффекты
  • 'BEbs' Blink Strike - бафф способности
Триггеры
  • SpellBlinkStrike
Импорт
  • ReplaceableTextures\CommandButtons\BTNBlinkStrike.blp
  • ReplaceableTextures\CommandButtonsDisabled\DISBTNBlinkStrike.blp
  • ReplaceableTextures\PassiveButtons\PASBlinkStrike.blp
  • ReplaceableTextures\CommandButtonsDisabled\DISPASBlinkStrike.blp
Настройка
constant integer AbilityID = 'AEbs'; // Равкод способности
constant integer AbilityBuffID = 'ABbs'; // Равкод способности для баффа
constant integer BuffID = 'BEbs'; // Равкод баффа

constant integer PathItemID = 'wolg'; // Предмет для нахождения финальной точки

constant real TimerPeriod = 0.25; // Время между блинками: 1/4 секунды.
constant integer DamageDistance = 64; // Расстояние захвата юнита

constant string EffectCasterHandsPath = "Abilities\\Weapons\\AvengerMissile\\AvengerMissile.mdl"; // Еффект, который крепится к "hand left" и "hand right"
constant string EffectBlinkTarget = "Abilities\\Spells\\NightElf\\Blink\\BlinkTarget.mdl"; // Еффект блинка для точки
constant string EffectBlinkCaster = "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl"; // Еффект блинка для кастера

constant string CasterAnimationName = "Attack 2"; // Анимация при ударе
constant real CasterAnimationDuration = 0.9; // Длительность анимации при ударе у модели
constant integer CasterOpacity = 150; // Прозрачность кастера на момент заклинания 0 - 255

constant real CasterAnimationScale = CasterAnimationDuration/TimerPeriod*1; // Не трогать

hashtable HT = InitHashtable(); // Хэштаблица для таймера
// Можете вписать туда вашу таблицу, например:
// hashtable HT = udg_HashTable;

// Текстаг над целью. Настроен на эмуляцию критического удара
function addTextTag(widget target, string text) {
    real x = GetWidgetX(target);
    real y = GetWidgetY(target);
    texttag tt = CreateTextTag();
    SetTextTagText(tt, text+"!", 0.024);
    SetTextTagPos(tt, x, y, 0.0);
    SetTextTagColor(tt, 255, 0, 0, 255);
    SetTextTagVelocity(tt, 0.0,  0.04);
    SetTextTagFadepoint(tt, 2.0);
    SetTextTagLifespan(tt, 5.0);
    SetTextTagVisibility(tt, IsVisibleToPlayer(x, y, GetLocalPlayer()));
    SetTextTagPermanent(tt, false);
    tt = null;
}

// Нанесение урона по цели
function onDamage(unit caster, unit target, integer level) {
    // caster - юнит, использующий способность
    // target - цель
    // level - уровень способности
    real damage = level*100; // считаем урон от уровня способности
    UnitDamageTarget(
        caster,
        target,
        damage,
        true, // является ли атакой
        false, // является ли дальним боем
        ATTACK_TYPE_MAGIC,
        DAMAGE_TYPE_NORMAL,
        WEAPON_TYPE_WHOKNOWS
    );
    addTextTag(target, I2S(R2I(damage)));
}

// Дальность заклинания
function getRange(integer level) -> integer {
    // level - уровень способности
    return 800 + level*100;
}

// Ограничить количество целей. Снять ограничение: -1;
function getTargetLimit(integer level) -> integer {
    // level - уровень способности
    return -1;
}

// Проверяем, возможно ли кастовать заклинание
function canBlink(unit caster) -> boolean {
    // caster - юнит, использующий способность
    return (
        GetUnitAbilityLevel(caster, 'BEer') == 0 // Гнев деревьев
    );
}

// Проверка целей
function checkTarget(unit caster, unit target) -> boolean {
    // caster - юнит, использующий способность
    // target - цель проверки
    return (
        caster != target // Не кастер
        &&
        !IsUnitType(target, UNIT_TYPE_STRUCTURE) // Не здание
        &&
        !IsUnitType(target, UNIT_TYPE_FLYING) // Не летающий
        &&
        !IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) // Восприимчив к магии
        &&
        IsPlayerEnemy(GetOwningPlayer(caster), GetOwningPlayer(target)) // Враг
    );
}
Код заклинания
//! zinc
library SpellBlinkStrike {
    constant integer AbilityID = 'AEbs'; // Равкод способности
    constant integer AbilityBuffID = 'ABbs'; // Равкод способности для баффа
    constant integer BuffID = 'BEbs'; // Равкод баффа
    
    constant integer PathItemID = 'wolg'; // Предмет для нахождения финальной точки
    
    constant real TimerPeriod = 0.25; // Время между блинками: 1/4 секунды.
    constant integer DamageDistance = 64; // Расстояние захвата юнита
    
    constant string EffectCasterHandsPath = "Abilities\\Weapons\\AvengerMissile\\AvengerMissile.mdl"; // Еффект, который крепится к "hand left" и "hand right"
    constant string EffectBlinkTarget = "Abilities\\Spells\\NightElf\\Blink\\BlinkTarget.mdl"; // Еффект блинка для точки
    constant string EffectBlinkCaster = "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl"; // Еффект блинка для кастера
    
    constant string CasterAnimationName = "Attack 2"; // Анимация при ударе
    constant real CasterAnimationDuration = 0.9; // Длительность анимации при ударе у модели
    constant integer CasterOpacity = 150; // Прозрачность кастера на момент заклинания 0 - 255
    
    constant real CasterAnimationScale = CasterAnimationDuration/TimerPeriod*1; // Не трогать

    hashtable HT = InitHashtable(); // Хэштаблица для таймера
    // Можете вписать туда вашу таблицу, например:
    // hashtable HT = udg_HashTable;
    
    // Текстаг над целью. Настроен на эмуляцию критического удара
    function addTextTag(widget target, string text) {
        real x = GetWidgetX(target);
        real y = GetWidgetY(target);
        texttag tt = CreateTextTag();
        SetTextTagText(tt, text+"!", 0.024);
        SetTextTagPos(tt, x, y, 0.0);
        SetTextTagColor(tt, 255, 0, 0, 255);
        SetTextTagVelocity(tt, 0.0,  0.04);
        SetTextTagFadepoint(tt, 2.0);
        SetTextTagLifespan(tt, 5.0);
        SetTextTagVisibility(tt, IsVisibleToPlayer(x, y, GetLocalPlayer()));
        SetTextTagPermanent(tt, false);
        tt = null;
    }
    
    // Нанесение урона по цели
    function onDamage(unit caster, unit target, integer level) {
        // caster - юнит, использующий способность
        // target - цель
        // level - уровень способности
        real damage = level*100; // считаем урон от уровня способности
        UnitDamageTarget(
            caster,
            target,
            damage,
            true, // является ли атакой
            false, // является ли дальним боем
            ATTACK_TYPE_MAGIC,
            DAMAGE_TYPE_NORMAL,
            WEAPON_TYPE_WHOKNOWS
        );
        addTextTag(target, I2S(R2I(damage)));
    }
    
    // Дальность заклинания
    function getRange(integer level) -> integer {
        // level - уровень способности
        return 800 + level*100;
    }
    
    // Ограничить количество целей. Снять ограничение: -1;
    function getTargetLimit(integer level) -> integer {
        // level - уровень способности
        return -1;
    }
    
    // Проверяем, возможно ли кастовать заклинание
    function canBlink(unit caster) -> boolean {
        // caster - юнит, использующий способность
        return (
            GetUnitAbilityLevel(caster, 'BEer') == 0 // Гнев деревьев
        );
    }
    
    // Проверка целей
    function checkTarget(unit caster, unit target) -> boolean {
        // caster - юнит, использующий способность
        // target - цель проверки
        return (
            caster != target // Не кастер
            &&
            !IsUnitType(target, UNIT_TYPE_STRUCTURE) // Не здание
            &&
            !IsUnitType(target, UNIT_TYPE_FLYING) // Не летающий
            &&
            !IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) // Восприимчив к магии
            &&
            IsPlayerEnemy(GetOwningPlayer(caster), GetOwningPlayer(target)) // Враг
        );
    }
    
    //
    // Заклинание
    //
    
    // Polar - https://xgm.guru/p/wc3/polar
        function GetPolarOffsetX(real x, real distance, real angle) -> real {
            return x + distance * Cos(angle * bj_DEGTORAD);
        }
        function GetPolarOffsetY(real y, real distance, real angle) -> real {
            return y + distance * Sin(angle * bj_DEGTORAD);
        }
        function DistanceBetweenCoords(real x1, real y1, real x2, real y2) -> real {
            real dx = x2 - x1;
            real dy = y2 - y1;
            return SquareRoot(dx*dx + dy*dy);
        }
        function AngleBetweenCoords(real x1, real y1, real x2, real y2) -> real {
            return bj_RADTODEG * Atan2(y2 - y1, x2 - x1);
        }
        function AngleDifference(real a1, real a2) -> real {
            real x;
            a1 = ModuloReal(a1, 360);
            a2 = ModuloReal(a2, 360);
            if (a1 > a2) {
                x = a1;
                a1 = a2;
                a2 = x;
            }
            x = a2 - 360;
            if (a2 - a1 > a1 - x){
                a2 = x;
            }
            return RAbsBJ(a1 - a2);
        }
        function Perpendicular (real Xa, real Ya, real Xb, real Yb, real Xc, real Yc) -> real {
            return SquareRoot((Xa - Xc) * (Xa - Xc) + (Ya - Yc) * (Ya - Yc)) * Sin(Atan2(Yc-Ya,Xc-Xa) - Atan2(Yb-Ya,Xb-Xa));
        }
    // endPolar
    
    function isUnitAlive(unit target) -> boolean{
        return GetWidgetLife(target) > 0.405;
    }
    
    struct data {
        unit caster, target[8190];
        integer targetI, targetC, limit, level, timerID;
        real xc, yc, xt, yt, angle;
        effect effectCaster[2];
        boolean isCancel;
        
        static method create(unit caster, real xt, real yt) -> data {
            data this = data.allocate();
            item it;
            integer level = GetUnitAbilityLevel(caster, AbilityID);
            real xc = GetUnitX(caster);
            real yc = GetUnitY(caster);
            real distance = DistanceBetweenCoords(xc, yc, xt, yt);
            real angle = AngleBetweenCoords(xc, yc, xt, yt);
            real range = I2R(getRange(level));
            unit u, utemp;
            integer i, j, imin;
            timer t;
            integer tid;
            group g = CreateGroup();
            if (distance > range){
                xt = GetPolarOffsetX(xc, range, angle);
                yt = GetPolarOffsetY(yc, range, angle);
            }
            range = RMinBJ(distance, range);
            
            it = CreateItem(PathItemID, xt, yt);
            
            this.isCancel = false;
            this.level = level;
            this.limit = getTargetLimit(this.level);
            this.caster = caster;
            this.xc = xc;
            this.yc = yc;
            this.xt = GetItemX(it);
            this.yt = GetItemY(it);
            RemoveItem(it); it = null;
            
            this.angle = AngleBetweenCoords(this.xc, this.yc, this.xt, this.yt);
            
            this.effectCaster[0] = AddSpecialEffectTarget(EffectCasterHandsPath, this.caster, "hand left");
            this.effectCaster[1] = AddSpecialEffectTarget(EffectCasterHandsPath, this.caster, "hand right");
            
            GroupEnumUnitsInRange(g, xc, yc, range + DamageDistance, Filter(function() -> boolean {
                return isUnitAlive(GetFilterUnit());
            }));
            
            this.targetI = -1;
            this.targetC = -1;
            
            while(true){
                u = FirstOfGroup(g);
                if (u == null) {break;}
                if (
                    checkTarget(this.caster, u)
                    &&
                    R2I(RAbsBJ(Perpendicular(this.xc, this.yc, this.xt, this.yt, GetUnitX(u), GetUnitY(u)))) <= DamageDistance
                    &&
                    AngleDifference(AngleBetweenCoords(this.xc, this.yc, this.xt, this.yt), AngleBetweenCoords(this.xc, this.yc, GetUnitX(u), GetUnitY(u))) < 60
                ){
                    this.targetI = this.targetI + 1;
                    this.target[targetI] = u;
                }
                GroupRemoveUnit(g, u);
            }
            DestroyGroup(g); g = null; u = null;
            
            SetUnitPathing(this.caster, false);
            SetUnitTimeScale(this.caster, CasterAnimationScale);
            SetUnitVertexColor(this.caster, 255, 255, 255, CasterOpacity);
            UnitAddAbility(this.caster, AbilityBuffID);
            
            if (this.targetI >= 0){
                t = CreateTimer();
                tid = GetHandleId(t);
                i = 0;
                while(i < this.targetI){
                    imin = i;
                    j = i + 1;
                    while(j <= this.targetI){
                        if (
                            DistanceBetweenCoords(this.xc, this.yc, GetUnitX(this.target[j]), GetUnitY(this.target[j]))
                            <
                            DistanceBetweenCoords(this.xc, this.yc, GetUnitX(this.target[imin]), GetUnitY(this.target[imin]))
                        ){
                            imin = j;
                        }
                        j = j + 1;
                    }
                    utemp = this.target[i];
                    this.target[i] = this.target[imin];
                    this.target[imin] = utemp;
                    i = i + 1;
                }
                
                SaveInteger(HT, tid, 0, this);
                TimerStart(t, TimerPeriod, true, function data.callback);
            } else {
                this.destroy();
            }
            
            utemp = null;
            t = null;
            return this;
        }
        
        method destroy(){
            integer i;
            
            SetUnitPathing(this.caster, true);
            UnitRemoveAbility(this.caster, AbilityBuffID);
            UnitRemoveAbility(this.caster, BuffID);
            
            if (!this.isCancel){
                SetUnitX(this.caster, this.xt);
                SetUnitY(this.caster, this.yt);
                DestroyEffect(AddSpecialEffectTarget(EffectBlinkCaster, this.caster, "origin"));
            }
            
            SetUnitTimeScale(this.caster, 1);
            if (isUnitAlive(this.caster)){
                SetUnitAnimation(this.caster, "stand");
            } else {
                SetUnitAnimation(this.caster, "death");
            }
            SetUnitVertexColor(this.caster, 255, 255, 255, 255);
            
            for(0 <= i <= 1){
                DestroyEffect(this.effectCaster[i]);
                this.effectCaster[i] = null;
            }
            for(0 <= i <= this.targetI){
                this.target[i] = null;
            }
            
            this.caster = null;
            this.deallocate();
        }
        
        static method callback(){
            timer t = GetExpiredTimer();
            integer tid = GetHandleId(t);
            data this = LoadInteger(HT, tid, 0);
            integer order = GetUnitCurrentOrder(this.caster);
            this.targetC = this.targetC + 1;
            
            if ((order != 0 && order != 851983 /* attack */ && order != 851972 /* stop */) || !isUnitAlive(this.caster)){
                this.isCancel = true;
            }

            if (this.targetC > this.targetI || this.isCancel || (this.limit >= 0 && this.targetC >= this.limit)){
                this.destroy();
                PauseTimer(t);
                DestroyTimer(t);
                FlushChildHashtable(HT, tid);
            } else {
                if (isUnitAlive(this.target[targetC])){
                    onDamage(this.caster, this.target[targetC], this.level);
                    SetUnitX(this.caster, GetUnitX(this.target[targetC]));
                    SetUnitY(this.caster, GetUnitY(this.target[targetC]));
                    IssueImmediateOrderById(this.caster, 851972 /* stop */);
                    SetUnitAnimation(this.caster, CasterAnimationName);
                }
                this.target[targetC] = null;        
            }
            
            t = null;
        }
    }
    
    function onInit(){
        integer i;
        trigger t1 = CreateTrigger();
        trigger t2 = CreateTrigger();
        
        for (0 <= i < bj_MAX_PLAYER_SLOTS){
            TriggerRegisterPlayerUnitEvent(t1, Player(i), EVENT_PLAYER_UNIT_SPELL_CAST, null);
            TriggerRegisterPlayerUnitEvent(t2, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null);
        }
        TriggerAddCondition(t1, Filter(function() -> boolean {
            if (GetSpellAbilityId() == AbilityID && GetUnitAbilityLevel(GetTriggerUnit(), AbilityBuffID) == 0){
                DestroyEffect(AddSpecialEffect(EffectBlinkTarget, GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit())));
            }
            return false;
        }));
        TriggerAddCondition(t2, Filter(function() -> boolean {
            if (
                GetSpellAbilityId() == AbilityID
                &&
                GetUnitAbilityLevel(GetTriggerUnit(), AbilityBuffID) == 0
                &&
                canBlink(GetTriggerUnit())
            ){
                data.create(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY());
            }
            return false;
        }));
        
        t1 = null; t2 = null;
    }
}
//! endzinc
`
ОЖИДАНИЕ РЕКЛАМЫ...

Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
2
26
5 лет назад
2
Вот это очень круто!)
Только не понял, почему на последней гифке здоровье героя тратится?
Столь мощно Аура Возмездия работает?
0
21
5 лет назад
0
8gabriel8, там же аура у хранителя рощи
да
вот тогда уточнить хочу
UnitDamageTarget(this.caster, this.target[targetC], this.damage, true, false, AttackType, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
это движок считает атакой или все-таки просто уроном?
если в инвентаре есть башер, может ли выпасть баш? и т. п.
0
29
5 лет назад
0
Столь мощно Аура Возмездия работает?
Там 300 магического урона по тяжёлой броне, которая с футмана по 420 хп снимает, конечно возвратка будет))
это движок считает атакой или все-таки просто уроном?
Это просто урон, на который не работают модификаторы.
this.damage, true, false  // <- эта переменная отвечает за ближний/дальний бой при нанесении урона, на дальний бой не сработает возврат
0
21
5 лет назад
0
NazarPunk, эх, так и предполагалось.
Просто нужен подобный скилл, но там должно именно считаться полноценной реальной атакой на каждого врага с возможностью прока баша, молний, срабатывания триггеров "меня атаковали" и т. п. хреновин
Ну идея через мемхак и хэш пилить, в принципе должно работать, но придется чуть попотеть
0
29
5 лет назад
0
Ну идея через мемхак и хэш пилить
Что все так мемхак обожают?
ClotPh:
именно считаться полноценной реальной атакой
Это реальное нанесение урона, отловить можно по наличию бафа у юнита и наложить всё что нужно.
2
21
5 лет назад
2
NazarPunk, а как ты без мемхака сделаешь именно полноценную АТАКУ при таком спелле? А если нужно, чтобы быстро герой через всех промелькнул, например, по линии за 0.6 секунды и всех супербыстро проатаковал, как The Dark Lady в ХоН?
В том-то и дело, что нанесение урона юниту и его атака - разные, хотя часто и связанные вещи
Атаку можно имитировать, но это костыль + бд для всех триггеров, триггерящихся на атаку
Мемхак не то чтобы обожают, особенно с ятп появлением его части функций на лицухе, но без него на 1.26 очень многое нормально не сделать
////
Мне вот именно так и надо
Чтобы герой сделал дэш по линии и всех врагов на пути проатаковал обычной атакой за это время, каждого ровно 1 раз
планирую через хэш, сброс кулдауна атаки с правильной скоростью ее анимации
хз, вроде должно получиться

вот именно атака нужна, даже еще раз уточню, а не урон от атаки, чтобы прокали всякие башеры-маелштормы, могли прокнуть криты и т. д.
с уроном-то от атаки это мемхаком вообще раз плюнуть делается
2
29
5 лет назад
2
а как ты без мемхака сделаешь именно полноценную АТАКУ при таком спелле?
Мне интересно реализовать заклинанание на том, что движок позволяет и не париться с переносом на другие версии. С таким успехом можно взять тот же юнити и пилить на нём всё, что угодно.
0
21
5 лет назад
0
NazarPunk, ну да, я претензий к реализации твоего-то спелла не имею, это просто уже немного оффтоп пошел
Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.