Как Warcraft 3 помог мне изучить парочку ЯП’ов

в 23:04, , рубрики: Delphi, game development, JASS, warcraft 3, Программирование, метки: , , ,

Прочитав статейку я решил рассказать о том, как же Warcraft помог мне. Я уже достаточно долго занимаюсь картостроением для игры Warcraft 3. Для многих наверное секрет, но компания Blizzard, выпустившая игру, дала пользователям достаточно мощный редактор карт с интерпретируемым языком программирования, который они назвали JASS (подробнее на вики). . «Редактор мира» от Близзард не давал покоя «буржуйским» картостроителям, и они выпустили его хак-версию, которая была названа JNGP (Jass New Gen Pack): хак подгружал разные библиотеки и добавлял разные вкусности — подсветку кода, отключение лимита объектов на карте, парсеры (о них ниже).

Native WorldEditor

Как Warcraft 3 помог мне изучить парочку ЯПов
JNGP

Как Warcraft 3 помог мне изучить парочку ЯПов

Этот язык имеет достаточно многословный синтаксис, а так же событийно-ориентированную структуру, поэтому два небезызвестных (в мире картостроя) человека создали препоцессоры языка, о которых так же написано в вики. Первый — vJass (v — от ника создателя — Vexorian) добавил в JASS ооп'шности и все вытекающие вкусности (инкапсуляция и т.д.). После русский программер ADOLF выпустил свой парсер, который создал новый диалект — cJass, парсер написал на MASM'е. Он делал синтаксис JASS'a похожим на классический Си, а так же добавлял препроцессорную обработку (перечисления, макросы, подключение внешних скриптов, обработчик for, оператор инкремента, декремента, сокращенные вычисления (+=, -=, /=, *=) и многое другое). Пример ниже:

Code

function test takes nothing returns integer
    local integer i = 0
    set i = i + 1
    return i
    //Simple function on "pure JASS"
endfunction
int test()
{
    int i = 0;
    return ++i;
    //Same on cJass
}

К сожалению, проект был заброшен, а знающих ассемблер в круге картостроителей очень мало. Но не о том речь.
Однажды мне в голову пришла мысль сочинить свой парсер. Так как я пркатически не смыслю в программировании, выбор мой пал на Delphi. Дав парсеру тривиальное название (JASP — Just Another Script Preprocessor) я сел сочинять плюшки. Так на свет появились динамически-типизированные переменные (как в C#), единичное объявление глобальных переменных, уничтожение объектов и прочее, чего так не хватало в vJass & cJass. Сочинив первые две версии парсера, в которые я добавил все эти мелкие, но полезные плюшки (их можно изучить в мануале), я понял, что надо совершенствоваться. Выбор мой пал на C#, и со сравнительно недавнего времени я переношу (а точнее уже перенес и допиливаю напильником) свежую версию 0.3, периодически отписываясь на тематических форумах и пополняя свои заметочки. В конечном итоге хочу сказать, что тот самый JASS, который мой отец называл баловством научил меня основам программирования и позволил углубиться из Делфи в Шарп, что не может меня не радовать. Думаю, что хватит, спасибо всем за ваше внимание.

Ах да, вот ради интереса заклинание на всех трех диалектах JASS'a в сумме:

scope FreezingShoot
{

    #define
    {
        <trigger gg_trg_Freezing_Shoot = null>;
        private isEnemy(t, u) = IsUnitEnemy(t, GetOwningPlayer(u)) && GetWidgetLife(u) > .405;
    }
    #include "cj_types_priv.j";
    
    private struct FS
    {
        unit caster;
        unit array dummy[3];
        static constant int count = 3;
        real dist = 0.;
        
        static void Timer()
        {
            var t = GetExpiredTimer();
            FS s = LoadInteger(hash, GetHandleId(t), 0);
            real x, y;
            if (s.dist <= 750.)
            {
                for (int i = 0; i < s.count; i++)
                {
                    var angle = GetUnitFacing(s.dummy[i]) * .0174532;
                    x = GetWidgetX(s.dummy[i]) + 25. * Cos(angle);
                    y = GetWidgetY(s.dummy[i]) + 25. * Sin(angle);
                    SetUnitPosition(s.dummy[i], x, y);
                    for (unit target; UnitsInRange(x, y, 80.) use temp)
                    {
                        if (isEnemy(s.caster, target))
                        {
                            UnitDamageTarget(s.caster, target, 100., true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS);
                            delete AddSpecialEffect("Abilities\Weapons\FrostWyrmMissile\FrostWyrmMissile.mdl", x, y);
                            DummycastToTarget(s.caster, target, 'A001', 852075);
                            RemoveDummy(s.dummy[i]);
                        }
                    }
                }
                s.dist += 25.;
            } elseif (s.dist > 750. || s.count <= 0) {
                for (int i = 0; i < s.count; i++)
                {
                    if (s.dummy[i] != null)
                    {
                        x = GetWidgetX(s.dummy[i]);
                        y = GetWidgetY(s.dummy[i]);
                        delete AddSpecialEffect("Abilities\Weapons\FrostWyrmMissile\FrostWyrmMissile.mdl", x, y);
                        RemoveDummy(s.dummy[i]);
                    }
                }
                FlushChildHashtable(hash, GetHandleId(t));
                PauseTimer(t);
                delete t, s;
            }
            flush t;
        }
        
        static void Init(unit caster, real tX, real tY)
        {
            new FS s, timer t;
            s.caster = GetTriggerUnit();
            var angle = Atan2(tY - GetWidgetY(s.caster), tX - GetWidgetX(s.caster)) * 57.295;
            var x = GetWidgetX(s.caster);
            var y = GetWidgetY(s.caster);
            for (int i = 0; i < s.count; i++)
            {
                s.dummy[i] = CreateDummy(GetOwningPlayer(caster), "Abilities\Weapons\ColdArrow\ColdArrowMissile.mdl", .7, x, y, 100., angle);
                angle -= 20.;
            }
            SaveInteger(hash, GetHandleId(t), 0, s);
            TimerStart(t, .04, true, function thistype.Timer);
            flush t;
        }
        
    }
    
    callback onUnitSpellEffect('A000')
    {
        FS.Init(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY());
    }

}

Автор: xxxTy3uKxxx

* - обязательные к заполнению поля