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

Заклинание: Волна Воды

» опубликован
» Способ реализации: Lua
» Тип: Способность
» MUI: да
» Импорт: нет
» Утечки: нет
» Требования: нет
Герой запускает волну, которая наносит 100/200/300 урона и рикошетит от рельефа.

Скриншот

Технические подробности

» Перенос в свою карту
Триггеры
  • WaterWave
Способности
  • Волна Воды 'SWaW:ANcl'
» Настройка
local ABILITY_ID = AbilityId('SFiB')
local MISSILE_EFFECT = {'Abilities\\Weapons\\WaterElementalMissile\\WaterElementalMissile.mdl',2} --> model, scal
local MISSILE_HEIGHT = 100
local MISSILE_START_DISTANCE = 100

local TIMER_PERIOD = 0.03125 --> 1/32
local SPEED = 600
local DISTANCE = {1200, 1800, 2400}

local DAMAGE_UNIT = {50, 75, 100}
local DAMAGE_UNIT_RANGE = 64
local DAMAGE_UNIT_PERIOD = 0.09
local DAMAGE_UNIT_EFFECT = {'Abilities\\Spells\\Other\\CrushingWave\\CrushingWaveDamage.mdl', 'origin'}
local DAMAGE_EXPLODE = {100, 200, 300}
local DAMAGE_EXPLODE_RANGE = 254
local DAMAGE_EXPLODE_EFFECT = 'Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl'
» Код
//! beginusercode
do
    -- На момент патча 1.31 эта функция всегда возвращает 0. Поэтому создадим её локальный аналог.
    local function AbilityId(id)
        return id:byte(1) * 0x1000000 + id:byte(2) * 0x10000 + id:byte(3) * 0x100 + id:byte(4)
    end

    -- Настройки
    local ABILITY_ID = AbilityId('SFiB')
    local MISSILE_EFFECT = {'Abilities\\Weapons\\WaterElementalMissile\\WaterElementalMissile.mdl',2} --> model, scal
    local MISSILE_HEIGHT = 100
    local MISSILE_START_DISTANCE = 100

    local TIMER_PERIOD = 0.03125 --> 1/32
    local SPEED = 600
    local DISTANCE = {1200, 1800, 2400}

    local DAMAGE_UNIT = {50, 75, 100}
    local DAMAGE_UNIT_RANGE = 64
    local DAMAGE_UNIT_PERIOD = 0.09
    local DAMAGE_UNIT_EFFECT = {'Abilities\\Spells\\Other\\CrushingWave\\CrushingWaveDamage.mdl', 'origin'}
    local DAMAGE_EXPLODE = {100, 200, 300}
    local DAMAGE_EXPLODE_RANGE = 254
    local DAMAGE_EXPLODE_EFFECT = 'Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl'

    -- Заклинание
    local SPEED_INC = SPEED/(1/TIMER_PERIOD)

    local function InMapXY(x, y)
        return
            x > GetRectMinX(bj_mapInitialPlayableArea)
            and
            x < GetRectMaxX(bj_mapInitialPlayableArea)
            and
            y > GetRectMinY(bj_mapInitialPlayableArea)
            and
            y < GetRectMaxY(bj_mapInitialPlayableArea)        
    end

    local GetTerrainZ_location = Location(0, 0)
    local function GetTerrainZ(x, y)
        MoveLocation(GetTerrainZ_location, x, y);
        return GetLocationZ(GetTerrainZ_location);
    end

    local TRIGGER = CreateTrigger()
    for i = 0, bj_MAX_PLAYER_SLOTS - 1, 1
    do
        TriggerRegisterPlayerUnitEvent(TRIGGER, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT)
    end
    TriggerAddCondition(t, Condition(function()
        return GetSpellAbilityId() == ABILITY_ID
    end))
    TriggerAddAction(TRIGGER, function()
        local caster = GetTriggerUnit()
        local x = GetUnitX(caster)
        local y = GetUnitY(caster)

        local angle = Atan2(GetSpellTargetY() - y, GetSpellTargetX() - x)
        local cos = Cos(angle)
        local sin = Sin(angle)

        x = x + MISSILE_START_DISTANCE*cos
        y = y + MISSILE_START_DISTANCE*sin
        local z = GetTerrainZ(x, y) + MISSILE_HEIGHT
        local zx
        local zy

        local level = GetUnitAbilityLevel(caster, ABILITY_ID)
        local distance = DISTANCE[level]

        local missile = AddSpecialEffect(MISSILE_EFFECT[1], x, y)
        BlzSetSpecialEffectYaw(missile, angle)
        BlzSetSpecialEffectHeight(missile, MISSILE_HEIGHT)
        BlzSetSpecialEffectScale(missile, MISSILE_EFFECT[2])
        
        local damaged = CreateGroup()
        local damaging = CreateGroup()

        local time = 0
        
        TimerStart(CreateTimer(), TIMER_PERIOD, true, function()
            zx = GetTerrainZ(x + 2*SPEED_INC*Cos(angle), y + 1*SPEED_INC*Sin(angle)) - z + MISSILE_HEIGHT
            zy = GetTerrainZ(x + 1*SPEED_INC*Cos(angle), y + 2*SPEED_INC*Sin(angle)) - z + MISSILE_HEIGHT
            
            if zx > z then angle = math.pi - angle end
            if zy > z then angle = 0 - angle end
            if zx > z or zy > z then
                cos = Cos(angle)
                sin = Sin(angle)
                BlzSetSpecialEffectYaw(missile, angle)
            end

            time = time + TIMER_PERIOD

            x = x + SPEED_INC*cos
            y = y + SPEED_INC*sin
            distance = distance - SPEED_INC

            if
                not InMapXY(x,y)
                or
                distance <= 0
            then
                
                DestroyEffect(AddSpecialEffect(DAMAGE_EXPLODE_EFFECT, x, y))
                GroupEnumUnitsInRange(damaging, x, y, DAMAGE_EXPLODE_RANGE, Filter(function()
                    return  
                            UnitAlive(GetFilterUnit())
                            and
                            IsPlayerEnemy(GetOwningPlayer(caster), GetOwningPlayer(GetFilterUnit()))
                            and
                            not IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE)
                            and
                            not IsUnitType(GetFilterUnit(), UNIT_TYPE_FLYING)
                end))
                
                ForGroup(damaging, function()
                    GroupAddUnit(damaged, GetEnumUnit())
                    DestroyEffect(AddSpecialEffectTarget(DAMAGE_UNIT_EFFECT[1], GetEnumUnit(), DAMAGE_UNIT_EFFECT[2]))
                    UnitDamageTarget(caster, GetEnumUnit(), DAMAGE_UNIT[level], false, true, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
                end)

                DestroyGroup(damaged)
                DestroyGroup(damaging)
                DestroyEffect(missile)
                DestroyTimer(GetExpiredTimer())
                return
            end

            BlzSetSpecialEffectX(missile, x)
            BlzSetSpecialEffectY(missile, y)
            BlzSetSpecialEffectHeight(missile, z - GetTerrainZ(x, y))

            if
                time > DAMAGE_UNIT_PERIOD
            then
                time = 0
                GroupEnumUnitsInRange(damaging, x, y, DAMAGE_UNIT_RANGE, Filter(function()
                    return  UnitAlive(GetFilterUnit())
                            and
                            IsPlayerEnemy(GetOwningPlayer(caster), GetOwningPlayer(GetFilterUnit()))
                            and
                            not IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE)
                            and
                            not IsUnitType(GetFilterUnit(), UNIT_TYPE_FLYING)
                            and
                            not IsUnitInGroup(GetFilterUnit(), damaged)
                end))
                
                ForGroup(damaging, function()
                    GroupAddUnit(damaged, GetEnumUnit())
                    DestroyEffect(AddSpecialEffectTarget(DAMAGE_UNIT_EFFECT[1], GetEnumUnit(), DAMAGE_UNIT_EFFECT[2]))
                    UnitDamageTarget(caster, GetEnumUnit(), DAMAGE_UNIT[level], false, true, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
                end)

                GroupClear(damaging)
            end

        end)
        
    end)
    
end
//! endusercode


Просмотров: 1 193

PT153 #1 - 5 месяцев назад 1
Каким образом она летит? На гифке как-то странно.
Bergi_Bear #2 - 5 месяцев назад 0
тогда нужно описание что летит рандомно, но это как-то ужасно..
В точку => в случайное место => в точку ещё раз
NazarPunk #3 - 5 месяцев назад -1
Каким образом она летит?
По прямой, пока не столкнётся с рельефом.

тогда нужно описание
Описания не моя сильная сторона((
Bergi_Bear #4 - 5 месяцев назад 2
Описания не моя сильная сторона((
Всё равно их никто не читает
ScopteRectuS #5 - 5 месяцев назад (отредактировано ) 0
NazarPunk, правильно ли я понимаю, что локальные переменные в Lua обнулять не нужно? Если да, то когда они сами обнулятся?
Просто писал такой код:
function InitDebugTriggers( )
    local trig = CreateTrigger( )
    local str = "asd"

    BlzTriggerRegisterPlayerKeyEvent( trig, Player( 0 ), OSKEY_ESCAPE, 0, true )

    TriggerAddAction( trig, function( )
        print( str )
    end )
end
Если нажать Esc, то на экран высветится "asd" вместо "nil". Почему так происходит, если функция InitDebugTriggers давно завершила свою работу (во время инициализации карты)? А действие триггера происходит уже намного позже, когда локальная переменная должна уже быть обнулена.
NazarPunk #6 - 5 месяцев назад 2
локальные переменные в Lua обнулять не нужно?
Нужно уничтожать объекты. Локальные переменные живут внутри своей области видимости и уничтожаются сборщиком мусора. Если хотите побыстрее отправить переменную в мусор, присвойте ей nil.
Переменная str живёт в замыкании от функции InitDebugTriggers( ).
PT153 #7 - 5 месяцев назад (отредактировано ) 0
ScopteRectuS, дело в том, что она локальна для всего блока InitDebugTriggers, а не только для функции, потому и не обнуляется. Тем более, переменная используется, с чего бы ей обнуляться?
NazarPunk #8 - 5 месяцев назад (отредактировано ) 0
Если говорить упрощённо, то передавая безымянную функции в TriggerAddAction вы также передаёте копии объектов, из её области видимости, где какраз и находится str.
local str = "one"
function InitDebugTriggers( )
	local trig = CreateTrigger( )
	BlzTriggerRegisterPlayerKeyEvent( trig, Player( 0 ), OSKEY_ESCAPE, 0, true )

	TriggerAddAction( trig, function( )
		print(str) --> two
	end)
end
str = "two"
Феникс #9 - 5 месяцев назад 1
Эффект волны выглядит неочень, может заменить на девятый вал?
NazarPunk #10 - 5 месяцев назад 1
может заменить на девятый вал?
Девятый вал расширяется и выглядит некрасиво, а искать на хайве было лень. Я его вообще для этого вопроса делал)
ScopteRectuS #11 - 5 месяцев назад 0
NazarPunk, получается, что, если переменная может использоваться в замыкающей функции, то она не будет очищена сборщиком мусора?

Также еще заметил, что глобальные переменные можно создавать внутри функции:
function CreateAllForces( )
    DEFENSIVE_FORCE = CreateForce( )
    OFFENSIVE_FORCE = CreateForce( )
    ALL_PLAYERS     = CreateForce( )

    for i = 0, MAX_PLAYERS_COUNT do
        if GetPlayerSlotState( Player( i ) ) == PLAYER_SLOT_STATE_PLAYING then
            ForceAddPlayer( ALL_PLAYERS, Player( i ) )
                
            if GetPlayerTeam( Player( i ) ) == 0 then
                ForceAddPlayer( DEFENSIVE_FORCE, Player( i ) )
            elseif GetPlayerTeam( Player( i ) ) == 1 then
                ForceAddPlayer( OFFENSIVE_FORCE, Player( i ) )
            end
        end
    end
end
Можно ли так делать? Если вызвать эту функцию дважды, то при первом вызове глобальная переменная будет создана, а во втором перезаписана?

Если создавать локальные переменные в цикле:
for i = 0, 10 do
    local var = i 
end
Переменная var будет создана 11 раз или 1 раз, а дальше перезаписываться?
NazarPunk #12 - 5 месяцев назад (отредактировано ) 2
Также еще заметил, что глобальные переменные можно создавать внутри функции
Глобальные переменные можно создавать откуда угодно. Но лучше так не делать
если переменная может использоваться в замыкающей функции, то она не будет очищена сборщиком мусора
Сборщик мусора очищает всё неиспользуемое. Если не уверены насчёт очистки, всегда есть nil.
Переменная var будет создана 11 раз или 1 раз, а дальше перезаписываться?
Блок do ... end создаёт область видимости и переменная будет создана один раз, потом будет перезаписываться и успешно умрёт вместе с циклом.
ScopteRectuS #13 - 5 месяцев назад (отредактировано ) 2
NazarPunk, спасибо большое. Теперь всё стало намного понятнее. Не могли ли бы Вы также рассказать про *in*:
for i = 0, 10, in do
Что за ключевое слово такое?

NazarPunk, еще хотел бы узнать о функциях в Lua.
function varName( ) return 0 end
varName = function( ) return 0 end
Это одно и то же или нет? Все функции в Lua - переменные? Я пришел к такому после того, как объявил две функции с одинаковыми именами, а при вызове этой функции вызывалась та, что ниже находилась, выглядит так, будто перезаписали переменную.
NazarPunk #14 - 5 месяцев назад (отредактировано ) 2
in это непонятная зверушка. Где вы такой for увидели?
Используйте лучше стандартный
for init,max/min value, increment
do
	statement(s)
end

function varName( ) return 0 end
varName = function( ) return 0 end
Это обычный сахар и таки да, вы переобъявили функцию.
Все функции в Lua - переменные?
В lua всё является объектами.
ScopteRectuS #15 - 5 месяцев назад (отредактировано ) 2
NazarPunk, спасибо. Поставил бы плюсиков, да кончились они.(
max = 3 на одного пользователя
PT153 #16 - 5 месяцев назад 0
Что за ключевое слово такое?
Хм, конструкция странная, скорее всего ошибочная. Но in может использоваться для перебора всех элементов массива, если вообще такое ключевое слово есть.
NazarPunk #17 - 5 месяцев назад 0
если вообще такое ключевое слово есть
На stackoverflow говорят что есть.
GetLocalPlayer #18 - 5 месяцев назад 2
Словно кто-то шваброй провел.
PT153 #19 - 5 месяцев назад 0
NazarPunk, я так и думал, что как в Python.
SomeFire #20 - 5 месяцев назад 0
Странно, что волна не дамажит повторно, а пролетает сквозь юнитов вхолостую.
NazarPunk #21 - 5 месяцев назад 0
Странно, что волна не дамажит повторно, а пролетает сквозь юнитов вхолостую.
Волна летит медленно, так что нужно или часто наносить маленький урон или вешать бафф. А переусложнять не хотелось.

Наверное сделаю волну замедления с баффом, чтоб повторно накладывалось.
PT153 #22 - 5 месяцев назад 0
NazarPunk, волна сделана на основе Девятого Вала? Если да, то ничего удивительного, я в Bullet Hell писал про баги с этим заклинанием.
NazarPunk #23 - 5 месяцев назад 0
волна сделана на основе Девятого Вала?
Волна сделана на основе Канала. Стандартные абилки от рельефа не отражаются.
PT153 #24 - 5 месяцев назад 0
NazarPunk, а сама волна триггерно двигается?
NazarPunk #25 - 5 месяцев назад (отредактировано ) 0
а сама волна триггерно двигается?
Да. Зачем выкладывать стандартные абилки?
Это сообщение удалено
ScopteRectuS #27 - 3 месяца назад (отредактировано ) 0
TimerStart(CreateTimer(), TIMER_PERIOD, true, function() ...
Является ли безымянная функция утечкой? Каждый раз создаётся новая функция, а как её удалить я не знаю.
NazarPunk #28 - 3 месяца назад 1
Является ли безымянная функция утечкой?
Она умрёт вместе с таймером.
prog #29 - 3 месяца назад 1
Она умрёт вместе с таймером.
Уточнение. Она умрет со следующей сборкой мусора, при которой она будет признана ненужной.
И вот тут начинаются нюансы.
ScopteRectuS #30 - 3 месяца назад (отредактировано ) 0
И вот тут начинаются нюансы.
prog, а если подробнее?
NazarPunk #31 - 3 месяца назад 0
а если подробнее?
Если просто, тов lua, как и в js объекты сразу не уничтожаются, а ждут своего часа до прихода сборщика мусора который удаляет объекты, на которые нет ссылок. Но как недавно выяснилось, ручной запуск сборщика может прибить таймеры и события триггеров.
prog #32 - 3 месяца назад 1
ручной запуск сборщика может прибить таймеры и события триггеров
А ты проверил в итоге, не делает ли то же самое автоматический сборщик? У меня с тех пор пока не было столько времени чтобы сесть и довести вар до сборки мусора несколько раз. Что-то мне подсказывает, что неудачные попытки делать систему движения снарядов на PTR были вызваны именно этим - сборщик мусора ел анонимные таймеры на анонимных функциях в анонимных триггерах.
ScopteRectuS #33 - 3 месяца назад (отредактировано ) 0
удаляет объекты, на которые нет ссылок
То есть, если в коде просто написать CreateTimer( ), то он сам уничтожится? Как сборщик понимает, что этот таймер уже не нужен?
NazarPunk #34 - 3 месяца назад 0
А ты проверил в итоге, не делает ли то же самое автоматический сборщик?
Подскажи как 100% отловить срабатывание сборщика, затестю.
сборщик мусора ел анонимные таймеры на анонимных функциях в анонимных триггерах.
С полностью именоваными так же падал. Когда запускал четыре безымянных таймера, то падали не все.
Bergi_Bear #35 - 3 месяца назад 0
сборщик мусора ел анонимные таймеры на анонимных функциях в анонимных триггерах
тогда да действительно похоже
prog #36 - 3 месяца назад (отредактировано ) 0
Подскажи как 100% отловить срабатывание сборщика, затестю.
Один из вариантов - добавить финализатор к таблице через метаметод __gc, потом убить все ссылки на эту таблицу и ждать.
В момент срабатывания финальной фазы сборки мусора перед удалением нашей таблицы вызовется __gc из её метатаблицы.