XGM Forum
Сайт - Статьи - Проекты - Ресурсы - Блоги

Форуме в режиме ТОЛЬКО ЧТЕНИЕ. Вы можете задать вопросы в Q/A на сайте, либо создать свой проект или ресурс.
Вернуться   XGM Forum > Warcraft> Академия: форум для вопросов> Jass
Ник
Пароль
Войти через VK в один клик
Сайт использует только имя.

Закрытая тема
 
adic3x

offline
Опыт: 108,439
Активность:
[Info] Оптимизация JASS кода
тут я хочу выложить несколько интересный мыслей по поводу производительности. хорошо, я пишу это в состоянии помутненного рассудка, так что делайте скидку на это. многие идеи носят параноидальный характер. итак, приступим

несоветую читать это новичкам в Jass, врядли это произведет позитивный еффект.

узкие места



сначала хочу рассказать о самом важном нюансе - в оптимизации нуждаются в первую очередь узкие места, т.е. те участки которые выполняются достаточно часто. к примеру поток которые совершает массу действий через короткие промежутки времени - достаточно узкое место. тригер, срабатывающий на событие, которое происходит часто тоже претендует на узкое место. а вот к примеру поток из main() быть узким не может никак. хорошо, это не значит что не нужно оптимизировать и их - как правило написание оптимального кода по времени и трудозатратам почти равны. итак, надеюсь вы поняли это, идем дальше.

тригеры



часто я вижу жуть которую делают чисто из следованию какой то глупой традиции.

пример жуткого кода

Код:
function Trig_x_Conditions takes nothing returns boolean
    if ( not ( GetSpellAbilityId() == 'A000' ) ) then
        return false
    endif
    return true
endfunction

function Trig_x_Actions takes nothing returns nothing
endfunction

//===========================================================================
function InitTrig_x takes nothing returns nothing
    set gg_trg_x = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_x, EVENT_PLAYER_UNIT_SPELL_CAST )
    call TriggerAddCondition( gg_trg_x, Condition( function Trig_x_Conditions ) )
    call TriggerAddAction( gg_trg_x, function Trig_x_Actions )
endfunction


немного лучше, но тоже кошмарно

Код:
function Trig_x_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A000'
endfunction

function Trig_x_Actions takes nothing returns nothing
endfunction

//===========================================================================
function InitTrig_x takes nothing returns nothing
    set gg_trg_x = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_x, EVENT_PLAYER_UNIT_SPELL_CAST )
    call TriggerAddCondition( gg_trg_x, Condition( function Trig_x_Conditions ) )
    call TriggerAddAction( gg_trg_x, function Trig_x_Actions )
endfunction


но в таком случае количество вызовов функции GetSpellAbilityId() = количеству таких спелов.

удобнее, короче и быстрее

Код:
function XxX takes nothing returns nothing
 local integer i=GetSpellAbilityId()


 // binary tree
 if i>.... then
  if i>.... then

  else

  endif
 else
  if i>.... then

  else

  endif
 endif
endfunction

function init takes nothing returns nothing
 local trigger t=CreateTrigger()

 call TriggerAddAction(t, function XxX)

 call TriggerRegisterPlayerUnitEvent(t, Player(0x00), EVENT_PLAYER_UNIT_SPELL_CAST, null)
 call TriggerRegisterPlayerUnitEvent(t, Player(0x01), EVENT_PLAYER_UNIT_SPELL_CAST, null)
 call TriggerRegisterPlayerUnitEvent(t, Player(0x02), EVENT_PLAYER_UNIT_SPELL_CAST, null)
 call TriggerRegisterPlayerUnitEvent(t, Player(0x03), EVENT_PLAYER_UNIT_SPELL_CAST, null)
 // --- >
 call TriggerRegisterPlayerUnitEvent(t, Player(0x0f), EVENT_PLAYER_UNIT_SPELL_CAST, null)
endfunction


в таком случае сразу растить дерево может быть нерациональным, тем более если количество спелов неизвестно заранее, легче сделать тупо линейнывй поиск, но потом переделать под дерево.

глобальное мышление



хорошо, предположим нам надо сделать 5 спелов. делаем первый, потом делаем второй, потом третий, который начинает конфликтовать с первым (ну к примеру пишет в UserData или использует какой либо отлов урона), исправляем первый и тогда перестает работать второй и приходится делать в нем кучу проверок... в общем понятно

хороший вариант - сначала сесть и продумать как что должно быть, примерно представить задачу, спроектировать код, определиться с системами и т.д. в результате избавляем себя от кучи гемора и делаем код более "чистым"

это действительно будет избавлять вас от очень большой рутины.

inline и ветвление



сейчас речь идет конечно не о vJass, а о том, что некоторые функции стоит делать внутри других. если функция которая вызывается очень часто и при этом занимает несколько строк есть смысл писать ее непосредсвенно. так же помните, что чем больше аргументов тем больше вызов функции сам по себе.

отлично. линейный код всегда будет быстрее. помните это когда пытаетесь сделать цикл который сработает 4 раза или что то вроде. хотя это скорее просто совет, т.к. размер нашего кода нас тоже немного но волнует.

помните также что несколько if (дерево) и один call или даже инлайн будет быстрее TriggerExecute/Evaluate или ExecuteFunc.

http://xgm.guru/forum/showthread.php?t=17923 - читайте также эту тему, там есть интересные вариант ветвления.

пусть умрет BJ



нивкоем случае не используйте BJ функции. в любом случае вы теряете временя как минимум на вызове. более того большинство BJ могут либо создавать утечки, либо вообще работать достаточно коряво. В TESH и JassCraft можно совершенно легко увидеть "начинку" любой функции.

я считаю их использование просто признаком плохого тона.

также наследием BJ являеться вариант кода, когда bool для получения его же самого сравнивают с другим bool

Код:
if IsUnitType(u, ut)=true then


это не нужно!

также не используйте

Код:
OrderId("stop") // etc.


используйте программу ConvOrder и указывайте сразу значение

утечки



хорошо, утечки в коде можно разделить на две категории: неудаленные обьекты и неудаленные ссылки (первое куда страшнее, хотя в некоторых случаях второе тоже не хорошо)

неудаленный обьект - к примеру мы использовали констуркцию

Код:
local location l=GetSpellTargetLoc()


после этого мы обязанны удалить эту локацию, иначе память отведенная под нее не освободиться и она будет все время "висеть". более того неудаленный юнит будет грузить движок - и как результат лаги.

однако, иногда есть смысл создавать обьекты только один раз, и не удалять их, а использовать повторно и не создавать новых (это быстрее) - яркий пример организации dummy cast из CasterSystem векса.

обнуление некоторых переменных - тоже очень важно (особенно если вы запоминаете что то через RB и массивы - рост хендлов будет абузить это) - но ради всего не надо обнулять int, real, string, bool, code! обнуляются локальные переменны типа handle и его наследники, и то только те, которые не высвабаждают хендл без обнуления (texttag и player к примеру обнулять нестоит)

просто war не считает хендл занятым пока на него существуют ссылки (он находится в переменной), а локальная переменная - висячая ссылка.

не используйте кешь



кеш намного медленнее массивов, по моим расчетам примерно в 5 раз. более того он делает код нечитабельным. и отучает думать. многие задачи решаются куда более елегантно без него (если приложить немного мысли).

опастность кеша в том что к примеру если записать хендл юнита, потом через некоторое время попытаться его извлечь - мы можем получить совершенно другой обьект (т.к. юнит может быть удален и ссылки на него потерты, в результате чего war посчитает что этот хендл можно присвоить другому обьекту)

передача аргументов в другую функцию также легко делается через один на карту набор глобальных переменных, главное следить за потоками.

таже если массив может быть заменен прост переменной отлично! так и стоит поступить.

цикл по группам



ужастно:

Код:
local group gr=CreateGroup()
 local unit u
 call GroupEnumUnitsInRange(gr, x, y, r, null)
 loop
  set u=FirstOfGroup(gr)
  exitwhen u=null
  // action
 endloop
 call DestroyGroup()
 set gr=null


не могу видеть это больше, типичный пример старомодного кода.

оптимизированный вариант:

Код:
globals
 group gr_temp=CreateGroup()
 boolexpr ex=null
endglobals

function xXx takes nothing returns boolean
 // action
 return false
endfunction

 // --->
 call GroupEnumUnitsInRange(gr_temp, x, y, r, ex)
 // <---

function init takes nothing returns nothin
 set ex=Condition(function xXx)
endfunction


отлично! аргументы могут быть спокойно переданы через глобальные переменные. это большое подспорье если надо к примеру сделать какое либо одноразовое действие на юнитами в радиусе и т.д.

хорошо, но не забывайте о ForGroup, если условие будет истинным и юнит будет добалвен в группу - самое просто перебирать группу именно через эту функцию.

числа



возможны вы привыкли к вашей "умной" среде разработки, которая set i=1+1 превращает в set i=2 в момент компиляции. но тут такое не пройдет. старайтесь использовать сразу вычисленные числа.

меня всегда корежило от кода типа

Код:
local real f=GetRandomReal(0, 360)*bj_DEGTORAD


правильно:

Код:
local real f=GetRandomReal(0., 6.283185)


помните, что некоторые native принимают значение в градусах (CreateUnit, SetUnitFacing) а некоторые в радианах (Cos, Sin) так что стоит думать какое значение вам нужно в данном случае. тем более SetUnitFacing(u, 360.) и SetUnitFacing(u, 720.) будут работать одинаково.

также старайтесь использовать максимально простые формулы (к примеру если мы считаем длину вектора не стоит из суммы квадратов его протекций на координатные оси извлекать квадратный корень и сравнивать его с какой либо длиной, куда удобней возвести длину в квадрат у сравнивать уже с просчитанным заранее квадратом)

http://xgm.guru/articles.php?section=wc3&name=about_int - хе-хе, я так же рекомендую читать эту статью для поиска идей по оптимизации.

черт, о чем я хотел еще рассказать?

таймеры, разделение потоков



хорошо, как правило для отсроченных действий принято использовать таймеры. ранее я видел кучу кодов, где на хендл таймера через кешь вешалось все что только не жалко. это неправильно. панацеей может быть использование struct из vJass (или их аналога, лично я пишу их сам, хм, иногда мне казалось по коментариям некоторых товарищей что до vJass массивов вообще небыло)

в таком случае надо только приаттачить структуру и все, это и быстрее и намного удобнее. примерно так

Код:
struct adx
 // --->
 // members
endstruct

//
 set t=CreateTimer()
 set a=adx.create()
 call *attach*(t, a)
//


также события с малым периодом (движение частиц) могут обрабатываться одним таймеро в циклах, ссылку на тему с обсуждением я уже выкладывал.

теперь, если ваш поток вызывает лаги и имеется такая возможность - сделайте глобальный таймер, который скажем будет срабатывать каждые .5 сек и делать что либо. это может быть полезно при создание большого количества обьектов, загрузке чего либо либо просто для разругзке движка.

к примеру так

Код:
function A takes nothing return nothing
 // action
 call TimerStart(GetExpiredTimer(), .5, false, function B)
endfunction

 // --->
  call TimerStart(CreateTimer(), .5, false, function A)
 // <---


и т.д. либо зациклить его на одну функцию и передавать значение через какой либо счетчик сработок.

еще немного о переменных



все переменные - DWORD (используйте RB во благо). локальные переменные несколько быстрее глобальных. массивы медленне обычных переменных. на обявление локальных переменных тоже нужно время.

Код:
local unit u=GetTriggerUnit()
 // ---> ---> --->
 set u=null


медленне чем

Код:
call GetTriggerUnit()
 call GetTriggerUnit()


т.е. если вам надо вызвать какую либо функцию несколько раз, крепко подумайте, стоит ли ее результат заносить в локальную переменную.

есть еще интересный нюанс - можно использовать метку аргумента как переменную, т.е. если мы передаем в функцию юнита, используем его а потом он нам не нужен - мы можем поместить в эту переменную другого юнита, это может быть короче, удобнее и быстрее (т.е. не надо инициализировать лишнюю локальную переменную) более того такие переменные не нуждаются в обнулении.

эффекты



это очень важный, хоть и косвенны вопрос. часто для визуальных эффектов (к примеру делается какой либо красивый нестандартный спелл) используются юниты в качестве эффектов. это вполне оправданно если эффект должен двигаться по какой либо нетривиальной траектории, либо к примеру отскакивать от поверхности рельефа. но если есть возможность сделать импортированную модель и использовать конструкцию типа DestroyEffect(AddSpecialEffect(...)) то это то, что я бы рекомендовал место создания кучи юнитов. опять же это более простой и удобный вариант.

StopWatch native



входящие в состав NewGen WE пак нативов большое подспорье в проверках на скорость алгоритма. если вы нуждаетесь в сравнение - вы обязанны использовать это (благо последнии версии стабильно запускаются).

достаточно исчерпывающую информацию можно найти в ReadMe.txt (правда я правил ongameload.lua).

в завершении



хорошо, я писал тут о достаточно простых и порой очевидных вещах (если я что то придумаю еще или я просто что то забыл я добавлю это после). по хорошему вы должны при написании каждой строки кода спрашивать себя - "как я могу сделать это лучше?". хорошо, и я хочу закончить этот труд фразой взятой из другого не моего и не жассового тутора по оптимизации:

Цитата:
Уфф, я больше не знаю, что вам посоветовать. Ммм, пpочитайте это снова xD

Отредактировано ADOLF, 15.08.2008 в 13:13.
Старый 13.08.2008, 11:51
alexkill

offline
Опыт: 18,872
Активность:
Цитата:
удобнее, короче и быстре ...в таком случае сразу растить дерево может быть нерациональным, тем более если количество спелов неизвестно заранее, легче сделать тупо линейнывй поиск, но потом переделать под дерево.


Ты бы хоть описал, что должно быть в условии (я так понял, 256-ричное представление)

Код:
if i>'A000' then
if i>'A022' then


Цитата:
нивкоем случае не используйте BJ функции

Единственное, с чем соглашусь бесспорно.

Цитата:
кеш намного медленнее массивов, по моим расчетам примерно в 5 раз. более того он делает код нечитабельным. и отучает думать. многие задачи решаются куда более елегантно без него (если приложить немного мысли).

Я тебе объясню, почему новички (и не только) используют его: в статьях о Jass, на которые очень часто и с душой даются ссылки, очень детально расписан процесс перехода с глобальных на локальные переменные. Посему использование глобалок, как думают многие пользователи, отходит на второй план и подсознательно ищутся способы обойти их (кеш, строки и т.п.). Как-то нужно смягчить резкость.

Цитата:

цикл по группам


ужастно:не могу видеть это больше, типичный пример старомодного кода.


Использование старых (проверенных временем) способов - не всегда пример дурного тона. Пример с группами не исключение.


Цитата:
как правило для отсроченных действий принято использовать таймеры. ранее я видел кучу кодов, где на хендл таймера через кешь вешалось все что только не жалко. это неправильно.


Ты и сейчас это увидишь... =)

Цитата:
панацеей может быть использование struct из vJass


теперь соглашусь, месяц назад бы поспорил
_________
P.S. В целом статья - больше излияние души, как показалось. Понять в принципе можно.
Старый 13.08.2008, 12:22
J
expert
offline
Опыт: 48,447
Активность:
мало полезной информации
  • узкие места
  • тригеры (та половина которая про условия)
  • пусть умрет BJ
  • утечки
  • не используйте кешь
- описывается в других статьях, в некоторых даже подробнее
  • цикл по группам
  • таймеры, разделение потоков
  • еще немного о переменных
- ну норм что упомянул
  • числа
- ты вродебы увидел что я так делал и решил сказать, но я это понимаю и сделал так потомучто единственый раз когда я так сказал это код зевса который выполнялся 1 раз в секунду, и чтобы ему самому было понятно т.к. он новичек

Цитата:
Сообщение от ADOLF
опастность кеша в том что к примеру если записать хендл юнита, потом через некоторое время попытаться его извлечь - мы можем получить совершенно другой обьект (т.к. юнит может быть удален и ссылки на него потерты, в результате чего war посчитает что этот хендл можно присвоить другому обьекту)
неверно, я вроде тебе уже говорил об этом, это не опасность кеша, это всеволиш один из багов по роботе с ним возникающий при криворукости и неправельном обнулении, подобных проблем можно достич и на массивах

все остальное вода...

Отредактировано Jon, 13.08.2008 в 17:16.
Старый 13.08.2008, 14:54
kvaDrug

offline
Опыт: 1,601
Активность:
Все было очень полезно для меня. Автор - хороший человек)
передача аргументов в другую функцию также легко делается через один на карту набор глобальных переменных, главное следить за потоками.
плз, ссылку на статью о потоках, можно англ.
SetUnitFacing - а, простите за тупой вопрос, между чем это угол.
Старый 13.08.2008, 16:48
akkolt

offline
Опыт: 13,826
Активность:
kvaDrug, что задашь, то и будет. А так это просто функция, пока не принявшая никаких данных.
Старый 13.08.2008, 17:05
ShadoW DaemoN

offline
Опыт: 37,078
Активность:
Насчет триггеров - хмм, можно же сделать что-то вроде:
Код:
function Run takes nothing returns nothing
  local integer i = GetSpellAbilityId()
  if i >= 'A000' and i <= 'A100' then
    call ExecuteFunc("spl" + I2S(i))
  endif
endfunction

и, имхо вместо действия можно поставить условие:
Код:
function init takes nothing returns nothing
  local trigger t=CreateTrigger()
  // call TriggerAddAction(t, function Run)
  call TriggerAddCondition(t, Condition(function Run))
  // . . .
endfunction


Насчет if IsUnitType(u, ut)==true then - хмм, буржуи советуют писать проверку для конкретно этой функции, ибо она может косячить, для всех остальных случаев можно без проверки.

Заключение классное)
set i = i + 1
Как я могу сделать это лучше?
Старый 13.08.2008, 17:24
kvaDrug

offline
Опыт: 1,601
Активность:
akkolt, явно не джазз панда) Абсолютно в теме.
Я вообщето хотел узнать больше о потоках. Глянул структуры - суровая штука, иду использовать.
Старый 13.08.2008, 17:40
adic3x

offline
Опыт: 108,439
Активность:
Jon, нет, это небыло попыткой как либо обозначить что это делал именно ты, я видел это и раньше, когда к примеру при движении юнита по поляркам сохраняли именно в градусах угол и потом каждый раз умножали на константу

Цитата:
- описывается в других статьях, в некоторых даже подробнее


да, мб, хотя имхо и немного с других сторон, в любом случае я решил написать все в одной

Цитата:
ссылку на статью о потоках


невидел никогда в природе

Цитата:
это не опасность кеша, это всеволиш один из багов по роботе с ним возникающий при криворукости и неправельном обнулении


ну я поробно описал как и почему оно бывает, в любом случае кешь медленее, а статья о том как сделать код быстрее

Цитата:
Пример с группами не исключение


то что пишу я труевее у одбней

Цитата:
Ты бы хоть описал, что должно быть в условии (я так понял, 256-ричное представление)


я думаю это и так ясно

Цитата:
call ExecuteFunc("spl" + I2S(i))


это дольше

ADOLF добавил:
я также добавил один подпункт)
Старый 13.08.2008, 18:28
Sebra

offline
Опыт: 5,603
Активность:
Цитата:
if IsUnitType(u, ut)=true then

Кажется, конкретно эта функция не всегда возвращает корректный boolean.
Помнится я про это у Векса читал.
Вызывает проблемы в фильтрах.

Цитата:
call GetTriggerUnit()

Если не ошибаюсь, это единственная быстрая функция.
Потери на запуск любой другой функции превысят потери на локальную переменную.

Цитата:
Ты бы хоть описал, что должно быть в условии

Типа так:
Код:
if i>'A050' then
    if i>'A080' then
        if i>'A085' then
            // action
        else
            // action
        endif
    else
        if i>'A070' then
            // action
        else
            // action
        endif
    endif
else
    if i>'A020' then
        if i>'A030' then
            // action
        else
            // action
        endif
    else
        if i>'A010' then
            // action
        else
            // action
        endif
    endif
endif

8 абил 3 сравнения.
Старый 13.08.2008, 19:56
PlayerDark
Coraline
offline
Опыт: 10,569
Активность:
ADOLF Такой стиль кода быстрее но 1) его понятность уменьшается во много раз. 2) На современных компьютерах потеря скорости при обращении к кешу а так же как и неоптимизированные функции работают все равно довольно быстро. Единственное чего надо избегать это утечек памяти.

PlayerDark добавил:
Да, и редактирование такого кода тоже более трдоемкая задача.
Старый 14.08.2008, 13:30
adic3x

offline
Опыт: 108,439
Активность:
Цитата:
Кажется, конкретно эта функция не всегда возвращает корректный boolean.
Помнится я про это у Векса читал.
Вызывает проблемы в фильтрах.


в варе булы как и все типы 32, в них 0 фалсе, остальное все тру, а специально через РБ возращал все значения в виде була (И2Б(64)) к примеру в фильтры - все работало нормально

Цитата:
Если не ошибаюсь, это единственная быстрая функция.

стопВач нативе в руки и тестить)

Цитата:
Такой стиль кода быстрее но 1) его понятность уменьшается во много раз

понятность кода определяется именованием меток понятным и удобным (функций и переменных) а так же толково раставленными коментариями

Цитата:
2) На современных компьютерах потеря скорости при обращении к кешу а так же как и неоптимизированные функции работают все равно довольно быстро

в любом случае вы рано или поздно достигаете рубежа "производительности"

Цитата:
Да, и редактирование такого кода тоже более трдоемкая задача

чем же? если в коде предусмотренно что бы он мог быть расширенным - то это делается так же просто
Старый 14.08.2008, 15:27
Sebra

offline
Опыт: 5,603
Активность:
Цитата:
в варе булы как и все типы 32, в них 0 фалсе, остальное все тру, а специально через РБ возращал все значения в виде була (И2Б(64)) к примеру в фильтры - все работало нормально
Попробую найти, где это у Векса...

Цитата:
стопВач нативе в руки и тестить)
Увы, у меня 1.22. Вот как сделают JNGP новый, так сразу.
Старый 14.08.2008, 19:40
Sunn
To feel joy, not be blue
offline
Опыт: 4,975
Активность:
На известном забугорном сайте прочитал, что следует вместо локаций использовать координаты. Да и тут видел нечто подобное. Насколько это умный совет? Ведь всеравно, чтоб указать точку при помощи координат, мы пишем Location(x, y), а ф-я Location() возвращает location. Тоесть работаем со все той же location, но чтоб ее получить надо запомнить 2 значения и вызвать функцию. Вместо того чтоб просто передать/запомнить location. А если с нашими координатами работает несколько функций? Тогда количество действий увеличивается кратно количеству этих самых функций... обьясните, что и как, плиз.
Старый 28.08.2008, 14:36
Лось

offline
Опыт: 7,223
Активность:
Мы не используем ф-ию Location, когда работаем с координатами, зачем нам указывать точку, если можно указать X и Y?
Тока если узнать высоту рельефа ( ф-ия GetLocationZ(location loc) ), а так все нативки работают с координатами.
Старый 28.08.2008, 15:07
adic3x

offline
Опыт: 108,439
Активность:
Цитата:
Насколько это умный совет?

действительно это так !!! надо написать о правильном ветвление и о координатах если тут этого нету !!!

Цитата:
Ведь всеравно, чтоб указать точку при помощи координат, мы пишем Location(x, y), а ф-я Location() возвращает location. Тоесть работаем со все той же location, но чтоб ее получить надо запомнить 2 значения и вызвать функцию.


...

для всех функций берущих или возращающих локации есть аналоги которые используют две риала место лока (кроме высоты терайна и точки каста)

CreateUnit(player, integer, realX, realY, real)
CreateUnitAtLoc(player, integer, location, real)

прототипы такого рода функций

почему лушче координаты?!

1) не надо создавать обьект
1.1) ненадо удалять обьект
1.2) ненадо обнулять
2) для их изменения мы не вызываем функцию, а просто меняем значение в переменной (что быстрее намного)
2.1) для получения координаты по одной из осей не надо вызывать функцию, можно обратиться напрямую
2.2) любое действие с локацией будет медленне чем с координатами, поскольку идет вызов функции
3) локации не разработанны под 3д, системы на координатах можно заточить под что угодно
3.1) локации и векторы это уже бред, а вот координаты прикрасно с ними вяжуться, по сути если вы используете ооп подход как на вЖасс (я пишу тоже на "лоу левел") то вы можете разработать такую систему, которая будет максимально удобной
Старый 28.08.2008, 15:08
Sunn
To feel joy, not be blue
offline
Опыт: 4,975
Активность:
Лось, как не используя функцию Location можно написать следуйщее:
Код:
call AddSpecialEffectLocBJ( Location(x, y), "Abilities\\Spells\\Human\\ThunderClap\\ThunderClapCaster.mdl" )
?
И еще, где можно почитать про struct тому, кто впервые это видит в жассе?
Старый 28.08.2008, 15:15
Лось

offline
Опыт: 7,223
Активность:
Код:
AddSpecialEffect("Abilities\\Spells\\Human\\ThunderClap\\ThunderClapCaster.mdl",x,y)

Xenosapien, common.j тебе в руки...
Старый 28.08.2008, 15:18
adic3x

offline
Опыт: 108,439
Активность:
скачайте ген пак - он подсвечивает бж сразу красным - будет вам счастье)))
Старый 28.08.2008, 15:26
Sunn
To feel joy, not be blue
offline
Опыт: 4,975
Активность:
Оу, спасибо Лось. Хотя конечно справочник по наптивам в *тхт это злоба дня.
Старый 28.08.2008, 15:27
adic3x

offline
Опыт: 108,439
Активность:
Цитата:
И еще, где можно почитать про struct тому, кто впервые это видит в жассе?

есть ридми в самом паке на буржуйсок наречии и есть перевод в этом разделе...
Старый 28.08.2008, 15:28
Закрытая тема

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск

Ваши права в разделе
Вы не можете создавать темы
Вы не можете отвечать на сообщения
Вы не можете прикреплять файлы
Вы можете скачивать файлы

BB-коды Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход



Часовой пояс GMT +3, время: 10:44.