Добавлен , опубликован
Раздел:
Триггеры и объекты
Так как игра не позволяет нам применять произвольную способность от произвольного юнита, была придумана хитрая система: сделать скрытого юнита и кастовать от его лица. Поэтому мы сразу его и создадим, для чего скопируем "(nsno) Полярная сова" с равкодом "dumy" и изменим следующие поля:
ucbs (castbsw) Графика - Анимация: обратный ход броска0
udtm (death)Графика - Время смерти (сек.)0.10
ushu (unitShadow)Графика - Отображение тени (боевая единица)Нет
umdl (file)Графика - Файл модели.mdl
ucol (collision)Пути - Физический размер0.00
uine (inEditor)Редактор - Размещаемо в РедактореНет
uabi (abilList)Способности - Возможные способности(Aloc) Москиты
umpr (regenMana)Характеристики - Восстановление маны1000.00
umpi (mana0)Характеристики - Изначальное количество маны1000
umpm (manaN)Характеристики - Максимум маны1000
upoi (points)Характеристики - Опыт, начисляемый за уничтожение0
usid (sight)Характеристики - Радиус обзора (днём)0
usin (nsight)Характеристики - Радиус обзора (ночью)0

Применение

Для начала создадим тестовую способность с минимальным количеством кода и проверим её работоспособность.
local SpellGroup   = CreateGroup()
local SpellTrigger = CreateTrigger()
local SpellId      = FourCC('spel')
for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
	TriggerRegisterPlayerUnitEvent(SpellTrigger, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT)
end
TriggerAddAction(SpellTrigger, function()
	if GetSpellAbilityId() ~= SpellId then return end
	local caster      = GetTriggerUnit()
	local casterOwner = GetOwningPlayer(caster)
	local x, y        = GetSpellTargetX(), GetSpellTargetY()
	
	local radius = BlzGetAbilityRealLevelField(GetSpellAbility(), ABILITY_RLF_AREA_OF_EFFECT, 0)
	GroupEnumUnitsInRange(SpellGroup, x, y, radius)
	for index = BlzGroupGetSize(SpellGroup) - 1, 0, -1 do
		local target = BlzGroupUnitAt(SpellGroup, index)
		if UnitAlive(target) and IsUnitEnemy(target, casterOwner) then
			KillUnit(target) -- впоследствии мы заменим эту функцию на даммикаст
		end
	end
	GroupClear(SpellGroup)
end)
Теперь можно кастовать самую популярную способность - оглушение. Скопируем "(ACcb) Ледяная стрела" с равкодом "stun" и изменим следующие поля:
amat (Missileart) Графика - Анимация дистанционной атакиудалить всё
Htb1 (DataA1) ABILITY_RLF_DAMAGE_HTB1Данные - Урон0.00
amcs (Cost1) ABILITY_ILF_MANA_COSTХарактеристики - Затрачиваемая мана0
aсdn (Cool1) ABILITY_RLF_COOLDOWNХарактеристики - Перезарядка0.00
atar (targs) Характеристики - Разрешённые целиснять все галки
Чтобы каждый раз не создавать даммиков, мы поступим хитро - создадим одного даммика за "Нейтрально-пассивного" и будем двигать его к цели. Таки способом мы можем кастовать в цикле и не испытывать проблем с видимостью. "Нейтрально-пассивный" видит всю карту.
local dummy  = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), FourCC('dumy'), 0, 0, 0)
local stunId = FourCC('stun')
UnitAddAbility(dummy, stunId)
local stunAbility = BlzGetUnitAbility(dummy, stunId)

---@param target unit
---@param durationNormal real
---@param durationHero real
function DummyCastStun(target, durationNormal, durationHero)
	SetUnitX(dummy, GetUnitX(target))
	SetUnitY(dummy, GetUnitY(target))
	BlzSetAbilityRealLevelField(stunAbility, ABILITY_RLF_DURATION_NORMAL, 0, durationNormal)
	BlzSetAbilityRealLevelField(stunAbility, ABILITY_RLF_DURATION_HERO, 0, durationHero)
	IssueTargetOrderById(dummy, 852095, target) -- thunderbolt
end
Таким способом используя всего одного даммика можно накладывать баффы, которые не наносят урон. С уроном всё немного сложнее ибо ели юнит умрёт, то убийцей игра посчитает "Нейтрально-пассивного" и не даст награду за убийство в купе с опытом.
Если всёже вам понадобится урон, то рассмотрим следующий пример. Скопируем "(Aenr) Гнев деревьев (враг 1)" с равкодом "root" и настроим следующие поля:
amcs (Cost1) ABILITY_ILF_MANA_COSTХарактеристики - Затрачиваемая мана0
aсdn (Cool1) ABILITY_RLF_COOLDOWNХарактеристики - Перезарядка0.00
atar (targs) Характеристики - Разрешённые целиснять все галки
Пот тому же принципу, что и в прошлый раз, создадим даммика каждому игроку и выдадим им способность:
local dummys      = {}
local rootAbility = {}
local rootId      = FourCC('root')
for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
	dummys[i] = CreateUnit(Player(i), FourCC('dumy'), 0, 0, 0)
	UnitAddAbility(dummys[i], rootId)
	rootAbility[i] = BlzGetUnitAbility(dummys[i], rootId)
end

---@param player player
---@param target unit
---@param durationNormal real
---@param durationHero real
function DummyCastRoot(player, target, durationNormal, durationHero, damage)
	local id      = GetPlayerId(player)
	local dummy   = dummys[id]
	local ability = rootAbility[id]
	
	SetUnitX(dummy, GetUnitX(target))
	SetUnitY(dummy, GetUnitY(target))
	BlzSetAbilityRealLevelField(ability, ABILITY_RLF_DURATION_NORMAL, 0, durationNormal)
	BlzSetAbilityRealLevelField(ability, ABILITY_RLF_DURATION_HERO, 0, durationHero)
	BlzSetAbilityRealLevelField(ability, ABILITY_RLF_DAMAGE_PER_SECOND_EER1, 0, damage)
	UnitShareVision(target, player, true)
	IssueTargetOrderById(dummy, 852171, target) -- entanglingroots
	UnitShareVision(target, player, false)
end
Обратите внимание на UnitShareVision, всё дело в том, что юниты не могут кастовать в цель, которую не видят. Поэтому и приходится проделать трюк с предоставлением видимости. Так что будте осторожны, если в другом заклинании вы открываете видимость над юнитом, то UnitShareVision(target, player, false) заберёт её обратно.
Осталось разобрать те случаи, когда невозможно использовать одного юнита для всех кастов. Например заклинания требующие поддержки (channel) или с задержкой перед кастом. Тогда нам придётся уничтожать даммиков после использования. К счастью это несложно:
local dummy       = CreateUnit(casterOwner, FourCC('dumy'), x, y, 0) -- создаём дамми
UnitAddAbility(dummy, FourCC('ACfs')) -- даём юниту способность "Огненный столб"
IssuePointOrderById(dummy, 852488, x, y) -- отдаём приказ flamestrike
UnitApplyTimedLife(dummy, FourCC('BTLF'), 10) -- устанавливаем юниту время жизни

Заключение

Система дамми-каста довольно проста, но если всёже у вас останутся вопросы, то можете смело задавать их в комментариях. Также можете скачать карту и поэксперементировать.
Код
do
	local InitGlobalsOrigin = InitGlobals
	function InitGlobals()
		InitGlobalsOrigin()
		
		do
			local dummy  = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), FourCC('dumy'), 0, 0, 0)
			local stunId = FourCC('stun')
			UnitAddAbility(dummy, stunId)
			local stunAbility = BlzGetUnitAbility(dummy, stunId)
			
			---@param target unit
			---@param durationNormal real
			---@param durationHero real
			function DummyCastStun(target, durationNormal, durationHero)
				SetUnitX(dummy, GetUnitX(target))
				SetUnitY(dummy, GetUnitY(target))
				BlzSetAbilityRealLevelField(stunAbility, ABILITY_RLF_DURATION_NORMAL, 0, durationNormal)
				BlzSetAbilityRealLevelField(stunAbility, ABILITY_RLF_DURATION_HERO, 0, durationHero)
				IssueTargetOrderById(dummy, 852095, target) -- thunderbolt
			end
		end
		
		do
			local dummys      = {}
			local rootAbility = {}
			local rootId      = FourCC('root')
			for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
				dummys[i] = CreateUnit(Player(i), FourCC('dumy'), 0, 0, 0)
				UnitAddAbility(dummys[i], rootId)
				rootAbility[i] = BlzGetUnitAbility(dummys[i], rootId)
			end
			
			---@param player player
			---@param target unit
			---@param durationNormal real
			---@param durationHero real
			function DummyCastRoot(player, target, durationNormal, durationHero, damage)
				local id      = GetPlayerId(player)
				local dummy   = dummys[id]
				local ability = rootAbility[id]
				
				SetUnitX(dummy, GetUnitX(target))
				SetUnitY(dummy, GetUnitY(target))
				BlzSetAbilityRealLevelField(ability, ABILITY_RLF_DURATION_NORMAL, 0, durationNormal)
				BlzSetAbilityRealLevelField(ability, ABILITY_RLF_DURATION_HERO, 0, durationHero)
				BlzSetAbilityRealLevelField(ability, ABILITY_RLF_DAMAGE_PER_SECOND_EER1, 0, damage)
				UnitShareVision(target, player, true)
				IssueTargetOrderById(dummy, 852171, target) -- entanglingroots
				UnitShareVision(target, player, false)
			end
		end
		
		-- Spell
		local SpellGroup   = CreateGroup()
		local SpellTrigger = CreateTrigger()
		local SpellId      = FourCC('spel')
		for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
			TriggerRegisterPlayerUnitEvent(SpellTrigger, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT)
		end
		TriggerAddAction(SpellTrigger, function()
			if GetSpellAbilityId() ~= SpellId then return end
			local caster      = GetTriggerUnit()
			local casterOwner = GetOwningPlayer(caster)
			local x, y        = GetSpellTargetX(), GetSpellTargetY()
			
			local dummy       = CreateUnit(casterOwner, FourCC('dumy'), x, y, 0) -- создаём дамми
			UnitAddAbility(dummy, FourCC('ACfs')) -- даём юниту способность "Огненный столб"
			IssuePointOrderById(dummy, 852488, x, y) -- отдаём приказ flamestrike
			UnitApplyTimedLife(dummy, FourCC('BTLF'), 10) -- устанавливаем юниту время жизни
			
			local radius = BlzGetAbilityRealLevelField(GetSpellAbility(), ABILITY_RLF_AREA_OF_EFFECT, 0)
			GroupEnumUnitsInRange(SpellGroup, x, y, radius)
			for index = BlzGroupGetSize(SpellGroup) - 1, 0, -1 do
				local target = BlzGroupUnitAt(SpellGroup, index)
				if UnitAlive(target) and IsUnitEnemy(target, casterOwner) then
					DummyCastStun(target, 5, 3)
					DummyCastRoot(casterOwner, target, 5, 3, 10)
				end
			end
			GroupClear(SpellGroup)
		end)
	
	end
end
`
ОЖИДАНИЕ РЕКЛАМЫ...

Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
2
28
5 лет назад
2
Ману у дамми можно занулить, а требования способности менять в коде. Опыт не нужно трогать, юнит же неуязвим.
Вместо .mdl можно поставить просто _.
Укажи поля CastPoint, Fly Height, Movement Type, Collision Size. Они тоже важны.
7
32
5 лет назад
7
Отличная статья, всё кратко и по полочкам, а пехотинец с последней гифки побежал изучать Луа
0
29
5 лет назад
0
Ману у дамми можно занулить, а требования способности менять в коде.
Если уже можно настроить в редакторе, почему это не сделать.
Опыт не нужно трогать, юнит же неуязвим.
Это же очки статистики в послеигровом меню, зачем лишний раз их накапливать.
Вместо .mdl можно поставить просто _.
Сколько не видел даммиков, везде .mdl, решил не ломать традицию
Укажи поля CastPoint, Fly Height, Movement Type, Collision Size. Они тоже важны.
Они у исходного юнита уже правильно выставлены.
2
28
5 лет назад
2
Это же очки статистики в послеигровом меню, зачем лишний раз их накапливать.
Это же опыт за убийство, не? А тут даммик, которого нельзя убить.
Если уже можно настроить в редакторе, почему это не сделать.
Просто всё равно у дамми спелов манакост и кд на 0.
0
29
5 лет назад
0
Это же опыт за убийство, не?
Нет, это значение, которое можно получить через GetUnitPointValue() и нужное для послеигровой статистики.
Просто всё равно у дамми спелов манакост и кд на 0.
Я даже в статье кастовал "Огненный столб" не сбивая ему манакост.
0
28
5 лет назад
0
GetUnitPointValue()
Это же вообще другое поле в РО. Или это опять кривой перевод?
0
29
5 лет назад
Отредактирован nazarpunk
0
Это же вообще другое поле в РО. Или это опять кривой перевод?
Опыт из юнитов считается из игровых констант. Здесь есть формулы.
Да и название в РО намекает
Я это значение использую для задания дополнительных типов юнитов.
Загруженные файлы
0
28
5 лет назад
Отредактирован PT153
0
Так-то Point Value - это point value, ничего общего с опытом оно не имеет. В ТД туда пишут начальную стоимость башни. Например, в той же BlizzardTD.

Здесь есть формулы.
Там это значение никак не фигурирует, потому что с опытом никак не связано.

Также есть такой сурс.
Has no significance in the game other than to be used as part of a trigger through the Integer functions
А самый надёжный сурс говорит вот что.
Point Value - This is the number of points awarded to the player who kills this unit.
Что как бы совсем не связано с опытом. А так как даммика никто не убивает, то и смысла менять данное поле нет. Хотя в этом нет ничего страшного.

Короче, перевод крайне кривой, мда.
Характеристики - Опыт, начисляемый за уничтожение
vs.
Point Value - This is the number of points awarded to the player who kills this unit.
Загруженные файлы
Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.