, Гильдия «Черамор»

WarCraft 3: [lua] Дамми каст

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

Так как игра не позволяет нам применять произвольную способность от произвольного юнита, была придумана хитрая система: сделать скрытого юнита и кастовать от его лица. Поэтому мы сразу его и создадим, для чего скопируем "(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

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

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


PT153 #1 - 4 месяца назад 0
Ману у дамми можно занулить, а требования способности менять в коде. Опыт не нужно трогать, юнит же неуязвим.
Вместо .mdl можно поставить просто _.
Укажи поля CastPoint, Fly Height, Movement Type, Collision Size. Они тоже важны.
Bergi_Bear #2 - 4 месяца назад 6
Отличная статья, всё кратко и по полочкам, а пехотинец с последней гифки побежал изучать Луа
NazarPunk #3 - 4 месяца назад 0
Ману у дамми можно занулить, а требования способности менять в коде.
Если уже можно настроить в редакторе, почему это не сделать.
Опыт не нужно трогать, юнит же неуязвим.
Это же очки статистики в послеигровом меню, зачем лишний раз их накапливать.
Вместо .mdl можно поставить просто _.
Сколько не видел даммиков, везде .mdl, решил не ломать традицию
Укажи поля CastPoint, Fly Height, Movement Type, Collision Size. Они тоже важны.
Они у исходного юнита уже правильно выставлены.
PT153 #4 - 4 месяца назад 0
Это же очки статистики в послеигровом меню, зачем лишний раз их накапливать.
Это же опыт за убийство, не? А тут даммик, которого нельзя убить.
Если уже можно настроить в редакторе, почему это не сделать.
Просто всё равно у дамми спелов манакост и кд на 0.
NazarPunk #5 - 4 месяца назад 0
Это же опыт за убийство, не?
Нет, это значение, которое можно получить через GetUnitPointValue() и нужное для послеигровой статистики.
Просто всё равно у дамми спелов манакост и кд на 0.
Я даже в статье кастовал "Огненный столб" не сбивая ему манакост.
PT153 #6 - 4 месяца назад 0
GetUnitPointValue()
Это же вообще другое поле в РО. Или это опять кривой перевод?
NazarPunk #7 - 4 месяца назад (отредактировано ) 0
Это же вообще другое поле в РО. Или это опять кривой перевод?
Опыт из юнитов считается из игровых констант. Здесь есть формулы.
Да и название в РО намекает
Я это значение использую для задания дополнительных типов юнитов.
прикреплены файлы
PT153 #8 - 4 месяца назад (отредактировано ) 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.
прикреплены файлы
NazarPunk #9 - 4 месяца назад 0
Там это значение никак не фигурирует.
Потому, что оно не используется для расчёта набранного героем опыта))
PT153 #10 - 4 месяца назад (отредактировано ) 0
NazarPunk, выше дополнил пост.
Они у исходного юнита уже правильно выставлены.
Не совсем, у совы Colision Size равен 16. Лично я даммикам вообще pathing отключаю.
Ещё стоит Art - Projectile Launch - Z (launchZ) поставить на 0 и Art - Propulsion Window (propWin) на 180.
Я ещё Max Pitch и Max Roll зануляю, реген отключаю, дабы таймер регена юнита потенциально не тикал.
NazarPunk #11 - 4 месяца назад (отредактировано ) 0
А так как даммика никто не убивает, то и смысла менять данное поле нет.
Я где-то видел системы, где нулевое значение этого поля обозначало даммиков. Решил обнулить для совместимости.
Не совсем, у совы Colision Size равен 16.
Исправлено
Лично я даммикам вообще pathing отключаю.
Замечал такой баг, что юнит без движения не перемещается.
Ещё стоит Art - Projectile Launch - Z (launchZ) поставить на 0 и Art - Propulsion Window (propWin) на 180.
Это ж влияет на запуск снарядов от даммика, но тогда нужно будет ещё высоту полёта к кастеру приспосабливать.
Я ещё Max Pitch и Max Roll зануляю
Я пытался указать только необходимые поля. Если у кого-то возникнут проблемы с заклинаниями, поправлю и дополню статью.
реген отключаю, дабы таймер регена юнита потенциально не тикал.
Я тоже в приступах перфекционизма так делаю, но если не плодить даммиков сотнями, прирост будет незаметен.
ScopteRectuS #12 - 4 месяца назад 0
NazarPunk, планируется ли в будущем статья про фреймы?
NazarPunk #13 - 4 месяца назад 2
планируется ли в будущем статья про фреймы?
Планируется, но ещё думаю, каким образом на подстатьи разбить ибо много разной информации.