WarCraft 3: [lua] Кастуем все заклинания используя один триггер

» Раздел: Триггеры и объекты

Идея

Основная идея заключается в том, что lua позволяет хранить функции в таблицах и при срабатывании триггера нам всего лишь нужно вызвать определённую функцию. Основная проблема в том, как это красиво сделать.

Шаблон

Воспользовавшись тем, что редактор собирает код в том порядке, в котором он там размещён, создадим папку для способностей:
В блоке Ability определим глобальную таблицу, для хранения всех способностей:
ABILITY = {}
В самом простом случае, хранить способности можно таким образом:
ABILITY[FourCC('A000')] = function()
	print('spell')
end
Но как мы знаем у способности есть пять событий, вызываемых в таком порядке:
  • EVENT_PLAYER_UNIT_SPELL_CHANNEL
  • EVENT_PLAYER_UNIT_SPELL_CAST
  • EVENT_PLAYER_UNIT_SPELL_EFFECT
  • EVENT_PLAYER_UNIT_SPELL_ENDCAST
  • EVENT_PLAYER_UNIT_SPELL_FINISH
Поэтому немного изменим шаблон:
ABILITY[FourCC('A000')] = {
	CHANNEL = function()
		print('EVENT_PLAYER_UNIT_SPELL_CHANNEL')
	end,
	CAST    = function()
		print('EVENT_PLAYER_UNIT_SPELL_CAST')
	end,
	EFFECT  = function()
		print('EVENT_PLAYER_UNIT_SPELL_EFFECT')
	end,
	ENDCAST = function()
		print('EVENT_PLAYER_UNIT_SPELL_ENDCAST')
	end,
	FINISH  = function()
		print('EVENT_PLAYER_UNIT_SPELL_FINISH')
	end
}

Триггер

Осталось только сделать триггер, который будет работать с этим шаблоном. Для этого вернёмся в блок Ability, откроем do ... end и начнём.
Для начала сохраним id всех событий, чтоб каждый раз не вызывать функцию:
local EventChannelId = GetHandleId(EVENT_PLAYER_UNIT_SPELL_CHANNEL)
local EventCastId    = GetHandleId(EVENT_PLAYER_UNIT_SPELL_CAST)
local EventEffectId  = GetHandleId(EVENT_PLAYER_UNIT_SPELL_EFFECT)
local EventEndCastId = GetHandleId(EVENT_PLAYER_UNIT_SPELL_ENDCAST)
local EventFinishId  = GetHandleId(EVENT_PLAYER_UNIT_SPELL_FINISH)
Далее создадим триггер и добавим ему все события.
local AbilityTrigger = CreateTrigger()
for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
	TriggerRegisterPlayerUnitEvent(AbilityTrigger, Player(i), EVENT_PLAYER_UNIT_SPELL_CHANNEL)
	TriggerRegisterPlayerUnitEvent(AbilityTrigger, Player(i), EVENT_PLAYER_UNIT_SPELL_CAST)
	TriggerRegisterPlayerUnitEvent(AbilityTrigger, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT)
	TriggerRegisterPlayerUnitEvent(AbilityTrigger, Player(i), EVENT_PLAYER_UNIT_SPELL_ENDCAST)
	TriggerRegisterPlayerUnitEvent(AbilityTrigger, Player(i), EVENT_PLAYER_UNIT_SPELL_FINISH)
end
Если вам не нужны все события, то ненужную строчку можете просто удалить.
Так как доступ к локальным переменным быстрее чем к глобальным, то включим дух оптимизаторства на спичках и определим переменную поближе к месту использования:
local ABILITYS = ABILITY ---@type table
И в завершении добавим действия триггеру:
TriggerAddAction(AbilityTrigger, function()
	local eventId = GetHandleId(GetTriggerEventId())
	local ability = ABILITYS[GetSpellAbilityId()]
	
	if ability ~= nil then
		if eventId == EventChannelId and ability.CHANNEL ~= nil then ability.CHANNEL()
		elseif eventId == EventCastId and ability.CAST ~= nil then ability.CAST()
		elseif eventId == EventEffectId and ability.EFFECT ~= nil then ability.EFFECT()
		elseif eventId == EventEndCastId and ability.ENDCAST ~= nil then ability.ENDCAST()
		elseif eventId == EventFinishId and ability.FINISH ~= nil then ability.FINISH()
		end
	end
end)
Как видите, код довольно таки прост, что зная шаблон комментировать его не вижу смысла
» Весь код
ABILITY = {}
do
	local EventChannelId = GetHandleId(EVENT_PLAYER_UNIT_SPELL_CHANNEL)
	local EventCastId    = GetHandleId(EVENT_PLAYER_UNIT_SPELL_CAST)
	local EventEffectId  = GetHandleId(EVENT_PLAYER_UNIT_SPELL_EFFECT)
	local EventEndCastId = GetHandleId(EVENT_PLAYER_UNIT_SPELL_ENDCAST)
	local EventFinishId  = GetHandleId(EVENT_PLAYER_UNIT_SPELL_FINISH)
	
	local AbilityTrigger = CreateTrigger()
	for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
		TriggerRegisterPlayerUnitEvent(AbilityTrigger, Player(i), EVENT_PLAYER_UNIT_SPELL_CHANNEL)
		TriggerRegisterPlayerUnitEvent(AbilityTrigger, Player(i), EVENT_PLAYER_UNIT_SPELL_CAST)
		TriggerRegisterPlayerUnitEvent(AbilityTrigger, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT)
		TriggerRegisterPlayerUnitEvent(AbilityTrigger, Player(i), EVENT_PLAYER_UNIT_SPELL_ENDCAST)
		TriggerRegisterPlayerUnitEvent(AbilityTrigger, Player(i), EVENT_PLAYER_UNIT_SPELL_FINISH)
	end
	
	local ABILITYS = ABILITY ---@type table
	TriggerAddAction(AbilityTrigger, function()
		local eventId = GetHandleId(GetTriggerEventId())
		local ability = ABILITYS[GetSpellAbilityId()]
		
		if ability ~= nil then
			if eventId == EventChannelId and ability.CHANNEL ~= nil then ability.CHANNEL()
			elseif eventId == EventCastId and ability.CAST ~= nil then ability.CAST()
			elseif eventId == EventEffectId and ability.EFFECT ~= nil then ability.EFFECT()
			elseif eventId == EventEndCastId and ability.ENDCAST ~= nil then ability.ENDCAST()
			elseif eventId == EventFinishId and ability.FINISH ~= nil then ability.FINISH()
			end
		end
	end)
end

Добавление способностей

Основное правило добавления способностей - соблюдать шаблон и размещать их ниже блока Ability. Для примера создадим тестовую способность Ability_test:
ABILITY[FourCC('A000')] = {
	CHANNEL = function()
		print('EVENT_PLAYER_UNIT_SPELL_CHANNEL')
	end,
	CAST    = function()
		print('EVENT_PLAYER_UNIT_SPELL_CAST')
	end,
	EFFECT  = function()
		print('EVENT_PLAYER_UNIT_SPELL_EFFECT')
	end,
	ENDCAST = function()
		print('EVENT_PLAYER_UNIT_SPELL_ENDCAST')
	end,
	FINISH  = function()
		print('EVENT_PLAYER_UNIT_SPELL_FINISH')
	end
}
Если принцип работы понятен, то добавим ещё одну способность, более приближённую к настоящим реалиям:
do
	local DELAY             = 1
	local DAMAGE            = 100
	local EFFECT_CAST       = 'Abilities/Spells/Human/MassTeleport/MassTeleportCaster.mdl'
	local EFFECT_LIGHTNING  = 'Effect/Spell/Lightning.mdx'
	local GROUP             = CreateGroup()
	
	ABILITY[FourCC('ALig')] = {
		EFFECT = function()
			local caster  = GetTriggerUnit()
			local ability = GetSpellAbility()
			local level   = GetUnitAbilityLevel(caster, GetSpellAbilityId())
			local x, y    = GetSpellTargetX(), GetSpellTargetY()
			local radius  = BlzGetAbilityRealLevelField(ability, ABILITY_RLF_AREA_OF_EFFECT, level - 1)
			
			DestroyEffect(AddSpecialEffect(EFFECT_CAST, x, y))
			
			TimerStart(CreateTimer(), DELAY, false, function()
				DestroyEffect(AddSpecialEffect(EFFECT_LIGHTNING, x, y))
				GroupEnumUnitsInRange(GROUP, x, y, radius + 128)
				
				for index = BlzGroupGetSize(GROUP) - 1, 0, -1 do
					local target = BlzGroupUnitAt(GROUP, index)
					if UnitAlive(target) and IsUnitInRangeXY(target, x, y, radius) and not IsUnitType(target, UNIT_TYPE_FLYING) then
						UnitDamageTarget(caster, target, DAMAGE * level, false, true, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
					end
				end
				GroupClear(GROUP)
				
				DestroyTimer(GetExpiredTimer())
			end)
		end
	}
end

Заключение

Как видите, в оптимизации нет ничего сложного, но если всётаки у вас остались вопросы или появились предложения, то можее смело оставлять их в комментариях.

Просмотров: 329

» Лучшие комментарии


Morningstar #1 - 4 недели назад 0
А почему к АоЕ от способности добавляем 128?
Немного не понял этот момент.
За статью спасибо.
PT153 #2 - 4 недели назад (отредактировано ) 4
Morningstar, по-хорошему, нужно прибавлять максимальный Collision Size (ставится в игровых константах) и выбирать тех юнитов, что от точки каста находятся на расстоянии меньше и равным radius + unit's collision size. Последнее можно проверить функцией IsUnitInRangeXY().
Daro #3 - 3 недели назад 0
А можно отдельно файл импорта Lightning.mdx , для тех кто на 1.26
Steal nerves #4 - 3 недели назад (отредактировано ) 0
хм.. думал там обычная молния
NazarPunk #5 - 3 недели назад 2
А можно отдельно файл импорта Lightning.mdx
Держите, у автора там ещё много хороших эффектов.
Steal nerves #6 - 3 недели назад (отредактировано ) 1
Daro, на хайве заметил много молнии-эффектов в разделе SFX => effect