Cоздал я свой первый фрейм, короче, на основе фрейма таймердиалога. Но я так и не понял, как это работает. Как варик понимает, где я хочу поменять текст, когда вызываю BlzGetFrameByName("TimerDialogValue", 0). Ведь я не указываю, у wave_info или у wave_info_next нужно менять текст.
код
TimerStart(CreateTimer( ), 0, false, function( )
local game_ui = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI,0)
local wave_info = BlzCreateFrame("TimerDialog", game_ui, 0, 0)
local value = BlzGetFrameByName("TimerDialogValue", 0)
local title = BlzGetFrameByName("TimerDialogTitle", 0)
BlzFrameSetAbsPoint(wave_info, FRAMEPOINT_CENTER, 0.4, 0.4)
BlzFrameSetText(value, "Wolves")
BlzFrameSetText(title, "Current Wave:")
local wave_info_next = BlzCreateFrame("TimerDialog", game_ui, 0, 0)
local value_next = BlzGetFrameByName("TimerDialogValue", 0)
local title_next = BlzGetFrameByName("TimerDialogTitle", 0)
BlzFrameSetPoint(wave_info_next, FRAMEPOINT_TOP, wave_info, FRAMEPOINT_BOTTOM, 0.0, 0.0)
BlzFrameSetText(value_next, "Humands")
BlzFrameSetText(title_next, "Next Wave:")
end)
quq_CCCP, у меня крипы спавнятся не из одной точки. Крипы спавнятся в лагерях. В каждом лагере есть несколько точек спавна. При каждом спавне выбирается рандомная точка спавна из рандомного лагеря. Так что с реалистичностью у меня нет проблем.
local data = {}
for _, value in pairs(Creep) do
if type(value) == "table" and value.round ~= nil then
if data[value.round] == nil then
data[value.round] = {}
end
table.insert( data[value.round], value )
end
end
for key, value in pairs(data) do
local i = math.random(1, #value)
name [key] = value[i].name
stylizedName[key] = value[i].stylizedName
tip [key] = value[i].tip
prepare [key] = value[i].prepare
count [key] = value[i].count
limit [key] = value[i].limit
spawnCamp [key] = value[i].spawnCamp
startSound [key] = value[i].startSound
endSound [key] = value[i].endSound
pool [key] = value[i].pool
BJDebugMsg('Round ' .. key .. ': ' ..name[key])
end
N1ghtSiren, я недостаточно хорошо объяснился, что мне нужно. Иными словами, мне нужно, чтобы при написании следующего кода:
function Creep.wolves.initialize()
Creep.wolves.round = 1
Creep.wolves.name = 'W O L V E S'
Creep.wolves.tip = ''
Creep.wolves.prepare = 15.0
Creep.wolves.count = 300
Creep.wolves.limit = 120
Creep.wolves.spawnCamp = {SpawnCircle.getLeftCamp(), SpawnCircle.getMainCamp(), SpawnCircle.getRightCamp()}
Creep.wolves.startSound = CreateSound('Units\\Orc\\Spiritwolf\\SpiritWolfYes3.wav', false, false, false, 10, 10, 'DefaultEAXON')
Creep.wolves.endSound = CreateSound('Units\\Creeps\\Direwolf\\DireWolfDeath1.wav', false, false, false, 10, 10, 'DefaultEAXON')
Creep.wolves.pool = CreateUnitPool()
UnitPoolAddUnitType(Creep.wolves.pool, FourCC('nwlt'), 30.0)
UnitPoolAddUnitType(Creep.wolves.pool, FourCC('nwlg'), 20.0)
UnitPoolAddUnitType(Creep.wolves.pool, FourCC('nwld'), 10.0)
end
система должна сама понять, что появился новый тип волны под названием "Волки" и добавить его в список волн первого раунда (Creep.wolves.round = 1). А если я напишу function Creep.pigs.initialize(), система должна добавить еще один новый вид волн крипов.
do
SpawnCircle = {}
local campMain = {}
local campAir = {}
local campLeft = {}
local campRight = {}
local campWater = {}
local r = {}
local g = {}
local b = {}
function SpawnCircle.getMainCamp() return campMain end
function SpawnCircle.getAirCamp() return campAir end
function SpawnCircle.getRightCamp() return campRight end
function SpawnCircle.getLeftCamp() return campLeft end
function SpawnCircle.getWaterCamp() return campWater end
function SpawnCircle.getRandom(whichTable)
local camp = whichTable[math.random(1, #whichTable)]
return camp[math.random(1, #camp)]
end
function SpawnCircle.pingMinimap(whichTable)
for _, camp in pairs(whichTable) do
for _, circle in pairs(camp) do
PingMinimapEx(GetUnitX(circle), GetUnitY(circle), 12.0, r[GetOwningPlayer(circle)], g[GetOwningPlayer(circle)], b[GetOwningPlayer(circle)], false)
end
end
end
function SpawnCircle.initialize()
r[User.kamok] = 0xFF; g[User.kamok] = 0x03; b[User.kamok] = 0x03
r[User.bimok] = 0xFF; g[User.bimok] = 0xFF; b[User.bimok] = 0x03
r[User.samok] = 0x03; g[User.samok] = 0xFF; b[User.samok] = 0xFF
r[User.njmok] = 0x6F; g[User.njmok] = 0x25; b[User.njmok] = 0x83
r[User.damok] = 0x03; g[User.damok] = 0x03; b[User.damok] = 0xFF
table.insert( campMain, CreateUnit(User.kamok, FourCC('n005'), -2304.0, -3584.0, 270.0))
table.insert( campMain, CreateUnit(User.kamok, FourCC('n005'), 2560.0, 1280.0, 270.0))
table.insert( campAir, CreateUnit(User.bimok, FourCC('n005'), 9472.0, -9280.0, 270.0))
table.insert( campAir, CreateUnit(User.bimok, FourCC('n005'), 8256.0, -10496.0, 270.0))
table.insert( campLeft, CreateUnit(User.samok, FourCC('n005'), -1408.0, -10496.0, 270.0))
table.insert( campLeft, CreateUnit(User.samok, FourCC('n005'), 3072.0, -10496.0, 270.0))
table.insert(campRight, CreateUnit(User.njmok, FourCC('n005'), 9472.0, 384.0, 270.0))
table.insert(campRight, CreateUnit(User.njmok, FourCC('n005'), 9472.0, -4096.0, 270.0))
table.insert(campWater, CreateUnit(User.damok, FourCC('n005'), -2304.0, -6912.0, 270.0))
table.insert(campWater, CreateUnit(User.damok, FourCC('n005'), 5888.0, 1280.0, 270.0))
table.insert(campWater, CreateUnit(User.damok, FourCC('n005'), 5376.0, -10496.0, 270.0))
table.insert(campWater, CreateUnit(User.damok, FourCC('n005'), 9472.0, -6400.0, 270.0))
end
end
Wave
do
Wave = {}
local round = 1
local name = {}
local tip = {}
local prepare = {}
local count = {}
local limit = {}
local spawnCamp = {}
local startSound = {}
local endSound = {}
local pool = {}
local tickSound = nil
local hintSound = nil
local timer = nil
local timerWindow = nil
function Wave.displayTopMsg()
local gameui = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0)
local frame = BlzCreateFrameByType('TEXT', '', gameui, '', 0)
local alpha = 0xFF
BlzFrameSetPoint(frame, FRAMEPOINT_TOP, gameui, FRAMEPOINT_TOP, 0.0, -0.0195)
BlzFrameSetVisible(frame, true)
BlzFrameSetScale(frame, 2.6)
if pool[round + 1] ~= nil then
BlzFrameSetText(frame, '|cFFFFFF00R O U N D ' .. tostring(round) .. ' :|r ' .. tostring(name[round]))
elseif pool[round + 1] == nil then
BlzFrameSetText(frame, '|cFFFFFF00F I N A L R O U N D :|r ' .. tostring(name[round]))
end
TimerStart(CreateTimer(), 10.0, false, function()
TimerStart(GetExpiredTimer(), 0.03125, true, function()
alpha = alpha - 0x04
if alpha > 0x00 then
BlzFrameSetAlpha(frame, alpha)
else
PauseTimer(GetExpiredTimer())
DestroyTimer(GetExpiredTimer())
BlzDestroyFrame(frame)
end
end)
end)
end
function Wave.displayTip()
if tip[round] ~= '' then
DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, bj_TEXT_DELAY_ALWAYSHINT, ' ')
DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, bj_TEXT_DELAY_ALWAYSHINT, '|cFF32CD32HINT|r - ' .. tip[round])
StartSound(hintSound)
end
end
function Wave.getCount()
local n = 0
for _, value in pairs(Team.offensiveForce) do
n = n + GetPlayerState(value, PLAYER_STATE_RESOURCE_FOOD_USED)
end
return n
end
function Wave.playSound(soundHandle)
if soundHandle ~= nil then
StartSound(soundHandle)
end
end
function Wave.endRound()
round = round + 1
if pool[round] ~= nil then
Wave.playSound(endSound[round])
Wave.startSpawnTimeout()
Scoreboard.show(true)
PowerUp.create()
else
Game.startVictory()
Scoreboard.show(true)
end
end
function Wave.startRound()
TimerDialogDisplay(timerWindow, false)
SpawnCircle.pingMinimap(spawnCamp[round])
Wave.displayTopMsg()
Wave.displayTip()
Wave.playSound(startSound[round])
Wave.startSpawn()
Scoreboard.show(false)
PowerUp.create()
end
function Wave.startSpawn()
if count[round] > 0 then
TimerStart(timer, 0.75, false, Wave.startSpawn)
for i = 0, 8 do
if Wave.getCount() > limit[round] or count[round] == 0 then
break
end
local circle = SpawnCircle.getRandom(spawnCamp[round])
local unit = PlaceRandomUnit(pool[round], GetOwningPlayer(circle), GetUnitX(circle) + math.random(-16, 16), GetUnitY(circle) + math.random(-16, 16), 0.0)
IssuePointOrderById(unit, OrderId('attack'), 4096.0, -5120.0)
SetUnitColor(unit, PLAYER_COLOR_COAL)
DestroyEffect(AddSpecialEffectTarget('Abilities\\Spells\\Human\\MassTeleport\\MassTeleportTarget.mdl', circle, 'origin'))
count[round] = count[round] - 1
end
elseif count[round] == 0 and Wave.getCount() > 0 then
TimerStart(timer, 2.75, false, Wave.startSpawn)
elseif count[round] == 0 and Wave.getCount() == 0 then
Wave.endRound()
end
end
function Wave.startSpawnTimeout()
if timer == nil then
timer = CreateTimer()
timerWindow = CreateTimerDialog(timer)
TimerDialogSetTitleColor(timerWindow, 0xFF, 0xFF, 0xFF, 0xFF)
TimerDialogSetTimeColor (timerWindow, 0xFF, 0xFF, 0xFF, 0xFF)
end
if pool[round - 1] == nil then
TimerDialogSetTitle(timerWindow, 'First wave in:')
elseif pool[round + 1] == nil then
TimerDialogSetTitle(timerWindow, 'Final wave in:')
else
TimerDialogSetTitle(timerWindow, 'Next wave in:')
end
TimerDialogDisplay(timerWindow, true)
TimerStart(timer, prepare[round], false, Wave.startRound)
if prepare[round] >= 3.0 then
TimerStart(CreateTimer(), prepare[round] - 3.0, false, function()
StartSound(tickSound)
TimerStart(GetExpiredTimer(), 1.0, false, function()
StartSound(tickSound)
TimerStart(GetExpiredTimer(), 1.0, false, function()
StartSound(tickSound)
PauseTimer(GetExpiredTimer())
DestroyTimer(GetExpiredTimer())
end)
end)
end)
end
end
function Wave.initialize()
SpawnCircle.initialize()
tickSound = CreateSound('Sound\\Interface\\BattleNetTick.wav', false, false, false, 10, 10, 'DefaultEAXON')
hintSound = CreateSoundFromLabel('Hint', false, false, false, 10000, 10000)
for _, offensivePlayer in pairs(Team.offensiveForce) do
for _, defensivePlayer in pairs(Team.defensiveForce) do
SetPlayerAlliance(offensivePlayer, defensivePlayer, ALLIANCE_SHARED_VISION, true)
SetPlayerAlliance(defensivePlayer, offensivePlayer, ALLIANCE_SHARED_VISION, true)
end
end
local trig = CreateTrigger()
for _, value in pairs(Team.offensiveForce) do
TriggerRegisterPlayerUnitEvent(trig, value, EVENT_PLAYER_UNIT_SPELL_ENDCAST, nil)
end
TriggerAddAction(trig, function()
IssuePointOrderById(GetSpellAbilityUnit(), OrderId('attack'), 4096.0, -5120.0)
end)
TimerStart(CreateTimer(), 0.0, false, function()
DestroyTimer(GetExpiredTimer())
Wave.startSpawnTimeout()
end)
if math.random(1, 2) == 1 then
name [1] = 'L O B S T R O K K S'
tip [1] = ''
prepare [1] = 15.0
count [1] = 300
limit [1] = 60
spawnCamp [1] = {SpawnCircle.getWaterCamp()}
startSound[1] = CreateSound('Units\\Creeps\\Lobstrokkblue\\LobstrokkWhat1.wav', false, false, false, 10, 10, 'DefaultEAXON')
endSound [1] = CreateSound('Units\\Creeps\\Lobstrokkblue\\LobstrokkDeath1.wav', false, false, false, 10, 10, 'DefaultEAXON')
pool [1] = CreateUnitPool()
UnitPoolAddUnitType(pool[1], FourCC('nlpr'), 60.0)
UnitPoolAddUnitType(pool[1], FourCC('nlpd'), 50.0)
UnitPoolAddUnitType(pool[1], FourCC('nltc'), 40.0)
UnitPoolAddUnitType(pool[1], FourCC('nlds'), 30.0)
UnitPoolAddUnitType(pool[1], FourCC('nlsn'), 20.0)
UnitPoolAddUnitType(pool[1], FourCC('nlkl'), 10.0)
else
name [1] = 'W O L V E S'
tip [1] = ''
prepare [1] = 15.0
count [1] = 300
limit [1] = 120
spawnCamp [1] = {SpawnCircle.getLeftCamp(), SpawnCircle.getMainCamp(), SpawnCircle.getRightCamp()}
startSound[1] = CreateSound('Units\\Orc\\Spiritwolf\\SpiritWolfYes3.wav', false, false, false, 10, 10, 'DefaultEAXON')
endSound [1] = CreateSound('Units\\Creeps\\Direwolf\\DireWolfDeath1.wav', false, false, false, 10, 10, 'DefaultEAXON')
pool [1] = CreateUnitPool()
UnitPoolAddUnitType(pool[1], FourCC('nwlt'), 30.0)
UnitPoolAddUnitType(pool[1], FourCC('nwlg'), 20.0)
UnitPoolAddUnitType(pool[1], FourCC('nwld'), 10.0)
end
if math.random(1, 2) == 1 then
name [2] = 'T R O L L S'
tip [2] = ''
prepare [2] = 15.0
count [2] = 300
limit [2] = 120
spawnCamp[2] = {SpawnCircle.getLeftCamp(), SpawnCircle.getMainCamp(), SpawnCircle.getRightCamp()}
pool [2] = CreateUnitPool()
UnitPoolAddUnitType(pool[2], FourCC('nftr'), 60.0)
UnitPoolAddUnitType(pool[2], FourCC('nftb'), 50.0)
UnitPoolAddUnitType(pool[2], FourCC('nfsp'), 40.0)
UnitPoolAddUnitType(pool[2], FourCC('nftt'), 30.0)
UnitPoolAddUnitType(pool[2], FourCC('nftk'), 20.0)
UnitPoolAddUnitType(pool[2], FourCC('nfsh'), 10.0)
else
name [2] = 'O G R E S'
tip [2] = 'Огры спокойно могут надавать пиздю*ей неосторожному игроку, так что будьте керифул.'
prepare [2] = 15.0
count [2] = 50
limit [2] = 120
spawnCamp[2] = {SpawnCircle.getLeftCamp(), SpawnCircle.getMainCamp(), SpawnCircle.getRightCamp()}
pool [2] = CreateUnitPool()
UnitPoolAddUnitType(pool[2], FourCC('nogr'), 40.0)
UnitPoolAddUnitType(pool[2], FourCC('nomg'), 30.0)
UnitPoolAddUnitType(pool[2], FourCC('nogm'), 20.0)
UnitPoolAddUnitType(pool[2], FourCC('nogl'), 10.0)
end
if math.random(1, 5) == 1 then
name [3] = 'R E D D R A G O N S'
tip [3] = ''
prepare [3] = 15.0
count [3] = 300
limit [3] = 120
spawnCamp[3] = {SpawnCircle.getLeftCamp(), SpawnCircle.getMainCamp(), SpawnCircle.getRightCamp(), SpawnCircle.getAirCamp()}
pool [3] = CreateUnitPool()
UnitPoolAddUnitType(pool[3], FourCC('nrdk'), 40.0)
UnitPoolAddUnitType(pool[3], FourCC('nrdr'), 30.0)
UnitPoolAddUnitType(pool[3], FourCC('nrwm'), 20.0)
elseif math.random(1, 5) == 1 then
name [3] = 'B L A C K D R A G O N S'
tip [3] = ''
prepare [3] = 15.0
count [3] = 300
limit [3] = 120
spawnCamp[3] = {SpawnCircle.getLeftCamp(), SpawnCircle.getMainCamp(), SpawnCircle.getRightCamp(), SpawnCircle.getAirCamp()}
pool [3] = CreateUnitPool()
UnitPoolAddUnitType(pool[3], FourCC('nbdr'), 40.0)
UnitPoolAddUnitType(pool[3], FourCC('nbdd'), 30.0)
UnitPoolAddUnitType(pool[3], FourCC('nbwm'), 20.0)
elseif math.random(1, 5) == 1 then
name [3] = 'B R O N Z E D R A G O N S'
tip [3] = ''
prepare [3] = 15.0
count [3] = 300
limit [3] = 120
spawnCamp[3] = {SpawnCircle.getLeftCamp(), SpawnCircle.getMainCamp(), SpawnCircle.getRightCamp(), SpawnCircle.getAirCamp()}
pool [3] = CreateUnitPool()
UnitPoolAddUnitType(pool[3], FourCC('nbzw'), 40.0)
UnitPoolAddUnitType(pool[3], FourCC('nbzk'), 30.0)
UnitPoolAddUnitType(pool[3], FourCC('nbzd'), 20.0)
elseif math.random(1, 5) == 1 then
name [3] = 'G R E E N D R A G O N S'
tip [3] = ''
prepare [3] = 15.0
count [3] = 300
limit [3] = 120
spawnCamp[3] = {SpawnCircle.getLeftCamp(), SpawnCircle.getMainCamp(), SpawnCircle.getRightCamp(), SpawnCircle.getAirCamp()}
pool [3] = CreateUnitPool()
UnitPoolAddUnitType(pool[3], FourCC('ngrw'), 40.0)
UnitPoolAddUnitType(pool[3], FourCC('ngdk'), 30.0)
UnitPoolAddUnitType(pool[3], FourCC('ngrd'), 20.0)
else
name [3] = 'B L U E D R A G O N S'
tip [3] = ''
prepare [3] = 15.0
count [3] = 300
limit [3] = 120
spawnCamp[3] = {SpawnCircle.getLeftCamp(), SpawnCircle.getMainCamp(), SpawnCircle.getRightCamp(), SpawnCircle.getAirCamp()}
pool [3] = CreateUnitPool()
UnitPoolAddUnitType(pool[3], FourCC('nadw'), 40.0)
UnitPoolAddUnitType(pool[3], FourCC('nadk'), 30.0)
UnitPoolAddUnitType(pool[3], FourCC('nadr'), 20.0)
end
name [4] = 'K A \' M O K'
tip [4] = ''
prepare [4] = 45.0
count [4] = 1
limit [4] = 1
spawnCamp[4] = {SpawnCircle.getMainCamp()}
pool [4] = CreateUnitPool()
UnitPoolAddUnitType(pool[4], FourCC('n00D'), 100.0)
end
end
новый Creep
do
Creep = {}
local name = {}
local tip = {}
local prepare = {}
local count = {}
local limit = {}
local spawnCamp = {}
local startSound = {}
local endSound = {}
local pool = {}
Creep.wolves = {}
function Creep.wolves.initialize()
Creep.wolves.round = 1
Creep.wolves.name = 'W O L V E S'
Creep.wolves.tip = ''
Creep.wolves.prepare = 15.0
Creep.wolves.count = 300
Creep.wolves.limit = 120
Creep.wolves.spawnCamp = {SpawnCircle.getLeftCamp(), SpawnCircle.getMainCamp(), SpawnCircle.getRightCamp()}
Creep.wolves.startSound = CreateSound('Units\\Orc\\Spiritwolf\\SpiritWolfYes3.wav', false, false, false, 10, 10, 'DefaultEAXON')
Creep.wolves.endSound = CreateSound('Units\\Creeps\\Direwolf\\DireWolfDeath1.wav', false, false, false, 10, 10, 'DefaultEAXON')
Creep.wolves.pool = CreateUnitPool()
UnitPoolAddUnitType(Creep.wolves.pool, FourCC('nwlt'), 30.0)
UnitPoolAddUnitType(Creep.wolves.pool, FourCC('nwlg'), 20.0)
UnitPoolAddUnitType(Creep.wolves.pool, FourCC('nwld'), 10.0)
end
function Creep.initialize()
for _, value in pairs(Creep) do
if type(value) == "table" then
end
end
end
end
Ronnie, можно еще добавить рандомности, если перемещать юнита не сразу же по истечению времени, а добавить условие ( GetRandomInt( 0, 3 ) < 2 ), если юнит не проходит проверку, то его не трограем. Так будут двигаться не все юниты одновременно, а лишь некоторые. А таймер можно сделать не периодическим, а запускать его повторно каждый тик, но уже с рандомным таймаутом.
В идеале, имхо, в редакторе триггеров должен быть по дефолту триггер/блок кода с функцией config и main. Чтобы картостроитель сам решал, что ему нужно. Что-то подобное уже реализовано в стандартных мили картах, где есть триггер "Иниц. сражения", где создаётся всё необходимое. Естественно, нужно поставить запрет на удаление этих триггеров/блока кодов.
Это свойство не самого юнита, а способности, которая призывает юнита. Для того, чтобы дать обычному юниту время жизни, нужно через триггеры применить на данного юнита функцию Add Expiration Timer из вкладки Боевая Единица.
Есть еще функция SetUnitExploded( unit, boolean ). Если сделать тру, то юнит взрывается при смерти. Эта функция также есть и в GUI - Make Unit Explode On Death во вкладке боевых единиц.
Roy Mustang, с помощью редактора путей можно ли изменить тип проходимости, который устанавливается другими объектами? Например, сделать проходимой территорию рядом со зданием, деревом или клифом; сделать билдэйбл текстуру камней и т.д.
» WarCraft 3 / [lua] Фрэймы
Ред. scopterectus
» WarCraft 3 / [Lua] Реализация волн крипов
» WarCraft 3 / [Lua] Реализация волн крипов
» WarCraft 3 / [Lua] Реализация волн крипов
» WarCraft 3 / Бета версия Warcraft III Reforged 1.32
» WarCraft 3 / Бета версия Warcraft III Reforged 1.32
» WarCraft 3 / Бета версия Warcraft III Reforged 1.32
Ред. scopterectus
» WarCraft 3 / Симуляция бродячих Мобов.
» WarCraft 3 / BlzUnitHideAbility
Ред. scopterectus
» WarCraft 3 / Reforged на LUA
» WarCraft 3 / Reforged на LUA
Ред. scopterectus
» WarCraft 3 / Reforged на LUA
» WarCraft 3 / Reforged на LUA
» WarCraft 3 / Reforged на LUA
В TriggerAdAction уберите еще скобки у второго аргумента.
Ред. scopterectus
» WarCraft 3 / Характеристики призывных юнитов.
» WarCraft 3 / Как сделать, чтобы юнит взрывался когда у него остаётся 1 хп?
Ред. scopterectus
» WarCraft 3 / Бета версия Warcraft III Reforged 1.32
» WarCraft 3 / Бета версия Warcraft III Reforged 1.32
» WarCraft 3 / Бета версия Warcraft III Reforged 1.32
» Мир безумного / BlizzCon - 2019 (анонсы)
» WarCraft 3 / Бета версия Warcraft III Reforged 1.32
Ред. scopterectus
» WarCraft 3 / Аринас 1x1
» WarCraft 3 / lua таблица в таблице
» WarCraft 3 / Способность Weaver Geminate Attack !
» WarCraft 3 / [lua] Фрэймы