Так как игра не позволяет нам применять произвольную способность от произвольного юнита, была придумана хитрая система: сделать скрытого юнита и кастовать от его лица. Поэтому мы сразу его и создадим, для чего скопируем "(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
Вместо .mdl можно поставить просто _.
Укажи поля CastPoint, Fly Height, Movement Type, Collision Size. Они тоже важны.
Ред. nazarpunk
Да и название в РО намекает
Ред. PT153
Ред. PT153
Ещё стоит Art - Projectile Launch - Z (launchZ) поставить на 0 и Art - Propulsion Window (propWin) на 180.
Я ещё Max Pitch и Max Roll зануляю, реген отключаю, дабы таймер регена юнита потенциально не тикал.
Ред. nazarpunk