Добавлен , опубликован
Способ реализации:
Версия Warcraft:
Простое заклинание для новичков. Добавляет к стандартному Бурану дополнительный AOE урон раз в секунду. Код намеренно упрощён из-за обучающего характера материала. Более продвинутую версию можете посмотреть по следущей ссылке.

Видео

Код

//1. Обнуление глобалок не имеет смысла (только если вы уверены что она не будет больше вызыватся, тогда ради перфекционизма можно обнулить)
//2. Обнулять локальные переменные нужно обязательно, можно не обнулять integer, real, string (тут сам не знаю)
//3. Спелл сделан без кастомных функций, попытался более менее разъяснить о функциях для новичков, если где-то какая то ошибка, просьба отписать о ней)
//4. Итерация (повторение) - повторение таймера или цикла (тут таймер повторяется раз в 1 секунду, то есть раз в 1 секунду происходит итерация таймера)

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

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

	//Константы для удобства изменения параметров способности
    constant integer Blizzard_Id = 'A000' //Равкод способности
	constant real Blizzard_Range = 300 //Область воздействия способности
	constant real Blizzard_Damage = 30 //Урон способности
endglobals //Конец глобальных переменных

native UnitAlive takes unit id returns boolean //Объявляется функция с проверкой на то что юнит жив, если не объявить, то и юзать не получится

function Blizzard_Group takes nothing returns nothing //Функция группы (фильтрация и нанесение урона)
	set Target = GetEnumUnit() //В переменную Target записывается выбираемый из группы юнит
    
	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 ) //Наносит 300 урона отфильтрованным юнитам (врагам)
	endif //Конец условия
endfunction 

function Blizzard_Timer takes nothing returns nothing //Функция для таймера
    set Timer = GetExpiredTimer() //В Timer записывается истекающий таймер
    set TimerId = GetHandleId( Timer ) //в TimerId записывается хэндл-айди истекающего таймера
    set Caster = LoadUnitHandle( HT, TimerId, 'cstr' ) //В Caster выгружается значение из хэш-таблицы которые мы сохранили под родительским ключем Timer-а (TimerId) и под дочерним ключем 'cstr'
    
    //Ниже в двух функциях LoadReal мы таким же образом выгружаем наши значения из хэш-таблицы но уже под другими дочерними ключами
    call GroupEnumUnitsInRange( Group, LoadReal( HT, TimerId, 'cstX' ), LoadReal( HT, TimerId, 'cstY' ), Blizzard_Range, null ) //Выделяет юнитов в области Blizzard_Range (константа равная 300) и их добавление в группу
    call ForGroup( Group, function Blizzard_Group ) //Тут происходит вызов действия для группы
    call GroupClear( Group ) //Очистка группы от всех юнитов

    if GetUnitCurrentOrder( Caster ) != OrderId( "blizzard" ) then //Условие на то что наш герой перестал применять Буран
    //if GetUnitCurrentOrder( Caster ) != 0xd0079 then - этот вариант работает быстрее, так как использует id приказа без лишней возни со строками (без OrderId)
        call PauseTimer( Timer ) //Остановка таймера (таймер нужно остановить перед удалением так как иногда случается баг что итерация таймера происходит ещё раз)
        call DestroyTimer( Timer ) //Удаление таймера
        call FlushChildHashtable( HT, TimerId ) //Очистка хэш-таблицы по родительскому ключу (хэндл-айди Timer-а - TimerId)
    endif //Конец условия
endfunction //Конец функции

function Blizzard_Actions takes nothing returns nothing //Функция когда герой или юнит применяет способность
    if GetSpellAbilityId() == Blizzard_Id then //Условие если способность равна Blizzard_Id (константа равная равкоду Бурана 'A000')
        //CTRL + D в редакторе объектов что-бы узнать равкод чего либо
        set Timer = CreateTimer() //Создание таймера и его запись в переменную Timer
        set TimerId = GetHandleId( Timer ) //Получение хэндл-айди Timer-а и его запись в переменную TimerId

        //Сохранения в хэш-таблицу по родительскому ключу TimerId
        call SaveUnitHandle( HT, TimerId, 'cstr', GetTriggerUnit() ) //Сохраняется применяющий способность герой или юнит по дочернему ключу 'cstr'
        call SaveReal( HT, TimerId, 'cstX', GetSpellTargetX() ) //Сохраняется точка применения способности X по дочернему ключу 'cstX'
        call SaveReal( HT, TimerId, 'cstY', GetSpellTargetY() ) //Сохраняется точка применения способности Y по дочернему ключу 'cstY'
        call TimerStart( Timer, 1.0, true, function Blizzard_Timer ) //Запуск таймера Timer периодичностью в 1 секунду к которому привязана функция Blizzard_Timer (3 аргумент отвечает за периодичность)
	endif //Конец условия
endfunction //Конец функции

function InitTrig_Blizzard takes nothing returns nothing //Функция инициализации триггера (из функции main вызывается InitCustomTriggers() которая вызывает инициализацию всех триггеров на карте)
	local trigger t = CreateTrigger() //Создание триггера
	local integer i = 0 //Объявление целочисленной переменной

	loop //Начало цикла
		call TriggerRegisterPlayerUnitEvent( t, Player( i ), EVENT_PLAYER_UNIT_SPELL_EFFECT, null ) //Регистрирует событие применения способности для игрока i (изначально 0, 0 = 1 игрок, и т.д.)
		set i = i + 1 //Добавление 1 к i
		exitwhen i == bj_MAX_PLAYER_SLOTS //Условие выхода из цикла, тут вместо bj константы должно быть указано макс. кол-во игроков на карте 
    endloop //Конец цикла
    
	call TriggerAddAction( t, function Blizzard_Actions ) //В триггер добавляется функция с действиями при применения способности
	set t = null //Обнуляется локальная переменная
    
    call FogEnable( false ) //Это для видимости на всю карту (удалить)
    call FogMaskEnable( false ) //Это для видимости на всю карту (удалить)
endfunction //Конец функции

Требования

Скопировать код из шапки карты и триггера со спеллом к себе на карту, если такие глобальные переменные уже имеются, то копировать не надо. Поменять равкод способности в константах если он другой (скорее всего другой).
`
ОЖИДАНИЕ РЕКЛАМЫ...

Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
30
StringHash( "caster" ) за такое принято отрывать руки и засовывать туда, где не всходит солнце.
Ответы (10)
15
nazarpunk, хех, оно типо медленнее работать будет? Где-то слыхал что операции с string значениями тяжёлые, оно же вроде хэшируется (толи кэшируется, не помню уже) ещё? Тут в целом также можно было объявить StringHash в глобалки под понятные названия. Ну и вообще новичкам думаю со StringHash понятнее будет. Правда я не объяснил что оно делает...
15
nazarpunk, да и мне нравится когда в хэш сохраняется всё в кавычках (визуально для меня хорошо выглядит)
30
LastUchiha, и ты тем же временем боишься заюзать лишнюю integer. Ещё никто не восхищайлся твоей гейниальностью?
15
nazarpunk, да не боюсь я, мне просто не нравится когда что-то некрасиво в коде.
38
nazarpunk, дизлайк, вполне себе строчный ключ, а не твой говнокод из 4 букв, прибитый к языку
30
вполне себе строчный ключ
Напомни мне пожалуйста, когда в варкрафт завезли нормальную работу со строками? Ну а то что он каждый раз вычисляется тебя вообще не смущает?
9
nazarpunk, А где у нас разница будет ощутима?
На 10000 вызовов же, 13ms задержка
A
const integer CasterKEY = StringHash( "caster" )
и во все до 7ms уменьшает задержку против твоих рекомендованных 6ms
38
nazarpunk, да пусть вычисляется, это считанные такты. Константные строки лежат себе в памяти и никуда не дублируются
Учите херне какой-то, код ради кода
Где прорывные разработки, где демки, где стиль. Одни таймеры и инкомы
30
Ужас. Три вызова функции GetFilterUnit и почему UnitAlive не в самом начале?
function Blizzard_Filter takes nothing returns boolean //функция фильтра (фильтруются юниты, в группе юнитов остаются только враги)
    return IsUnitEnemy( GetFilterUnit(), GetOwningPlayer( uTemp ) ) and not IsUnitType( GetFilterUnit(), UNIT_TYPE_STRUCTURE ) and UnitAlive( GetFilterUnit() ) //сам фильтр       
endfunction 
Ответы (4)
15
nazarpunk, да с UnitAlive согласен, не в том порядке поставил. Три вызова функции - а спроси почему я не захотел сделать глобалку под FilterUnit, сам не знаю ответ на этот вопрос)
30
сам не знаю ответ на этот вопрос)
Я знаю, но за такое на сайте варны дают.
30
Рецензия на публикацию

Ужас

Если код подразумевается как обучение новичков, то автора необходимо расстрелять за вредительство. Вместо тысячи слов я его переписал по человечески.
native UnitAlive takes unit id returns boolean

globals
	hashtable HT = InitHashtable()
	group Group = CreateGroup()

	unit Caster
	real CasterX
	real CasterY

	unit Target

	timer Timer
	integer TimerId

	constant integer SpellBlizzardId = 'A000'
	constant real SpellBlizzardRange = 300
	constant real SpellBlizzardDamage = 300
endglobals

function SpellBlizzardEnum takes nothing returns nothing
	set Target = GetEnumUnit()
	if not UnitAlive(Target) then 
		return 
	endif
	if not IsUnitEnemy(Target, GetOwningPlayer(Caster)) then 
		return 
	endif
	if IsUnitType(Target, UNIT_TYPE_STRUCTURE) then
		return 
	endif

	call UnitDamageTarget(Caster, Target, SpellBlizzardDamage, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_UNKNOWN, WEAPON_TYPE_WHOKNOWS) 
endfunction 

function SpellBlizzardCallback takes nothing returns nothing
	set Timer = GetExpiredTimer()
	set TimerId = GetHandleId(Timer)
    
	set Caster = LoadUnitHandle(HT, TimerId, 'cstr')
	set CasterX = LoadReal(HT, TimerId, 'cstX')
	set CasterY = LoadReal(HT, TimerId, 'cstY')

	call GroupEnumUnitsInRange(Group, CasterX, CasterY, SpellBlizzardRange, null)
	call ForGroup(Group, function SpellBlizzardEnum) 
	call GroupClear(Group)
    
	if GetUnitCurrentOrder(Caster) != 0xd0079 then // blizzard
		call PauseTimer(Timer)
		call DestroyTimer(Timer)
		call FlushChildHashtable(HT, TimerId)
	endif 
endfunction 

function SpellBlizzardAction takes nothing returns nothing 
	if GetSpellAbilityId() != SpellBlizzardId then
		return
	endif
	set Caster = GetTriggerUnit()
	set CasterX = GetSpellTargetX()
	set CasterY = GetSpellTargetY()
	set Timer = CreateTimer() 
	set TimerId = GetHandleId(Timer)
    
	call SaveUnitHandle(HT, TimerId, 'cstr', Caster)
	call SaveReal(HT, TimerId, 'cstX', CasterX)
	call SaveReal(HT, TimerId, 'cstY', CasterY)
	call TimerStart(TimerId, 1.0, true, function SpellBlizzardCallback) 
endfunction

function InitTrig_Blizzard takes nothing returns nothing
	local trigger t = CreateTrigger()
	local integer i = -1

	loop
		set i = i + 1
		exitwhen i == bj_MAX_PLAYER_SLOTS
		call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null) //регистрирует событие применения способности для игрока i (изначально 0, 0 = 1 игрок, и так далее по порядку)
	endloop
    
	call TriggerAddAction(t, function SpellBlizzardAction) 
	set t = null
endfunction

P.S. Сюда бы по хорошему добавить уровней абилки и правильную работу с расстоянием до юнита, но в рамках пособия и так сойдёт.
Ответы (44)
15
nazarpunk, почему 3 условия стоят отдельно друг от друга ?
Загруженные файлы
30
LastUchiha, потому что так визуальней удобней смотреть, а не разгребать огромную моностроку. Ну и показать новичкам как можно использовать return в коллбэке.
15
nazarpunk, также, этот момент будет работать быстрее?
Загруженные файлы
30
LastUchiha, этот момент предотвращает создание Condition. Бояться лишней integer и плодить Condition пачками это верх гейниальности.
15
nazarpunk, понял, и почему бы не использовать фильтр как отдельную функцию которая будет проставлена в GroupEnum ? Лишняя функция чи шо ?
30
LastUchiha, а сам Filter ты не учитываешь? Или он не занимает память, которую ты так яросто пытаешься сэкономить?
15
nazarpunk, а это ну для чего ? Ну то есть почему exitwhen выше и i = -1? Лишний раз срабатывает i = i + 1!
Загруженные файлы
9
LastUchiha, Потому что так читать удобней, а не выискивать где конец цикла
15
IzobretatelBoom, ставить exitwhen в самом низу также норм идея. Читается - читается! Ищется - ищется! Не проводиться лишняя итерация цикла - не проводится!
30
Лишний раз срабатывает i = i + 1
Тоесть по твоему инкримент один раз на карту это страшно, а вычиление хэша строки на каждый чих это норм?
15
nazarpunk, ну вот StringHash() - я согласен теперь что не нужно его юзать.
30
я согласен теперь что не нужно его юзать.
А толку от твоего согласия если он до сих пор в исходнике? Можешь кстати тупо мой код взять и под себя переделать. Дарю.
15
nazarpunk, вот, сделал всё максимально близко к твоему способу за исключением нескольких моментов:
  1. exitwhen внизу
  2. в функции каста в проверку на способность сразу вставил код вместо return
  3. не записываю в функции каста координаты каста в переменные CastX, CastY, а сразу записываю в хэш-таблицу
  4. с выгрузкой координат каста также, не записываю в переменные, а сразу выгружаю в GroupEnum
  5. OrderId( "blizzard" ) оставил для ясности (если бы код выходил за рамки пособия - то пожалуйста)
  6. все 3 if-а собрал в один if, имхо это правильный вариант (моя задача показать новичкам как правильно сделать, а не как улучшить читаемость кода до небес), насчёт читаемости безспорно твой вариант лучше.
globals
    hashtable HT = InitHashtable()
    group Group = CreateGroup()

    unit Caster
    unit Target
    timer Timer
    integer TimerId
    
	constant integer Blizzard_Id = 'A000'
	constant real Blizzard_Range = 300
	constant real Blizzard_Damage = 300
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
    set Timer = GetExpiredTimer()
    set TimerId = GetHandleId( Timer )
    set Caster = LoadUnitHandle( HT, TimerId, 'cstr' )
    
    call GroupEnumUnitsInRange( Group, LoadReal( HT, TimerId, 'cstX' ), LoadReal( HT, TimerId, 'cstY' ), Blizzard_Range, null )
    call ForGroup( Group, function Blizzard_Group )
    call GroupClear( Group )

    if GetUnitCurrentOrder( Caster ) != OrderId( "blizzard" ) then
        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 TimerStart( Timer, 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
endfunction
30
LastUchiha,
  1. Вкусовщина
  2. Вкусовщина
  3. Вкусовщина
  4. Вкусовщина
  5. Здесь важно. Напиши ниже вариант с числом и закомментируй его.
  6. Вкусовщина. И в жассе мой способ не сильно повышает читаемость. Ибо строки плодит. Вот в ангеле и Зинке там была бы одна строка и Мега читаемость.
20
nazarpunk,
InitTrig_Blizzard
Плохая практика использовать комбинированый стиль именования.
Ну а в целом все классно, особенно нравится использования глобалок вместо локалок. Кто бы мне в начале пути такое показал...
Но для новичков нужно учитывать что в таком варианте можно словить коллизию с глобалками. Тут я посоветую грязножасс и использовать приватные глобалки. Но я понимаю что реализация на чистом жассе.
15
KaneThaumaturge, при правильном использовании ведь не словить (ну только если действий не особо много)?
20
LastUchiha, ну если юнит умирает от этого бурана и у него при смерти вызывается этот же буран который использует те же глобалки, то можно таймер и кастера потерять после строки нанесения урона. Я не говорю что стоит использовать локалки, просто это нужно учитывать.
20
LastUchiha, ну или например ты ту же глобалку с таймером используешь где-то ещё, и этот код может затриггерится при уроне или смерти. Так что важно разделять области использования при глобалках.
15
KaneThaumaturge, а, ну тут уже зависит от пользователя и того что он хочет реализовать.
30
Плохая практика использовать комбинированый стиль именования.
Здесь нет грязножасса. При наличии триггера Blizzard игра вызовет такую нативку.
20
LastUchiha, ну я это просто пишу для информации. Реализация хорошая.
15
KaneThaumaturge, я понял, у меня уже был случай перезаписи, но там в кастомной функции перезаписывало)
30
то можно таймер и кастера потерять после строки нанесения урона.
Посмотрел я после строки нанесения урона, как жаль, там столько использования этих переменных...
20
nazarpunk, не понял сарказм, а этот код шутка? Или это я что-то не понял?
call GroupClear(Group)
    
	if GetUnitCurrentOrder(Caster) != 0xd0079 then // blizzard
		call PauseTimer(Timer)
		call DestroyTimer(Timer)
		call FlushChildHashtable(HT, TimerId)
	endif 
30
ну или например ты ту же глобалку с таймером используешь где-то ещё, и этот код может затриггерится при уроне или смерти.
Может. Но всегда же можно вручную переменные заприватить. Благо уже даже плагины для этого есть.
30
не понял сарказм, а этот код шутка?
Это простая и эллегантная проверка на какст channel. Что не так то?
20
nazarpunk, не я про то, что в этом коде коллизия может быть с глобалками потому что он исполняется после нанесения урона
30
не я про то, что в этом коде коллизия может быть с глобалками потому что он исполняется после нанесения урона
Именно в этом не может. Потому что после урона нет работы с глобалками и тик таймера завершается. А следующий тик таймера перепишет глобалки заново.
Стопе, забыл. Чтоб точно не было коллизий нужно урон в самый низ отправить:
function SpellBlizzardCallback takes nothing returns nothing
	set Timer = GetExpiredTimer()
	set TimerId = GetHandleId(Timer)
    
	set Caster = LoadUnitHandle(HT, TimerId, 'cstr')

	if GetUnitCurrentOrder(Caster) != 0xd0079 then // blizzard
		call PauseTimer(Timer)
		call DestroyTimer(Timer)
		call FlushChildHashtable(HT, TimerId)
       return
	endif 

	set CasterX = LoadReal(HT, TimerId, 'cstX')
	set CasterY = LoadReal(HT, TimerId, 'cstY')

	call GroupEnumUnitsInRange(Group, CasterX, CasterY, SpellBlizzardRange, null)
	call ForGroup(Group, function SpellBlizzardEnum) 
	call GroupClear(Group)    
endfunction 
20
nazarpunk, именно в этом не может быть, да. Я же говорю, это так для информации. Если этот код модифицировать и использовать с ивентом урона, то может быть.
30
KaneThaumaturge, можно вообще решить этот вопрос эллегантно - создать DamageDeal систему с 0.1 таймером. Которая будет отложенно наносить урон.
Но здесь уже мы ступаем в область архитектуры.
20
nazarpunk, достаточно задержку в 0 на таймере, проверено. Я так и делаю. Только у меня модификаторы урона моментально срабатывают, а ивент на сам урон с задержкой.
20
nazarpunk, в целом лучше просто фор груп прям в конец переместить. Мб так лучше будет
30
в целом лучше просто фор груп прям в конец переместить. Мб так лучше будет
Ты забыл что ForGroup наносит поочерёдно урон? На втором юните всё сломается. А вот урон с задержкой решит проблему в корне. И можно будет перемещать как угодно. Тригер уже отработает и на переменные будет глубоко.
30
Только у меня модификаторы урона моментально срабатывают, а ивент на сам урон с задержкой.
Так это уже рюшечки. Главное базис сохранить - вызывать всё, что может стригерить событие с задержкой. Тогда можно во всю обмазаться универсальными глобалками и капитально сократить количество лишнего кода.
20
nazarpunk, а чё сломается, если ты форгруп запустишь и удалишь группу, то он все равно отработает
20
nazarpunk, да, я раньше брезговал задержку в 0 ставить. Но мне кажется это прям классная штука сейчас. Мб везде так сделаю.
20
KaneThaumaturge,
форгруп запустишь и удалишь группу, то он все равно отработает
Я вроде тестил такое, хотя уже не уверен.
20
nazarpunk, просто иногда тебе важно чтобы это исполнилось в одном "потоке"
30
просто иногда тебе важно чтобы это исполнилось в одном "потоке"
Просто иногда тебе лень спроэктировать систему, в которой это не важно. Ну или перейти на ас, где всё живет в своём инстансе и вообще до одного места на перезапись глобалок.
8
Вот тут Назарчик во всей красе смотрится, обзор на код весьма занимательный.
Ответы (2)
25
nazarpunk, своя реализация рошной заливки архимага что ли? Так можно так и написать
15
Вышла новая версия! Прокрутить к ресурсу
Переписал весь спелл (с помощью от nazarpunk)
Ответы (3)
30
LastUchiha, годнота. Лайк. Только формулировка сбивает с толку:
это результат конвертации строки "blizzard" в целочисленное значение
Это не конвертация, это настоящий ид приказа. Строки сбоку присраны для гуймуйщмков.
30
LastUchiha, у приказове есть целлочисленный id, который и использует игра. А есть представление в виде строки, которое используется в РО и гуймуй чтоб кожанным мешкам было удобней.
//if GetUnitCurrentOrder( Caster ) != 0xd0079 then - этот вариант работает быстрее, так как использует ид приказа без лишней возни со строками
30
Итерация (повторение) - повторение таймера или цикла (тут таймер повторяется раз в 1 секунду, то есть раз в 1 секунду происходит итерация таймера)
Этот коммент вообще не в том месте находится.

Когда копипастил забыл вкомментариях изменить ключи:
call SaveReal( HT, TimerId, 'cstX', GetSpellTargetX() ) //Сохраняется точка применения способности X по дочернему ключу 'cstr'
call SaveReal( HT, TimerId, 'cstY', GetSpellTargetY() ) //Сохраняется точка применения способности Y по дочернему ключу 'cstr'

group Group = CreateGroup() //Создаёт темповая группа
Это не темповая (слово "временна" видать запретили), а глобальная группа, которая используется для всех одноразовых переборов GroupEnum*.
Ответы (4)
30
group Group = CreateGroup() //Создаём группу, которую будем использовать для моментальной выборки юнитов
15
nazarpunk, так а про итерацию, вынести к комментам в начале триггера?
30
так а про итерацию, вынести к комментам в начале триггера?
Ну да. В блоке удаления это явно не к месту.
30
Как я понял, ты просто взял дефолт буран и добавил к нему урона. А будет версия с рандомными эффектами?
Ответы (45)
30
попробуй напади!
Они падают скучно, одной пачкой. Сделал бы чтоб они вразнобой летели, как например здесь.
15
nazarpunk, ну тут у тебя в спелле моментальный эффект удара, а у меня пока долетит... Мне теперь что-ли 2 таймера запускать (один для эффекта, а другой для всего остального) ?
30
ну дак как сделать это нормально ?
Запускаешь таймер с минимальным периодом, например 0.1 и спавнишь чё надо. Заодно приказ проверяешь. Гемороя не сильно больше чем сейчас, но правда требует аккуратности.
15
nazarpunk, пришлось включить мозги...
На то что не вынес LoadInteger значение в переменную не обращай внимания.
function Blizzard_Timer takes nothing returns nothing //Функция для таймера
    set Timer = GetExpiredTimer() //В Timer записывается истекающий таймер
    set TimerId = GetHandleId( Timer ) //в TimerId записывается хэндл-айди истекающего таймера
    set Caster = LoadUnitHandle( HT, TimerId, 'cstr' ) //В Caster выгружается значение из хэш-таблицы которые мы сохранили под родительским ключем Timer-а (TimerId) и под дочерним ключем 'cstr'
    set CastX = LoadReal( HT, TimerId, 'cstX' )
    set CastY = LoadReal( HT, TimerId, 'cstY' )

    call SaveInteger( HT, TimerId, 'time', LoadInteger( HT, TimerId, 'time' ) + 1 )

    if LoadInteger( HT, TimerId, 'time' ) >= 8 then //Условие: 8 = 0.8 = время полёта эффекта (задержка перед уроном вначале)
        call GroupEnumUnitsInRange( Group, CastX, CastY, Blizzard_Range, null ) //Выделяет юнитов в области Blizzard_Range (константа равная 300)
        call ForGroup( Group, function Blizzard_Group ) //Тут происходит вызов действия для группы
        call GroupClear( Group ) //Очистка группы от всех юнитов 
    endif //Конец условия

    if GetUnitCurrentOrder( Caster ) != OrderId( "blizzard" ) then //Условие на то что наш герой перестал применять Буран
    //if GetUnitCurrentOrder( Caster ) != 0xd0079 then - этот вариант работает быстрее, так как использует id приказа без лишней возни со строками (без OrderId)
        call PauseTimer( Timer ) //Остановка таймера (таймер нужно остановить перед удалением так как иногда случается баг что итерация таймера происходит ещё раз)
        call DestroyTimer( Timer ) //Удаление таймера
        call FlushChildHashtable( HT, TimerId ) //Очитска хэш-таблицы по родительскому ключу (хэндл-айди Timer-а - TimerId)
    else
        //Если выложу спелл - закоменчу.
        if LoadInteger( HT, TimerId, 'time' ) < 23 then
            if GetRandomInt( 0, 1 ) == 0 then
                call DestroyEffect( AddSpecialEffect( "Rain of Fire.mdx", GetRandomReal( CastX - Blizzard_Range / 2, CastX + Blizzard_Range / 2 ), GetRandomReal( CastY - Blizzard_Range / 2, CastY + Blizzard_Range / 2 ) ) )
            else
                call DestroyEffect( AddSpecialEffect( "Rain of Fire Fel.mdx", GetRandomReal( CastX - Blizzard_Range / 2, CastX + Blizzard_Range / 2 ), GetRandomReal( CastY - Blizzard_Range / 2, CastY + Blizzard_Range / 2 ) ) )  
            endif
        endif
    endif //Конец условия
endfunction //Конец функции

function Blizzard_Actions takes nothing returns nothing //Функция когда герой или юнит применяет способность
    if GetSpellAbilityId() == Blizzard_Id then //Условие если способность равна Blizzard_Id (константа равная равкоду Бурана 'A000')
        //CTRL + D в редакторе объектов что-бы узнать равкод чего либо
        set CastX = GetSpellTargetX()
        set CastY = GetSpellTargetY()
       
        set Timer = CreateTimer()
        set TimerId = GetHandleId( Timer )
        
        //Сохранения в хэш-таблицу по родительскому ключу TimerId
        call SaveUnitHandle( HT, TimerId, 'cstr', GetTriggerUnit() ) //Сохраняется применяющий способность герой или юнит по дочернему ключу 'cstr'
        call SaveReal( HT, TimerId, 'cstX', CastX ) //Сохраняется точка применения способности X по дочернему ключу 'cstX'
        call SaveReal( HT, TimerId, 'cstY', CastY ) //Сохраняется точка применения способности Y по дочернему ключу 'cstY'
        call SaveInteger( HT, TimerId, 'time', 0 )
        call TimerStart( Timer, 0.1, true, function Blizzard_Timer ) //Запуск таймера Timer периодичностью в 1 секунду к которому привязана функция Blizzard_Timer (3 аргумент отвечает за периодичность)
    endif //Конец условия
endfunction //Конец функции
Загруженные файлы
30
LastUchiha, не, ну красота же. Можно публиковать следующую версию. Ну и в этом и следующем ресурсе лучше видео вместо скриншота делать.
30
ну тут у тебя в спелле моментальный эффект удара
А маркировки точки удара тоже моментальная?
30
код норм ?
Вполне. Для новичка вообще идеальный. Отдельный лайк за то, что догадался считать тики таймера и работать с целыми числами.
call SaveInteger( HT, TimerId, 'time', LoadInteger( HT, TimerId, 'time' ) + 1 )
15
nazarpunk, это я уже давно умею, я сонный просто и не мог догадаться до столь простого решения
15
nazarpunk, это всё конечно хорошо, но вот это... Надо бы да фиксить!
Загруженные файлы
30
я сонный просто и не мог догадаться до столь простого решения
Я это решение очень редко вижу, вместо него лепят просто страшные городухи из миллиона таймеров, хотя можно было сделать намного проще. Новичкам очень полезно его показать.
30
это всё конечно хорошо, но вот это...
Что именно? Каст после отмены или спавн в одну точку?
15
LastUchiha, круто по таймеру на каждого даммика...
Загруженные файлы
15
nazarpunk, визуально я попал бураном по цели, а до стадии урона не дошло дело.
30
визуально я попал бураном по цели, а до стадии урона не дошло дело.
Ну так нужно дать таймеру ещё потикать. И положить значение в HT которое говорит, что каст уже отменён, но мы ждём урона.
30
ну вот я о том же щас думаю.
Ну или вообще заморочиться с тем, чтоб каждая плямба наносила урон. Там если немного вспомнить математику не сильно сложно доработать сделав из хт многомерный массив.
30
не дружу с математикой на самом деле.
Ну смотри, значения в виде равкодов 'cstr' и прочих очень большие. Значит у тебя свободно начало индексов. Тебе нужно хранить массив точек, куда ты направляешь плямбы. Вот например есть у тебя индексы:
[0,1,2,3,4,5,6,7,8,9]
Но у точки же две координаты - [x,y]
[[0,1],[2,3],[4,5],[6,7],[8,9]]
Ну и изи ищем индексы иксов:
x0 = 0*2 = 0
x1 = 1*2 = 2
x2 = 2*2 = 4
x3 = 3*2= 6
x4 = 4*2 = 8
Ну а игрик будет тупо идти следующим.
Ну а дальше всё вообще просто. В таблицу целых чисел, которая не пересекается с реальными, в ячейку номера тика ложишь индекс плямбы.
Например, на 24 тике должна бахнуть плямба номер 1. Вот и сохраянешь по ключу 24 значение 1. А потом просто проверяешь каждый тик, есть ли плямба.
15
Ну так нужно дать таймеру ещё потикать
А вот если дать ему ещё потикать, то полетит не 1 выстрел, а чуть больше половины от всего спелла.
15
nazarpunk, этот момент пока что отпустим так сказать.
Ну или вообще заморочиться с тем, чтоб каждая плямба наносила урон.
30
то полетит не 1 выстрел, а чуть больше половины от всего спелла.
Читай комментарий выше. Если сделать с плямбами, то у тебя будет индекс последней плямбы и флаг о том, что плямбы больше плодить не нужно. Когда ты ловишь отмену приказа, то просто ставишь флаг горшочек не вари и ждёшь когда текущий тик станет больше последнего.
30
этот момент пока что отпустим так сказать.
А зачем его опускать? Так же будет намного круче. Эволюция так сказать. В этом спеле ты дал новичкам базу, а в спеле с отдельными плямбами уже немного математики за пятый класс.
15
nazarpunk, понятное дело круче, щас попытаюсь вкурить это в себя...
30
знал бы ты что я год назад лепил...
Траст ми, на мой дерьмометр совы уже не помещаются.
30
nazarpunk, что-то понял но 80% не понял :D
Ну смотри, что нам нужно? Через 8 тиков нанести урон по точке. Для этого нужно сохранить точку на номер тика. В хэштаблице нам доступно миллион индексов, поэтому не паримся что индексы закончатся. Так как мы упаковываем двумерные массивы [x,y] в одномерный, то индексов у нас становится в два раза меньше, но полмиллиона нам точно хватит.
Назовём ключи [xk, yk], а номер тика i. Тогда номер ключа будет находиться таким образом:
xk = i * 2
yk = i * 2 + 1
Когда с упаковкой точек разобрались, разберёмся с тиками. Допустим мы хотим плодить плямбы со случайными промежутками. Единственное ограничение, мы не сможем сделать две плямбы в один тик. Разберём тики тупо по порядку:
1
2
3 - Рандом сработал, через 8 тиков на нужно нанести урон. Значит i = 8 + 3 = 11. Сохраняем на 11 тик координаты.
4
5 - Рандом сработал, через 8 тиков на нужно нанести урон. Значит i = 8 + 5 = 13. Сохраняем на 13 тик координаты.
6
7 - Кастер прервал заклинание, но i=13, а 7 < 13, пишем флаг, что плодить плямбы больше не нужно и продолжаем тикать
8
9
10
11 - В этой ячейке есть координаты, наносим урон
12
13 - В этой ячейке есть координаты, наносим урон, заодно прерываем заклинание ибо есть флаг и 13 >= 13
15
LastUchiha, что-бы этого добиться мне пришлось какую то ересь в коде написать...
xk = i * 2
yk = i * 2 + 1
Вот это не захотело работать!
Вместо этого не умножаю на два, а просто вместо единицы прибавляю 100 для уверенности.
Также если перестать кастовать буран после 16 тика, урона не будет. Тут я не могу догадаться как сделать.
15
	set CastX = GetRandomReal( CastX - Blizzard_Range, CastX + Blizzard_Range )
    set CastY = GetRandomReal( CastY - Blizzard_Range, CastY + Blizzard_Range )

    if Tick >= 8 then
        if Tick >= 16 then
            call SaveReal( HT, TimerId, Tick * 2, CastX )
            call SaveReal( HT, TimerId, Tick * 2 + 1, CastY )
        endif
        
        if Tick >= 16 and not Bool then 
            call GroupEnumUnitsInRange( Group, LoadReal( HT, TimerId, Tick * 2 ), LoadReal( HT, TimerId, Tick * 2 + 1 ), Blizzard_Range, null )
        elseif Tick < 16 then
            call GroupEnumUnitsInRange( Group, LoadReal( HT, TimerId, Tick * 2 ), LoadReal( HT, TimerId, Tick * 2 + 1 ), Blizzard_Range, null )
        endif
        
        call ForGroup( Group, function Blizzard_Group )
        call GroupClear( Group )
    else
        call SaveReal( HT, TimerId, Tick * 2 + 8, CastX )
        call SaveReal( HT, TimerId, Tick * 2 + 8 + 1, CastY )
    endif
30
Вместо этого не умножаю на два, а просто вместо единицы прибавляю 100 для уверенности.
Не нужно писать числа наугад. Перечитай коммент. Тут же всё просто:
set i = 14 // Номер тика, integer
set xk = i * 2 // Ключ x, integer
set yk = xk + 1 // Ключ y, integer
set x = GetReal(xk) // Значение x, real
set y = GetReal(yk) // Значение y, real
30
LastUchiha, вот, смотри, набросал на коленку:
function SpellBlizzardCallback1 takes nothing returns nothing
	local integer i // Номер тика
	local integer last // Номер последнего тика
	local integer xk // Ключ для x
	local integer yk // Ключ для y
	local real x // Координата x
	local real y // Координата y
	
	set Timer = GetExpiredTimer()
	set TimerId = GetHandleId(Timer)
    
	set Caster = LoadUnitHandle(HT, TimerId, 'cstr')
	set CasterX = LoadReal(HT, TimerId, 'cstX')
	set CasterY = LoadReal(HT, TimerId, 'cstY')

	set i = LoadInteger(HT, TimerId, 'tick') // Получаем номер тика
	call SaveInteger(HT, TimerId, 'tick', i + 1) // Увеличиваем тик на единицу

	set last = LoadInteger(HT, TimerId, 'last') // Получаем номер последнего тика

	// Проверяем флаг и рандомим плямбу
	if HaveSavedBoolean(HT, TimerId, 'ends') and GetRandomInt(1, 100) <= 30 then
		// Рандомим координаты плямбы.
		set x = CasterX + SpellBlizzardRange * GetRandomReal(-1, 1)
		set y = CasterY + SpellBlizzardRange * GetRandomReal(-1, 1)

		set last = last + 8 // Откладываем на восемь тиков
		call SaveInteger(HT, TimerId, 'last', last) // Сохраняем тик последнего урона

		// Расчитываем ключ для сохранения
		set xk = last * 2
		set yk = xk + 1

		// Сохраняем координаты
		call SaveReal(HT, TimerId, xk, x)
		call SaveReal(HT, TimerId, yk, y)

		// Спавним эффект
		// DestroyEffect ... лень писать
	endif

	// Проверяем плямбу на текущем тике
	set xk = i * 2
	set yk = xk + 1

	// Для проверки тупо проверяем, сохранено ли что либо на ячейке
	if HaveSavedReal(HT, TimerId, xk) then 
		set x = LoadReal(HT, TimerId, xk)
		set y = LoadReal(HT, TimerId, yk)

		// Наносим урон в координанах
	endif

	// Проверяем приказ и пишем флаг
	if GetUnitCurrentOrder(Caster) != 0xd0079 then // blizzard
		call SaveBoolean(HT, TimerId, 'ends', true)
	endif

	// Останавливаем каст если выполнены условия
	if HaveSavedBoolean(HT, TimerId, 'ends') and i >= last then 
		call PauseTimer(Timer)
		call DestroyTimer(Timer)
		call FlushChildHashtable(HT, TimerId)
	endif
endfunction 
30
странно оно всё конечно работает
Что именно то странного?
И я кстати протупил и за был not здесь:
if not HaveSavedBoolean(HT, TimerId, 'ends') and GetRandomInt(1, 100) <= 30 then
15
nazarpunk, это я уже понял, тут и не только это
set last = last + 8
А надо
set last = Tick + 8
15
Во что получилось!
function Blizzard_Timer takes nothing returns nothing
	local integer last
	local integer xk
	local integer yk
    
	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' )
	call SaveInteger( HT, TimerId, 'tick', Tick + 1 )
    set last = LoadInteger( HT, TimerId, 'last' )

	if not LoadBoolean( HT, TimerId, 'ends' ) then
		set CastX = CastX + Blizzard_Range * GetRandomReal( -1, 1 )
		set CastY = CastY + Blizzard_Range * GetRandomReal( -1, 1 )

		set last = Tick + 8
		call SaveInteger( HT, TimerId, 'last', last )

		set xk = last * 2
		set yk = xk + 1

		call SaveReal( HT, TimerId, xk, CastX )
		call SaveReal( HT, TimerId, yk, 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 xk = Tick * 2
	set yk = xk + 1

	if HaveSavedReal( HT, TimerId, xk ) then 
		set CastX = LoadReal( HT, TimerId, xk )
		set CastY = LoadReal( HT, TimerId, yk )
        
        call GroupEnumUnitsInRange( Group, CastX, CastY, Blizzard_Range, null )
        call ForGroup( Group, function Blizzard_Group )
        call GroupClear( Group )
	endif

	if GetUnitCurrentOrder( Caster ) != 0xd0079 then
		call SaveBoolean( HT, TimerId, 'ends', true )
	endif

	if LoadBoolean( HT, TimerId, 'ends' ) and Tick >= last then 
		call PauseTimer( Timer )
		call DestroyTimer( Timer )
		call FlushChildHashtable( HT, TimerId )
	endif
endfunction
15
ты зачем HaveSaved на Load поменял?
если бы не поменял оно бы и не заработало.
18
Лайк за любую движуху связанную с тригорноми скилами.
Ответы (7)
30
OVOgenez, ну я хз. Тригорные спелы конечно весело, но они сферические в вакууме. А хочется больше синергии и разных клёвых механик. А это в рамках одного спела не реализуешь,
18
nazarpunk, можно ж ещё спелпаки тогда делать, в виде героя для синергии.
за запятую в конце предложения тоже лайк
30
можно ж ещё спелпаки тогда делать, в виде героя для синергии.
Ну да, а к ним итемпаки, чтоб например создать огненный след по пути блинка.
18
nazarpunk, вообще я не думаю что тот кто шарит чё как делается будет себе в мапу импортировать чужие скилы, у каждого свои 100500 систем.
Те кому хочется разных клевых механик может с таких наработок взять разве что идею и мб глянуть как именно сделан спел, а дальше с 0 сделают под себя.
Короче не такая уж это и бессмысленная затея.
30
у каждого свои 100500 систем
Видел я эти системы. Лучше бы импортировали скилы.
18
nazarpunk, конечному игроку до фени чё там внутри. А вот автору удобнее работать со своим кодом. Всяко лучше чем напихать 99+ скилов рандомных людей и думать почему игра крашит. И скорее всего эти все скилы будут тянуть свои системы.
30
А вот автору удобнее работать со своим кодом.
Среднестатистическому автору удобей работать со своим кодом_Копировать_Копировать_Копировать_Копировать_Копировать_Копировать_Копировать_Копировать_Копировать_002
30
Кстати, держи, сгенерил тебе картинку для превью и основу для иконки.
Загруженные файлы
Ответы (3)
15
nazarpunk, это та что слева для превью на ресурсе получается?
30
это та что слева для превью на ресурсе получается?
Ну да, я ж специально с рамками без фона сделал, чтоб красиво смотрелась как у меня на боевом плоту.
Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.