Добавлен , опубликован
Алгоритмы, Наработки и Способности
Способ реализации:
cJass
Тип:
Способность
Версия Warcraft:
1.26+
Требования: JNGP (1.26-1.28) / SharpCraft (1.28.2+)
Список изменений
Версия 1.1+
  1. исправлен маленький баг, когда при шансе chance выпадало число chance и способность не срабатывала (пропустил в условии <= меньше ИЛИ РАВНО);
  2. высота снаряда при появлении теперь также зависит от высоты кастера.
Версия 1.1
  1. убраны все требования, кроме JNGP;
  2. убрана вариативность использования урона из-за ненадобности/собственной надстройки;
  3. добавлена возможность блокировать способности, чтобы те не вызывали эффект;
  4. добавлена возможность вызывать эффект исключительно разрешенным способностям;
  5. добавлен шанс срабатывания (по умолчанию – 100%);
  6. чуть больше комментариев.
Небольшая наработка немного импровизированной способности. cJass + MUI.
Идея by A.W.K., реализация Atesla. Некоторые наработки взяты из карты NazarPunk для более углубленной настройки библиотеки.
Инструкция по импорту:
  1. скопировать способность "Парад огня" и юнита "Дамми_Фаербол";
  2. скопировать папку со скриптами "Spell";
  3. настроить в библиотеке id способности и дамми, если они отличны от оригинала;
  4. выдать способность необходимым юнитам;
  5. опционально: настроить разрешенные/заблокированные способности, которые вызывают эффект "Парада огня", функции:
AddSpellActive(id способности) / AddSpellBlocked(id способности)
Примечание: у AddSpellActive() приоритет над AddSpellBlocked(), поэтому нужно использовать что-то одно.
Описание:
При использовании ЛЮБОЙ/РАЗРЕШЕННОЙ (в зависимости от настройки) способности мгновенно выпускает в случайного врага в радиусе radius (по умолчанию – 800м) огненный шар. Шар наносит урон, зависящий от потраченной на заклинание маны. Пассивная способность.
В карте для примера были заблокированы способности: Огненный столб (AHfs), Винные пары (ANdh), Леденящий крик (ANht)
Код способности
#include "cj_types.j"
#include "cj_antibj_base.j"
native UnitAlive takes unit id returns boolean

library FireBall initializer Init {

    /* Эти переменные не трогаем */
    private timer fb_timer = CreateTimer(); // Обязательная переменная. Глобальный таймер, который проходит по массиву
    private timer fb_timer_mana = CreateTimer(); // Таймер для маны
    private int fb_count = 0; // Количество срабатываний способности. Для поддержки MUI
    private bool fb_timer_active = false; // Активен ли таймер в данный момент
    private float fb_mana[]; // Сюда заносим ману
    private unit fb_unit[]; // Здесь хранятся кастеры
    private int spells_blocked[]; // Сюда заносим способности, которые не срабатывают с пассивкой
    private int spells_blocked_count = 0; // Кол-во заблоченных способностей
    private int spells_active[]; // Сюда заносятся способности, которые срабатывают с пассивкой. Игнорирует переменную выше
    private int spells_active_count = 0; // Кол-во срабатываемых способностей
    
   /* Настраиваемые переменные */
    private constant integer id = 'u000'; // Дамми-снаряд
    private constant player owner = Player( PLAYER_NEUTRAL_PASSIVE ); // Владелец дамми-снаряда
    private constant int pf = 'APOF'; // ID необходимой способности
    private constant int chance = 100; // Шанс срабатывания способности в процентах, от 1 до 100
    private constant string eff = "Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl"; // Эффект при взрыве шара
    private constant string att = "chest"; // На каком участке тела взрывается эффект
    private constant float period = .03125; // Период таймера снаряда: 1/32 секунды
    private constant float speed = 800. / ( 1. / period ); // Расстояние, которое снаряд пройдёт за каждый тик таймера
    private constant float radius = 800.; // Радиус применения шара
    private constant float distance = 50.; // Необходимое расстояние между шаром и целью, чтобы шар взорвался и нанес урон
    private constant float z = 75.; // Высота появления снаряда
    
    private void AddSpellBlocked( int spell ) {
        spells_blocked[spells_blocked_count] = spell;
        spells_blocked_count++;
    }
    
    private void AddSpellActive( int spell ) {
        spells_active[spells_active_count] = spell;
        spells_active_count++;
    }
    
    private float DistanceBetweenCoords( float x1, float y1, float x2, float y2 ) { // Расстояние между двумя точками
        float dx = x2 - x1;
        float dy = y2 - y1;
        return SquareRoot( dx * dx + dy * dy );
    }
    
    private void onDamage( unit hero, unit enemy, float damage ) { // Нанесение урона и вызов эффекта
        UnitDamageTarget( hero, enemy, damage, false, true, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_FIRE, null );
        DestroyEffect( AddSpecialEffectTarget( eff, enemy, att ) );
    }
    
    private void GetAbilityManaCost() { // Проходимся по массиву и отмеряем ману, передаем ее в стуктуру
        int count = fb_count - 1;
        loop {
        exitwhen count < 0
            fb_mana[count] = fb_mana[count] - GetUnitState( fb_unit[count], UNIT_STATE_MANA ) + 0.5;
            fb.new( fb_unit[count], count );
            count--;
        }
        if count < 0 {
            fb_count = 0;
            fb_timer_active = false;
            PauseTimer( fb_timer_mana );
        }
    }

    struct fb extends array{ // Структура способности
        unit caster;
        unit dummy;
        unit target;
        float mana_cost;
        float x;
        float y;

        static thistype count = 0;

        static thistype new( unit caster, int count_fb ) {
			thistype this = count;
            count++;
            this.caster = caster;
            this.x = GetUnitX( caster );
            this.y = GetUnitY( caster );
            this.target = null;
            this.mana_cost = I2R( R2I( fb_mana[count_fb] ) );
            group G = CreateGroup();
            unit enemy = null;
            player pc = GetOwningPlayer( caster );
            GroupEnumUnitsInRange( G, x, y, radius, null );
            loop{
                enemy = GroupPickRandomUnit( G );
                if ( UnitAlive( enemy ) && IsPlayerEnemy( GetOwningPlayer( enemy ), pc ) ){
                    this.target = enemy;
                    this.dummy = CreateUnit( owner, id, x, y, GetUnitFacing( this.caster ) );
                    SetUnitState( this.dummy, UNIT_STATE_MANA, GetUnitState( this.dummy, UNIT_STATE_MAX_MANA ) );
                    SetUnitFlyHeight( this.dummy, GetUnitFlyHeight( caster ) + z, 0. );
                }
                GroupRemoveUnit( G, enemy );
            exitwhen ( this.target != null || enemy == null )
            }
            if ( this.dummy == null ) { delete(); }
            GroupClear( G );
            DestroyGroup( G );
            G = null;
            return 0;
        }
        
        bool continue( float x, float y, float xc, float yc ) { return ( DistanceBetweenCoords( x, y, xc, yc ) <= distance || !UnitAlive( target ) ) }
        bool actions() {
            float xd = GetUnitX( dummy );
            float yd = GetUnitY( dummy );
            float xt = GetUnitX( target );
            float yt = GetUnitY( target );
            if ( continue( xd, yd, xt, yt ) ) { return delete(); }
            float zt = GetUnitFlyHeight( target ) + z;
            float a = Atan2( yt - yd, xt - xd );
            float d = DistanceBetweenCoords( xd, yd, xt, yt );
            SetUnitX(dummy, xd + speed * Cos( a ) );
            SetUnitY(dummy, yd + speed * Sin( a ) );
            SetUnitFlyHeight( dummy, d + zt, 1000. );
            SetUnitFacing( dummy, a );
            return true;
        }

        bool delete() {
            RemoveUnit( dummy );
            if ( UnitAlive( target ) ) { onDamage( caster, target, mana_cost ); }
            caster = null;
            dummy = null;
            target = null;
            count--;
            if ( ( this ) < ( count ) ){
                caster = count.caster;
                dummy = count.dummy;
                target = count.target;
                mana_cost = count.mana_cost;
                x = count.x;
                y = count.y;
                return false;
            }
            return true;
        }

    }
    
    private void update() {
        fb i = fb.count - 1;
        loop{
        exitwhen i < 0
            if ( i.actions() ) { i--; }
        }
    }

    private void FireBall_Init() { // Функция, вызываемая при срабатывании
        if ( GetRandomInt( 1, 100 ) <= chance ) {
            fb_unit[fb_count] = GetTriggerUnit();
            fb_mana[fb_count] = GetUnitState( fb_unit[fb_count], UNIT_STATE_MANA );
            fb_count++;
            if !fb_timer_active {
                fb_timer_active = true;
                TimerStart( fb_timer_mana, .010, true, function GetAbilityManaCost );
            }
        }
    }
    private bool GetFBSpells( int spell ) {
        int count = 0;
        bool have_spell = false;
        if ( spells_active_count > 0 ) {
            count = spells_active_count - 1;
            loop{
            exitwhen have_spell || count < 0
                if ( spells_active[count] == spell ) { have_spell = true; }
                count--;
            }
        return have_spell;
        } if ( spells_blocked_count > 0 ) {
            have_spell = true;
            count = spells_blocked_count - 1;
            loop{
            exitwhen !have_spell || count < 0
                if ( spells_blocked[count] == spell ) { have_spell = false; }
                count--;
            }
        } else { have_spell = true; }
        return have_spell;
    }
    private bool FireBall_Cond()  { return ( GetUnitAbilityLevel( GetTriggerUnit(), pf ) > 0 && GetFBSpells( GetSpellAbilityId() ) ); }

    private void Init() {
        DoNotSaveReplay();
        FogEnable( false );
        FogMaskEnable( false );
        Preload( eff );
        
        AddSpellBlocked( 'AHfs' ); AddSpellBlocked( 'ANdh' ); AddSpellBlocked( 'ANht' );
        
        trigger t = CreateTrigger();
        TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT );
        TriggerAddCondition( t, Condition( function FireBall_Cond ) );
        TriggerAddAction( t, function FireBall_Init );
        TimerStart( fb_timer, period, true, function update );
    }

}
`
ОЖИДАНИЕ РЕКЛАМЫ...
3
29
5 лет назад
3
  • Тащить мемхак ради одной функции глупо. Можно просто проверять % оставшейся маны.
  • Видео можно конвертировать в .gif онлайн
  • Лучше сделать возможность настраивать заклинания, при которых срабатывает спэлл.
0
8
5 лет назад
Отредактирован Atesla
0
  • Тащить мемхак ради одной функции глупо. Можно просто проверять % оставшейся маны.
Знаю, что мемхак был такой себе идей, однако он позволяет узнать ТОЧНОЕ кол-во маны, которая будет потрачена на спелл. Почитав обсуждение того, как можно это сделать без мемхака, я пришел к тому, что не хотел бы словить тучу багов и прочих ништяков, ибо придется делать много проверок, а способности бывают разные и есть погрешность того, что кол-во маны отловится неточное (то бишь, расчет был сделан на абсолютно любой спелл). Либо я ошибаюсь. Вообще нужно тщательно проверить все это...
  • Видео можно конвертировать в .gif онлайн
Видео конвертить в гиф я не хотел, потому что видео можно спокойно промотать до нужного момента без всякого труда.
  • Лучше сделать возможность настраивать заклинания, при которых срабатывает спэлл.
И да, изначально планировал сделать шанс срабатывания, но после убрал. Можно сделать какие-то настройки, но я пока не знаю какие: с идеями у меня все очень плохо.
UPD: кажись, я понял твою идею. Типа с какими спеллами она работать не собирается? Или же, наоборот, с какими она работает исключительно. Тут вот интересный такой момент...
0
29
5 лет назад
Отредактирован nazarpunk
0
Знаю, что мемхак был такой себе идей
Лучше сделайте фиксированный урон и уберите мемхак. Кому нужно, сам себе настроит. Я для этих целей функцию в настройки выношу.
function onDamage(unit caster, unit target, integer level){
	real damage = level*100;
	UnitDamageTarget(caster, target, damage, false, true, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS);
}
потому что видео можно спокойно промотать до нужного момента без всякого труда.
Можно записать более короткое видео, которое не имеет смысла проматывать. А так мне лень каждый раз в видео тыкать чтоб оно соизволило запуститься.
Atesla:
Можно сделать какие-то настройки, но я пока не знаю какие
Заклинания, при которых срабатывает/не срабатывает спэлл. Ибо если кто-то захочет, чтоб он срабатывал только от фаэрбола будет опечален.
1
32
5 лет назад
1
Кстати, если первая версия мемхака, то вставки nocjass не нужны
А ещё снарядную систему можно сделать и дефолтную, через дамми каст:
  • делает способность на основе кислотной бомбы алхимика (самая лучшая)
  • настраиваем в ней снаряд, бафф, урон, угол снаряда
  • делаем даммикаст ( я там понял на случайную цель)
  • ловим урон с бафом
  • удаляем бафф, лечим
  • и наносим триггерный урон
А так да, мемхак ради одной функции GetAbilityManaCost, весьма спорное решение, но если это часть большой системы, тогда всё норм +
1
32
5 лет назад
1
Как насчет делать снаряды из дамми каста аксид бомбы и ловить попадание по 0.00 урона и баффу от аксид бомбы, с мемхком можно кастовать от лица кастера без даммика, как это делают руны.
Мемхак конечно тяжелая наработка и ради 1 функции перебор, но что мешает запилить с помощью мемхака целого героя?
Bergi_Bear, у меня подсмотрел в карте эту систему?
0
32
5 лет назад
0
Bergi_Bear, у меня подсмотрел в карте эту систему?
неа, у тебя я только кд предметов подсмотрел
Вообще у меня в танчиках только использовалась такая система снарядов из даммиков, потому что нужно было с ними манипулировать (возможность уничтожить в полёте/ отклонить во время полёта/ нанести урон прям в полёте), но этой громоздкой системой никто и не пользовался, хотя 200 + таких юнитов игра держала без просадки фпс нормально, но я решил всё упросить и будущем запускать снаряды только лишь на движке вара
0
29
5 лет назад
0
у меня подсмотрел в карте эту систему?
Эта система сама напрашивается, я тоже до неё додумался)
0
8
5 лет назад
0
А так да, мемхак ради одной функции GetAbilityManaCost, весьма спорное решение, но если это часть большой системы, тогда всё норм +
Изначально да, было сделано для отдельного героя-мага с полностью триггерными способностями, но я решил реализовать все отдельно и получилось не очень. В общем, остается теперь думать над системой получения маны.
0
27
5 лет назад
0
Наработка для 2019 года слишком бесполезная:
  • с новыми патчами такое делается в две строки
  • зачем здесь кастомная функция снаряда + к комментариям выше
  • процент потраченной маны можно посчитать и без мемхака
1
32
5 лет назад
1
остается теперь думать над системой получения маны
не надо городить костыли, или оставайтесь на мемхаке либо делайте на последнем патче, где вроде бы как есть GetAbilityManaCost (инфа не точная, лень открывать смотреть)
0
29
5 лет назад
0
где вроде бы как есть GetAbilityManaCost (инфа не точная, лень открывать смотреть)
Загруженные файлы
0
8
5 лет назад
0
процент потраченной маны можно посчитать и без мемхака
Можно, я об этом писал – с глитчами и прочими вкусняшками.
На новых патчах не сижу до Рефорджа и не вижу смысла писать об этом.
А система, чтобы не плодить другую систему. Мне лично эта вкатывает.
0
27
5 лет назад
0
Atesla, наработки делаются для людей, чтобы упростить ту или иную задачу.
Здесь всё только усложнили, да и при чём она актуальна только для 1.26
0
8
5 лет назад
Отредактирован Atesla
0
PrincePhoenix:
Atesla, наработки делаются для людей, чтобы упростить ту или иную задачу.
Здесь всё только усложнили, да и при чём она актуальна только для 1.26
Потому и делалось для 1.26 версии, т.к. на более новых патчах такая реализация уже на блюдечке от самих Близзов.
UPD: Впрочем, у кого будет идея безболезненно и без багов сделать систему, которая узнает затраты маны на спеллы, то можно писать сюда.
0
32
5 лет назад
0
я как то делал но не могу вспомнить, на ум приходит только периодом 0,01 чекать количество маны текущее, и сравнивать изменения, как только изменение произошло значит мана потратилась, не думаю что это будет накладно для 10 героев, врядли способность будет более чем у 1 героя
0
8
5 лет назад
0
Bergi_Bear:
я как то делал но не могу вспомнить, на ум приходит только периодом 0,01 чекать количество маны текущее, и сравнивать изменения, как только изменение произошло значит мана потратилась, не думаю что это будет накладно для 10 героев, врядли способность будет более чем у 1 героя
Я бы хотел так сделать и не париться, но тут можно легко вызвать баг при одновременном касте спеллов (например, один юзает скилл, другой восстанавливает ману). Сами люди обсуждали это и выясняли. Если честно, я не хочу лезть на грабли.
Кому надо будет: xgm.guru/p/wc3/153351?postid=285485
0
27
5 лет назад
Отредактирован Феникс
0
Ну вот насколько 0.03 маны стоят внедрения MH и одновременно ломания поддержки всех новых патчей?
Если перевести это по уму в jass вообще избежишь любых проблем абуза
Загруженные файлы
0
29
5 лет назад
Отредактирован nazarpunk
0
Хорошо, что убрали мемхак, теперь люди смогут нормально в свою карту импортировать.
3
8
5 лет назад
3
NazarPunk:
Хорошо, что убрали мемхак, теперь люди смогут нормально в свою карту импортировать.
Счас еще и гифка подъедет :D
Чтобы оставить комментарий, пожалуйста, войдите на сайт.