Хеш-таблица — это структура данных, реализующая интерфейс
ассоциативного массива, а именно, она позволяет хранить пары (ключ, значение) и
выполнять три операции: операцию добавления новой пары, операцию поиска и
операцию удаления пары по ключу. Не будем вдаваться в подробности принципа её
работы, .
В статье мы рассмотрим самую распространённую область её применения в wc3:
прикрепление данных к объекту, на простейшем примере. Статья предполагает, что
читатель знаком с основами работы таймеров. Пример будет только на обычном Jass,
для совместимости (да и не все умеют работать c v/cJass).
Если вы знакомы с кэшем в wc3, то принцип работы с ним схож, с принципом
работы с хеш-таблицей. Только вместо строковых ключей, хеш-таблица использует
целочисленные значения (integer).
Допустим, мы хотим создать спелл, в
котором врагу на протяжении некоторого времени с малым периодом постоянно
наносится урон (для которого wait не подходит).
Мы создали триггер (в редакторе триггеров, для простоты объяснения) с
событием каста, дали ему условия и действия:
function Spell takes nothing returns nothing
local unit caster = GetSpellAbilityUnit()
local unit target = GetSpellTargetUnit()
endfunction
//Проверка спелла
function SpellCond takes nothing returns boolean
return GetSpellAbilityId()=='A000'
endfunction
//===========================================================================
function InitTrig_Spell takes nothing returns nothing
set gg_trg_Spell = CreateTrigger()
call TriggerRegisterPlayerUnitEvent(gg_trg_Spell,Player(0),EVENT_PLAYER_UNIT_SPELL_CAST,null)
call TriggerAddCondition(gg_trg_Spell,Condition(function SpellCond))
call TriggerAddAction(gg_trg_Spell,function Spell)
endfunction
Далее, нам нужен таймер, который будет периодически вызывать
функцию, внутри которой наносится урон:
function SpellDamage takes nothing returns nothing
call UnitDamageTarget(...)
endfunction
function Spell takes nothing returns nothing
local unit caster = GetSpellAbilityUnit()
local unit target = GetSpellTargetUnit()
local timer t = CreateTimer()
call TimerStart(t,0.04,true,function SpellDamage)
endfunction
Но как передать в функцию, кто кому должен наносить урон, и сколько раз?
Тут нам на помощь и приходит хеш-таблица. Перед работой нужно создать
и инициализировать глобальную хеш-таблицу, желательно при инициализации
карты.
» *1
Хеш-таблица - очень массивный объект и занимает много места в
памяти, поэтому рекомендуется создавать только одну на все
действия в карте. В противном случае, игра просто может слететь с фаталом или
зависнуть от переполнения.
Делать это нужно только один раз, например в этом
триггере:
function InitTrig_Spell takes nothing returns nothing
set gg_trg_Spell = CreateTrigger()
call TriggerRegisterPlayerUnitEvent(gg_trg_Spell,Player(0),EVENT_PLAYER_UNIT_SPELL_CAST,null)
call TriggerAddCondition(gg_trg_Spell,Condition(function SpellCond))
call TriggerAddAction(gg_trg_Spell,function Spell)
set udg_hash = InitHashtable()
endfunction
» *2
Если в карте имеется несколько спеллов, то рекомендуется
инициализировать хеш-таблицу в отдельном действии/триггере при
инициализации, чтобы избежать накладок во время редактирования или
переноса.
Работает хеш-таблица так: [ключ|значение]. Только как ключ мы
используем уникальный id объекта, а точнее - id нашего
таймера.
На него и будем сохранять нужные нам данные:
function Spell takes nothing returns nothing
local unit caster = GetSpellAbilityUnit()
local unit target = GetSpellTargetUnit()
local timer t = CreateTimer()
local integer h = GetHandleId(t)
call SaveUnitHandle(udg_hash,h,1,caster)
call SaveUnitHandle(udg_hash,h,2,target)
call SaveInteger(udg_hash,h,3,125)
call TimerStart(t,0.04,true,function SpellDamage)
set caster = null
set target = null
set t = null
endfunction
» *3
Также можно делать без создания переменной h:
call SaveUnitHandle(udg_hash,GetHandleId(t),1,caster)
Утечек это не вызовет, но немного снизит
производительность.
» *4
Аналогичными действиями сохраняются и другие объекты,
например группа:
call SaveGroupHandle(udg_hash,h,1,some_group)
Думаю, нет смысла перечислять все функции, так как на это существуют
function-листы.
» *5
Некоторые утверждают, что сохранение объектов под ключами 1,2,3...
неудобно и неуниверсально, и предлагают сохранять через уникальное значение
строки:
call SaveUnitHandle(udg_hash,h,StringHash("caster"),caster)
С ними можно согласиться, вам не придётся давать и запоминать цифры для
значений. Вы можете использовать такой способ для
удобства.
Готово, данные сохранены, теперь их можно будет достать в
функции нанесения урона, на которую запущен таймер.
Доставать данные мы будем
тоже по id таймера:
function SpellDamage takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer h = GetHandleId(t)
local unit caster = LoadUnitHandle(udg_hash,h,1)
local unit target = LoadUnitHandle(udg_hash,h,2)
local integer counter = LoadInteger(udg_hash,h,3)
if counter>0 then
call UnitDamageTarget(caster,target,1.0,true,true,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL,null)
call SaveInteger(udg_hash,h,3,counter-1)
else
call DestroyTimer(t)
call FlushChildHashtable(udg_hash,h)
endif
set caster = null
set target = null
set t = null
endfunction
» *6
Очень часто встречается неправильная конструкция очистки, приводящая
к утечкам:
call DestroyTimer(t)
call FlushChildHashtable(udg_hash,GetHandleId(t))
В данном случае, очистка произведена не будет, так как таймер
уничтожается раньше получения его id, поэтому в функцию очистки будет подано
неправильное значение (0).
Если вы используете конструкцию без переменной с
id, то делать нужно так:
call FlushChildHashtable(udg_hash,GetHandleId(t))
call DestroyTimer(t)
То есть сначала очищать, а потом удалять таймер.
В случае с
переменной, порядок этих действий значения не
имеет.
Спелл готов, данные записываются, достаются и удаляются из
хеш-таблицы.
Вот что у нас получилось в итоге:
function SpellDamage takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer h = GetHandleId(t)
local unit caster = LoadUnitHandle(udg_hash,h,1)
local unit target = LoadUnitHandle(udg_hash,h,2)
local integer counter = LoadInteger(udg_hash,h,3)
if counter>0 then
call UnitDamageTarget(caster,target,1.0,true,true,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL,null)
call SaveInteger(udg_hash,h,3,counter-1)
else
call DestroyTimer(t)
call FlushChildHashtable(udg_hash,h)
endif
set caster = null
set target = null
set t = null
endfunction
function Spell takes nothing returns nothing
local unit caster = GetSpellAbilityUnit()
local unit target = GetSpellTargetUnit()
local timer t = CreateTimer()
local integer h = GetHandleId(t)
call SaveUnitHandle(udg_hash,h,1,caster)
call SaveUnitHandle(udg_hash,h,2,target)
call SaveInteger(udg_hash,h,3,125)
call TimerStart(t,0.04,true,function SpellDamage)
set caster = null
set target = null
set t = null
endfunction
function SpellCond takes nothing returns boolean
return GetSpellAbilityId()=='A000'
endfunction
//===========================================================================
function InitTrig_Spell takes nothing returns nothing
set gg_trg_Spell = CreateTrigger()
call TriggerRegisterPlayerUnitEvent(gg_trg_Spell,Player(0),EVENT_PLAYER_UNIT_SPELL_CAST,null)
call TriggerAddCondition(gg_trg_Spell,Condition(function SpellCond))
call TriggerAddAction(gg_trg_Spell,function Spell)
set udg_hash = InitHashtable()
endfunction