Добавлен , опубликован
Способ реализации:
Версия Warcraft:
Ещё одна способность Бурана, но в этот раз Цветного!

Видео

Код

Для понимания сути происходящего ознакомьтесь с предыдущим ресурсом.
//DestroyEffect( AddSpecialEffect ) - создаётся эффект и сразу же удаляется, проигрывая свою анимацию смерти (чтоб уместить всё в одну строку)

globals
    hashtable HT = InitHashtable() //Создаётся хэш-таблица
    group Group = CreateGroup() //Создаём группу, которую будем использовать для моментальной выборки юнитов

    unit Caster //Создаётся переменная для кастеров
    unit Target //Создаётся переменная для целей
    real CastX //Создаётся переменная для точки каста Y
    real CastY //Создаётся переменная для точки каста X
    timer Timer //Создаётся переменная для таймеров
    integer TimerId //Создаётся переменная для хэндл-айди таймеров
    integer Tick //Создаётся переменная для записи тика таймера

    constant integer Blizzard_Id = 'A000'
	constant real Blizzard_Range = 150
	constant real Blizzard_Damage = 30
endglobals

native UnitAlive takes unit id returns boolean

function Blizzard_Group takes nothing returns nothing
	set Target = GetEnumUnit()
    
	if UnitAlive( Target ) and IsUnitEnemy( Target, GetOwningPlayer( Caster ) ) and not IsUnitType( Target, UNIT_TYPE_STRUCTURE ) then
		call UnitDamageTarget( Caster, Target, Blizzard_Damage, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_UNKNOWN, WEAPON_TYPE_WHOKNOWS )
    endif
endfunction

function Blizzard_Timer takes nothing returns nothing
	local integer lastTick
	local integer xKey
	local integer yKey
    
	set Timer = GetExpiredTimer()
	set TimerId = GetHandleId( Timer )
    
	set Caster = LoadUnitHandle( HT, TimerId, 'cstr' )
	set CastX = LoadReal( HT, TimerId, 'cstX' )
	set CastY = LoadReal( HT, TimerId, 'cstY' )

	set Tick = LoadInteger( HT, TimerId, 'tick' ) //Выгрузка в Tick счёт тиков таймера
	call SaveInteger( HT, TimerId, 'tick', Tick + 1 ) //Сохранение тик таймера в хэш таблицу по дочернему ключу tick
    set lastTick = LoadInteger( HT, TimerId, 'last' ) //Выгруза в lastTick последний тик таймера (смещается на 8 вперёд, за 8 тиков эффект падает на землю)

	if not LoadBoolean( HT, TimerId, 'ends' ) then //Если флаг сохранённый под дочерним ключем ends = false (изначально будет false ибо при чтении отсутствующего значения всегда возвращается 0 который в случае boolean интерпретируется как false) то спелл продолжает работу
		set CastX = CastX + Blizzard_Range * GetRandomReal( -1, 1 ) //Спавн эффекта в случайной точке в квадрате размером 300 (Для простоты рассчётов используется квадрат вместо окружности)
		set CastY = CastY + Blizzard_Range * GetRandomReal( -1, 1 ) //Спавн эффекта в случайной точке в квадрате размером 300 (Для простоты рассчётов используется квадрат вместо окружности)

		set lastTick = Tick + 8 //Смещение lastTick вперёд на 8 от текущего тика (что-бы урон проходил когда эффекты успевают падать)
		call SaveInteger( HT, TimerId, 'last', lastTick )

		set xKey = lastTick * 2 //запись в xKey = lastTick * 2 (xKey - по сути ключ созданный для удобства получения координаты X на 8 тиков позже)
		set yKey = xKey + 1 //запись в yKey = xKey + 1 (yKey - по сути ключ созданный для удобства получения координаты Y на 8 тиков позже)

		call SaveReal( HT, TimerId, xKey, CastX )
		call SaveReal( HT, TimerId, yKey, CastY )

        if GetRandomInt( 0, 1 ) == 0 then
            call DestroyEffect( AddSpecialEffect( "Rain of Fire.mdx", CastX, CastY ) )
        else
            call DestroyEffect( AddSpecialEffect( "Rain of Fire Fel.mdx", CastX, CastY ) )
        endif
	endif

	set xKey = Tick * 2 //Запись в xKey = Tick * 2 (для получения координаты X для текущего тика (координата которую мы сохраняли 8 тиков назад))
	set yKey = xKey + 1 //Запись в yKey = xKey + 1 (для получения координаты X для текущего тика (координата которую мы сохраняли 8 тиков назад))

	if HaveSavedReal( HT, TimerId, xKey ) then //Условие что значение X найдено (дополнительная проверка на Y не имеет смысла)
        call GroupEnumUnitsInRange( Group, LoadReal( HT, TimerId, xKey ), LoadReal( HT, TimerId, yKey ), Blizzard_Range, null )
        call ForGroup( Group, function Blizzard_Group )
        call GroupClear( Group )
	endif

    if GetUnitCurrentOrder( Caster ) != OrderId( "Blizzard" ) then
	//if GetUnitCurrentOrder( Caster ) != 0xd0079 then - этот вариант работает быстрее, так как использует id приказа без лишней возни со строками (без OrderId)
		call SaveBoolean( HT, TimerId, 'ends', true ) //Сохранение в хэш-таблицу флага под дочерним ключем ends = true
    endif

	if LoadBoolean( HT, TimerId, 'ends' ) and Tick >= lastTick then //Условие что флаг сохранненый под дочерним ключем ends = true
        call PauseTimer( Timer )
        call DestroyTimer( Timer )
        call FlushChildHashtable( HT, TimerId )
	endif
endfunction

function Blizzard_Actions takes nothing returns nothing
    if GetSpellAbilityId() == Blizzard_Id then
        set Timer = CreateTimer()
        set TimerId = GetHandleId( Timer )

        call SaveUnitHandle( HT, TimerId, 'cstr', GetTriggerUnit() )
        call SaveReal( HT, TimerId, 'cstX', GetSpellTargetX() )
        call SaveReal( HT, TimerId, 'cstY', GetSpellTargetY() )
        call SaveInteger( HT, TimerId, 'time', 0 )
        call TimerStart( Timer, 0.1, true, function Blizzard_Timer )
    endif
endfunction

function InitTrig_Blizzard 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 TriggerAddAction( t, function Blizzard_Actions )
	set t = null
    
    call FogEnable( false )
    call FogMaskEnable( false )
endfunction

Требования

Скопировать код из шапки карты и триггера со спеллом к себе на карту, если такие глобальные переменные уже имеются, то копировать не надо. Поменять равкод способности в константах если он другой (скорее всего другой), Скопировать файлы из импорта к себе на карту.
`
ОЖИДАНИЕ РЕКЛАМЫ...
30
Зачем ты здесь объявляешь одноимённую локалку Tick?
function InitTrig_Blizzard takes nothing returns nothing //Функция инициализации триггера (из функции main вызывается InitCustomTriggers() которая вызывает инициализацию всех триггеров на карте)
	local trigger t = CreateTrigger() //Создание триггера
	local integer Tick = 0 //Объявление целочисленной переменной
Притом со странным для этого контекста именем? Чем тебе стандартная i не угодила?

Убери меня из помощи, у новичков это вызовет недоверие. У нас одно сообщество и помогать в порядке вещей. Если писать каждого, то это будет ужас. Да и человеку со стороны эта информация никакой пользы не приносит. А если он заинтересуется, то всё есть в комментах.

ИМХО необходимо снижать градус комментирования.
loop //Начало цикла
Лучше в шапке написать, что для понимания сути происходящего ознакомьтесь с предыдущим ресурсом. А здесь убрать комментарии открытия и закрытия блоков. Оно замыливает глаза и отвлекает от происходящего. А у новичков на данном этапе скила чтения кода ещё нет, так что ты им медвежью услугу оказываешь.
Ответы (5)
15
LastUchiha, аа, я когда i на Tick в самом спелле менял оно автоматом сменило и там)
30
LastUchiha, ну дык ты же автозаменой менял. Здесь нужно аккуратно.
15
nazarpunk, ок тогда уберу те комменты которые есть в прошлом ресурсе, а в этом оставлю те которых нету в прошлом ресурсе.
30
set CastX = CastX + Blizzard_Range * GetRandomReal( -1, 1 ) //Спавн эффекта в случайной точке в радиусе 300
set CastY = CastY + Blizzard_Range * GetRandomReal( -1, 1 ) //Спавн эффекта в случайной точке в радиусе 300
Это не радиус, это квадрат. Радиус выглядит следующим образом.
set theta = GetRandomReal(0, 2 * bj_PI)
set rho = r * SquareRoot(GetRandomReal(0, 1)) // r это радиус

set x = cx + rho * Cos(theta)
set y = cy + rho * Sin(theta)
Ответы (3)
15
nazarpunk, ну, для новичков я подумал что так проще будет объяснить.
30
LastUchiha, но ты же их вводишь в заблуждение и они думают про окружность. Нужно явно написать, что для простоты расчётов мы используем квадрат вместо окружности.
Ну и ещё можно поговорить про распределение...
Загруженные файлы
30
//Если флаг сохранённый под дочерним ключем ends = false (изначально false, даже если мы не сохранили ничего в него до выгрузки)
Здесь налицо явное непонимание. Изначально значение отсутствует, что можно проверить с помощью HaveSaved* функций. И по хорошему чтение отсутствующих значений должно давать ошибку, но тогда гуймуйщики не смогли бы писать гуймуй в следствие своей криворукости. Посему в случае отсутствия значения возвращается ноль и неявно приводится к необходимому типу. В случае с boolean это false.
Ответы (2)
15
nazarpunk, тогда --> (изначально будет false, так как ничего в хэш-таблицу под данным дочерним ключем записано не было)?
30
изначально будет false, так как ничего в хэш-таблицу под данным дочерним ключем записано не было
Изначально будет false ибо при чтении отсутствующего значения всегда возвращается 0 который в случае boolean интерпретируется как false.
30
проигрывая свою анимацию смерти (чтоб уместить всё в одну строку)
Здесь больше приоритет не в умещещении в одну строку, а в том, чтоб не тратить строки на создание переменной.
Ответы (8)
30
всегда есть bj_lastCreatedEffect, так что хз
Не всегда. В моей карте я удалил blizzard.j.
15
nazarpunk, ну ты суров конечно xD. Я весь кастомный код в своем моде пишу именно там) А в чем профит от его удаления то?
30
А в чем профит от его удаления то?
А толку от него?
15
nazarpunk, ну не все функции из него утечны, можно кое-какие использовать + переменные некоторые bj_ можно использовать, чтобы не создавать свои собственные. Ну и в моем случае, так как у меня мод, а не карта, я не могу его выкинуть для обеспечения совместимости с картами, которые сделаны и используют функции из него, для работоспособности редактора, ведь гуи многое из него тянет.
30
Meddin, но если у тебя мод, то можно же уточки пофиксить.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.