Для воскрешения героя нужно убедиться что он умер. Для этого можно использовать два события:
- EVENT_PLAYER_UNIT_DEATH
- EVENT_PLAYER_HERO_REVIVABLE
Они не реагируют на смерть героя с возможностью перерерождения и можно выбирать любую. Задержку между сообщением о гибели и событием EVENT_PLAYER_HERO_REVIVABLE можно настроить в игровых константах:
Теперь напишем простейшее воскрешение героя играющего человека в стартовой локации и в дальнейшем будем использовать как шаблон.
local ReviveTrigger = CreateTrigger()
for i = 0, bj_MAX_PLAYER_SLOTS do
local player = Player(i)
if GetPlayerController(player) == MAP_CONTROL_USER and GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING then
TriggerRegisterPlayerUnitEvent(ReviveTrigger, player, EVENT_PLAYER_HERO_REVIVABLE)
end
end
TriggerAddAction(ReviveTrigger, function()
local hero = GetTriggerUnit()
local heroOwner = GetOwningPlayer(hero)
if GetPlayerController(heroOwner) ~= MAP_CONTROL_USER or GetPlayerSlotState(heroOwner) ~= PLAYER_SLOT_STATE_PLAYING then return end
local x, y = GetPlayerStartLocationX(heroOwner), GetPlayerStartLocationY(heroOwner)
ReviveHero(hero, x, y, true) -- здесь в дальнейшем будем воскрешать героев
end)
Таймер
Часто используется в AoS картах. Идея проста, после смерти отсчитываем время зависящее от уровня героя и возрождаем. Реализация тоже не очень сложна:
local timer = CreateTimer()
local dialog = CreateTimerDialog(timer)
TimerDialogSetTitle(dialog, GetUnitName(hero))
TimerStart(timer, GetHeroLevel(hero) * 2, false, function()
local x, y = GetPlayerStartLocationX(heroOwner), GetPlayerStartLocationY(heroOwner)
ReviveHero(hero, x, y, true)
DestroyTimerDialog(dialog)
DestroyTimer(GetExpiredTimer())
end)
TimerDialogDisplay(dialog, true)
Код
do
local InitGlobalsOrigin = InitGlobals
function InitGlobals()
InitGlobalsOrigin()
local ReviveTrigger = CreateTrigger()
for i = 0, bj_MAX_PLAYER_SLOTS do
local player = Player(i)
if GetPlayerController(player) == MAP_CONTROL_USER and GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING then
TriggerRegisterPlayerUnitEvent(ReviveTrigger, player, EVENT_PLAYER_HERO_REVIVABLE)
end
end
TriggerAddAction(ReviveTrigger, function()
local hero = GetTriggerUnit()
local heroOwner = GetOwningPlayer(hero)
if GetPlayerController(heroOwner) ~= MAP_CONTROL_USER or GetPlayerSlotState(heroOwner) ~= PLAYER_SLOT_STATE_PLAYING then return end
local timer = CreateTimer()
local dialog = CreateTimerDialog(timer)
TimerDialogSetTitle(dialog, GetUnitName(hero))
TimerStart(timer, GetHeroLevel(hero) * 2, false, function()
local x, y = GetPlayerStartLocationX(heroOwner), GetPlayerStartLocationY(heroOwner)
ReviveHero(hero, x, y, true)
DestroyTimerDialog(dialog)
DestroyTimer(GetExpiredTimer())
end)
TimerDialogDisplay(dialog, true)
end)
end
end
Камень Воскрешения
Если вы играли в кампанию Рексара, то наверно видели камни возрождения.
А если вы открывали редактор, то наверно заметили, что их несколько.
Отобразим это в коде. Заодно покроем случай, когда вам захочется сделать несколько типов камней для разных локаций.
local stonesId = { FourCC('nbse'), FourCC('nbsw') }
Так как работать с группой разных юнитов будет удобней, чем каждый раз проверять id в цикле, заодно и создадим группу для камней.
local stonesGroup = CreateGroup()
Исходя из того, что для каждого игрока может быть активным только один камень, создадим таблицу, в которой будем хранить активный камень по id игрока.
local playerStone = {}
Учитывая что у игрока может не быть активного камня, а возрождаться ему где-то нужно, напишем функцию поиска ближайшего камня:
---@param x real
---@param y real
---@return unit
local GetClosestStone = function(x, y)
local stone
local distance
for index = BlzGroupGetSize(stonesGroup) - 1, 0, -1 do
local unit = BlzGroupUnitAt(stonesGroup, index)
local dx, dy = GetUnitX(unit) - x, GetUnitY(unit) - y
local dist = math.sqrt(dx * dx + dy * dy)
if stone == nil or dist <= distance then
stone = unit
distance = dist
end
end
return stone
end
Для реализации смены активного камня воспользуемся функцией TriggerRegisterUnitInRange. Правда этот способ не позволяет определить юнита к которому подходят, но мы уже написали прекрасную функцию GetClosestStone, так что мы молодцы проблема решена заранее.
local RangeTrigger = CreateTrigger()
TriggerAddAction(RangeTrigger, function()
local hero = GetTriggerUnit()
local heroOwner = GetOwningPlayer(hero)
local heroOwnerId = GetPlayerId(heroOwner)
if not UnitAlive(hero)
or not IsUnitType(hero, UNIT_TYPE_HERO)
or GetPlayerController(heroOwner) ~= MAP_CONTROL_USER
or GetPlayerSlotState(heroOwner) ~= PLAYER_SLOT_STATE_PLAYING
then return end
if playerStone[heroOwnerId] ~= nil then
if heroOwner == GetLocalPlayer() then
AddUnitAnimationProperties(playerStone[heroOwnerId], 'alternate', false)
end
UnitShareVision(playerStone[heroOwnerId], heroOwner, false)
end
playerStone[heroOwnerId] = GetClosestStone(GetUnitX(hero), GetUnitY(hero))
if heroOwner == GetLocalPlayer() then
AddUnitAnimationProperties(playerStone[heroOwnerId], 'alternate', true)
end
UnitShareVision(playerStone[heroOwnerId], heroOwner, true)
end)
Если вы внимательно читали, то наверное вспомните, что мы создали группу, но так ничего в неё не добавили. Пора исправить этот недостаток:
GroupEnumUnitsInRect(stonesGroup, bj_mapInitialPlayableArea)
for index = BlzGroupGetSize(stonesGroup) - 1, 0, -1 do
local stone = BlzGroupUnitAt(stonesGroup, index)
local stoneId = GetUnitTypeId(stone)
local isExclude = true
for i = 1, #stonesId do
if stoneId == stonesId[i] then isExclude = false end
end
if isExclude then
GroupRemoveUnit(stonesGroup, stone)
else
TriggerRegisterUnitInRange(RangeTrigger, stone, 256)
end
end
В завершении добавим триггер воскрешения и можно наслаждаться результатом:
local ReviveTrigger = CreateTrigger()
for i = 0, bj_MAX_PLAYER_SLOTS do
local player = Player(i)
if GetPlayerController(player) == MAP_CONTROL_USER and GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING then
TriggerRegisterPlayerUnitEvent(ReviveTrigger, player, EVENT_PLAYER_HERO_REVIVABLE)
end
end
TriggerAddAction(ReviveTrigger, function()
local hero = GetTriggerUnit()
local heroOwner = GetOwningPlayer(hero)
local heroOwnerId = GetPlayerId(heroOwner)
if GetPlayerController(heroOwner) ~= MAP_CONTROL_USER or GetPlayerSlotState(heroOwner) ~= PLAYER_SLOT_STATE_PLAYING then return end
local stone = playerStone[heroOwnerId] ~= nil and playerStone[heroOwnerId] or GetClosestStone(GetUnitX(hero), GetUnitY(hero))
ReviveHero(hero, GetUnitX(stone), GetUnitY(stone), true)
end)
Код
do
local InitGlobalsOrigin = InitGlobals
function InitGlobals()
InitGlobalsOrigin()
local stonesId = { FourCC('nbse'), FourCC('nbsw') }
local stonesGroup = CreateGroup()
local playerStone = {}
---@param x real
---@param y real
---@return unit
local GetClosestStone = function(x, y)
local stone
local distance
for index = BlzGroupGetSize(stonesGroup) - 1, 0, -1 do
local unit = BlzGroupUnitAt(stonesGroup, index)
local dx, dy = GetUnitX(unit) - x, GetUnitY(unit) - y
local dist = math.sqrt(dx * dx + dy * dy)
if stone == nil or dist <= distance then
stone = unit
distance = dist
end
end
return stone
end
local RangeTrigger = CreateTrigger()
TriggerAddAction(RangeTrigger, function()
local hero = GetTriggerUnit()
local heroOwner = GetOwningPlayer(hero)
local heroOwnerId = GetPlayerId(heroOwner)
if not UnitAlive(hero)
or not IsUnitType(hero, UNIT_TYPE_HERO)
or GetPlayerController(heroOwner) ~= MAP_CONTROL_USER
or GetPlayerSlotState(heroOwner) ~= PLAYER_SLOT_STATE_PLAYING
then return end
if playerStone[heroOwnerId] ~= nil then
if heroOwner == GetLocalPlayer() then
AddUnitAnimationProperties(playerStone[heroOwnerId], 'alternate', false)
end
UnitShareVision(playerStone[heroOwnerId], heroOwner, false)
end
playerStone[heroOwnerId] = GetClosestStone(GetUnitX(hero), GetUnitY(hero))
if heroOwner == GetLocalPlayer() then
AddUnitAnimationProperties(playerStone[heroOwnerId], 'alternate', true)
end
UnitShareVision(playerStone[heroOwnerId], heroOwner, true)
end)
GroupEnumUnitsInRect(stonesGroup, bj_mapInitialPlayableArea)
for index = BlzGroupGetSize(stonesGroup) - 1, 0, -1 do
local stone = BlzGroupUnitAt(stonesGroup, index)
local stoneId = GetUnitTypeId(stone)
local isExclude = true
for i = 1, #stonesId do
if stoneId == stonesId[i] then isExclude = false end
end
if isExclude then
GroupRemoveUnit(stonesGroup, stone)
else
TriggerRegisterUnitInRange(RangeTrigger, stone, 256)
end
end
local ReviveTrigger = CreateTrigger()
for i = 0, bj_MAX_PLAYER_SLOTS do
local player = Player(i)
if GetPlayerController(player) == MAP_CONTROL_USER and GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING then
TriggerRegisterPlayerUnitEvent(ReviveTrigger, player, EVENT_PLAYER_HERO_REVIVABLE)
end
end
TriggerAddAction(ReviveTrigger, function()
local hero = GetTriggerUnit()
local heroOwner = GetOwningPlayer(hero)
local heroOwnerId = GetPlayerId(heroOwner)
if GetPlayerController(heroOwner) ~= MAP_CONTROL_USER or GetPlayerSlotState(heroOwner) ~= PLAYER_SLOT_STATE_PLAYING then return end
local stone = playerStone[heroOwnerId] ~= nil and playerStone[heroOwnerId] or GetClosestStone(GetUnitX(hero), GetUnitY(hero))
ReviveHero(hero, GetUnitX(stone), GetUnitY(stone), true)
end)
end
end
Заключение
Как видите, воскрешать героя не так уж и сложно, но если у вас есть вопросы или вы знаете интересные способы воскрешения, то пишите их в комментариях. Также можете скачать карту и поэксперементировать.
Ред. prog
Ред. nazarpunk
Ну и да, воскрешение у ближайшего камня не всегда хорошая идея - если, например, ближайший камень находится в каком-то секретном или временно непроходимом месте и у игрока еще нет активного камня, то результат будет плачевным - я скорее принудительно выдавал бы игроку какой-то камень на старте и не парился обработкой кейса "камня нет", а в таком случае и поиск ближайшего камня в коде не появляется и можно смело "экономить на спичках" т.к. нет реюза функции поиска ближайшего камня ;)
Ред. nazarpunk
Ред. prog
Это вариация воскрешения у камней, но с нюансами. Главное отличие - триггер привязки камня реализован через покупку предмета в магазине, а не через вход в радиус. Это позволяет ходить возле "камня" не боясь случайно перепривязаться в неудобном месте и заодно позволяет добавить стоимость к процессу привязки камня. А в моем случае я использую еще и перезарядку продажи, чтобы несколько игроков не могли без задержки привязаться к одному камню.
Ред. nazarpunk
Хотя на сколько я много раз тестировал группы перебора, то всегда первым объектом бывает тот, кто ближе к [ x, y ]. Хотя, может быть, просто тогда были совпадения. Раньше я думал, что он просто пихает туда юниты из общего набора, которые по дистанции просто ближе к точке.
Ред. nazarpunk
Ред. scopterectus
Ред. MpW