Final Fantasy IX: Помощь открытому проекту за вознаграждение

Предложения о постоянной работе и разовых заказах.

Final Fantasy IX: Помощь открытому проекту за вознаграждение

Сообщение Albeoris 14 дек 2017, 04:22

Доброго времени суток!

В лохматые годы на консоли PS1 вышла игра Final Fantasy IX.
Полтора года назад её портировалина PC.
Год назад я закончил разработку альтернативной версии движка (Assembly-CSharp.dll).
А месяц назад мы выбрались на NexusMods.

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

Количество запросов на новые возможности постепенно растёт. А занимаются разработкой полтора человека.

Тем не менее, удалось достичь многого: добавить поддержку FullHD (Hor+), вынести значительную часть ресурсов во внешние файлы, пользуясь этим собрать команду и запилить перевод на русский язык. На подходе поддержка кастомных задников, множество мелких улучшений в геймплее и т.д.

Но ещё больше хочется сделать. А качество кода ОЧЕНЬ низкое. Пожалуй, это худший код, который я когда-либо видел в жизни.
Занималась портированием на Unity тайваньская студия. И местами можно заметить ничем не прикрытые куски кода, откровенно выдернутые из PS1-ассёмблера и наспех сконвертированные в C#. Очень высокая связность отдельных частей и полное отсутствие таковой в иных. Сам игровой движок использовался исключительно для компиляции игры под различные платформы - в редакторе отображается только UI (отображался бы, так как исходные Unity проект ещё не восстановили, да и на текущем этапе это не имеет особого смысла).

Покажу пример кода, просто чтобы вы знали - с чем предстоит работать.
Синтаксис:
Используется csharp
        protected override void OnAnchor()
        {
                Transform cachedTransform = base.cachedTransform;
                Transform parent = cachedTransform.parent;
                Vector3 localPosition = cachedTransform.localPosition;
                Vector2 pivotOffset = this.pivotOffset;
                Single num;
                Single num2;
                Single num3;
                Single num4;
                if (this.leftAnchor.target == this.bottomAnchor.target && this.leftAnchor.target == this.rightAnchor.target && this.leftAnchor.target == this.topAnchor.target)
                {
                        Vector3[] sides = this.leftAnchor.GetSides(parent);
                        if (sides != null)
                        {
                                num = NGUIMath.Lerp(sides[0].x, sides[2].x, this.leftAnchor.relative) + (Single)this.leftAnchor.absolute;
                                num2 = NGUIMath.Lerp(sides[0].x, sides[2].x, this.rightAnchor.relative) + (Single)this.rightAnchor.absolute;
                                num3 = NGUIMath.Lerp(sides[3].y, sides[1].y, this.bottomAnchor.relative) + (Single)this.bottomAnchor.absolute;
                                num4 = NGUIMath.Lerp(sides[3].y, sides[1].y, this.topAnchor.relative) + (Single)this.topAnchor.absolute;
                                this.mIsInFront = true;
                        }
                        else
                        {
                                Vector3 localPos = base.GetLocalPos(this.leftAnchor, parent);
                                num = localPos.x + (Single)this.leftAnchor.absolute;
                                num3 = localPos.y + (Single)this.bottomAnchor.absolute;
                                num2 = localPos.x + (Single)this.rightAnchor.absolute;
                                num4 = localPos.y + (Single)this.topAnchor.absolute;
                                this.mIsInFront = (!this.hideIfOffScreen || localPos.z >= 0f);
                        }
                }
                else
                {
                        this.mIsInFront = true;
                        if (this.leftAnchor.target)
                        {
                                Vector3[] sides2 = this.leftAnchor.GetSides(parent);
                                if (sides2 != null)
                                {
                                        num = NGUIMath.Lerp(sides2[0].x, sides2[2].x, this.leftAnchor.relative) + (Single)this.leftAnchor.absolute;
                                }
                                else
                                {
                                        num = base.GetLocalPos(this.leftAnchor, parent).x + (Single)this.leftAnchor.absolute;
                                }
                        }
                        else
                        {
                                num = localPosition.x - pivotOffset.x * (Single)this.mWidth;
                        }
                        if (this.rightAnchor.target)
                        {
                                Vector3[] sides3 = this.rightAnchor.GetSides(parent);
                                if (sides3 != null)
                                {
                                        num2 = NGUIMath.Lerp(sides3[0].x, sides3[2].x, this.rightAnchor.relative) + (Single)this.rightAnchor.absolute;
                                }
                                else
                                {
                                        num2 = base.GetLocalPos(this.rightAnchor, parent).x + (Single)this.rightAnchor.absolute;
                                }
                        }
                        else
                        {
                                num2 = localPosition.x - pivotOffset.x * (Single)this.mWidth + (Single)this.mWidth;
                        }
                        if (this.bottomAnchor.target)
                        {
                                Vector3[] sides4 = this.bottomAnchor.GetSides(parent);
                                if (sides4 != null)
                                {
                                        num3 = NGUIMath.Lerp(sides4[3].y, sides4[1].y, this.bottomAnchor.relative) + (Single)this.bottomAnchor.absolute;
                                }
                                else
                                {
                                        num3 = base.GetLocalPos(this.bottomAnchor, parent).y + (Single)this.bottomAnchor.absolute;
                                }
                        }
                        else
                        {
                                num3 = localPosition.y - pivotOffset.y * (Single)this.mHeight;
                        }
                        if (this.topAnchor.target)
                        {
                                Vector3[] sides5 = this.topAnchor.GetSides(parent);
                                if (sides5 != null)
                                {
                                        num4 = NGUIMath.Lerp(sides5[3].y, sides5[1].y, this.topAnchor.relative) + (Single)this.topAnchor.absolute;
                                }
                                else
                                {
                                        num4 = base.GetLocalPos(this.topAnchor, parent).y + (Single)this.topAnchor.absolute;
                                }
                        }
                        else
                        {
                                num4 = localPosition.y - pivotOffset.y * (Single)this.mHeight + (Single)this.mHeight;
                        }
                }
                Vector3 vector = new Vector3(Mathf.Lerp(num, num2, pivotOffset.x), Mathf.Lerp(num3, num4, pivotOffset.y), localPosition.z);
                vector.x = Mathf.Round(vector.x);
                vector.y = Mathf.Round(vector.y);
                Int32 num5 = Mathf.FloorToInt(num2 - num + 0.5f);
                Int32 num6 = Mathf.FloorToInt(num4 - num3 + 0.5f);
                if (this.keepAspectRatio != UIWidget.AspectRatioSource.Free && this.aspectRatio != 0f)
                {
                        if (this.keepAspectRatio == UIWidget.AspectRatioSource.BasedOnHeight)
                        {
                                num5 = Mathf.RoundToInt((Single)num6 * this.aspectRatio);
                        }
                        else
                        {
                                num6 = Mathf.RoundToInt((Single)num5 / this.aspectRatio);
                        }
                }
                if (num5 < this.minWidth)
                {
                        num5 = this.minWidth;
                }
                if (num6 < this.minHeight)
                {
                        num6 = this.minHeight;
                }
                if (Vector3.SqrMagnitude(localPosition - vector) > 0.001f)
                {
                        base.cachedTransform.localPosition = vector;
                        if (this.mIsInFront)
                        {
                                this.mChanged = true;
                        }
                }
                if (this.mWidth != num5 || this.mHeight != num6)
                {
                        this.mWidth = num5;
                        this.mHeight = num6;
                        if (this.mIsInFront)
                        {
                                this.mChanged = true;
                        }
                        if (this.autoResizeBoxCollider)
                        {
                                this.ResizeCollider();
                        }
                }
        }


И другой:
Синтаксис:
Используется csharp
    public Int32 ProcessCode(ObjList objList)
    {
        Int32 result = -1;
        _objectExists = true;
        s0 = objList;
        next1();
        while (_objectExists)
        {
            s1 = s0.obj;
            _a1 = s1.state;
            if (_a1 == EventEngine.stateNew)
            {
                _a0 = EventEngine.stateInit;
                s1.state = (Byte)_a0;
                next0();
                continue;
            }

            _a0 = EventEngine.stateSuspend;
            _s2 = 0;
            if (_a1 == _a0)
            {
                next0();
                continue;
            }

            _nextCodeIndex = s1.ip;
            _a0 = s1.wait;
            if (_nextCodeIndex == _eventEngine.nil)
            {
                next0();
                continue;
            }

            _a2 = 1;
            if (_a0 != 0)
            {
                _a1 = 255;
                if (_a0 != 254)
                {
                    if (_a0 == _a1)
                    {
                        next0();
                    }
                    else
                    {
                        _a0 = s1.wait;
                        _a0--;
                        s1.wait = (Byte)_a0;
                        next0();
                    }
                }
                else
                {
                    _a0 = s1.winnum;
                    if (_a0 == 255)
                    {
                        ad4();
                    }
                    else
                    {
                        Boolean flag = _eTb.MesWinActive(_a0);
                        _a0 = 255;
                        if (flag)
                        {
                            next0();
                        }
                        else
                        {
                            s1.winnum = (Byte)_a0;
                            ad4();
                        }
                    }
                }
                continue;
            }

            _a1 = s1.vofs;
            _eventEngine.gExec = s1;
            _a1 <<= 2;
            _a0 = s1.cid;
            _instance = s1.buffer;
            _instanceVOfs = _a1;
            objV0 = s1;
            _v0 = s1.ip;
            if (_a0 != _a2)
            {
                result = ad3(_a0);
            }
            else
            {
                _a0 = s1.uid;
                _a0 -= 64;
                objV0 = _eventEngine.FindObjByUID(_a0);
                result = ad3(_a0);
            }
            objV0 = null;
        }
        return result;
    }


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

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

Чем заниматься, выбираете вы. Это может быть одна из ишуй с гитхаба. Может быть одна из вяло текущих задач (программная генерация GUI, импорт кастомных моделей, поддержка альтернативного облачного хранилища сейвов, совместимость с другими утилитами [Hades Workshop], GUI для редактирвоания настроек, да хоть документация на английском языке). Согласовали задачу - и вперёд.

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

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

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

Аванс. Он возможен при частично выполненной работе и в скромном объеме (15-20% от полной стоимости). Например, вы нашли причину какой-либо проблемы и готовы приступить к её устранению. Вы спроектировали удобный паблик API нового GUI-фреймворка, без его реализации. Вы нашли проблемный шейдер, который приводит к артефактам в графических эффектах при полноэкранном режиме и готовы заняться его переписыванием. И т.д.

Требования к качеству кода - средние. Мне не сложно дорефакторить за вами несколько типов, убрать дублирование кода, улучшить читабельность, если вы решили сложную задачу. Но если ваш код сплетёт и без того запутанные исходники в тугой клубок, а вместо нормальной архитектуры будет write-only портянка на 1500 строк, я оставляю за собой право или отправить вас её перепиливать, или отказаться принимать такой коммит и, как следствие, либо не заплатить (если не за что), либо оплатить только ресёрч, уже основываясь на собственных представлениях о его значимости.

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

Вне зависимости от наличия или отсутствия ТЗ вам нужно будет регулярно выходить на связь и докладывать о результатах работы. Если вы работает по выходным и подписались сделать всё за две недели - в конце первой. Если готовы реализовать функционал за четыре дня - в конце каждого дня. В общем, в разумные промежутки времени, привязанные к вашим срокам и вашему графику. Вы можете задать вопросы, попросить совета, и главное - рассказать о проделанной работе, проведённом ресёрче, продемонстрировать какие-нибудь скриншоты, сигнатуры методов, если речь о новом публичном API, видео из игры с новыми возможностями, картинки из редакторов со внешними ресурсами и т.д. Это нужно прежде всего вам, чтобы вы делали именно то, за что я готов вам заплатить, а мы остались друзьями и продолжили сотрудничество.

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

Тестирование и объемы изменений. За ошибки в коде, которые нашёл я, будут штрафы, поэтому к качеству кода нужно отнестись очень серьёзно. Причина в том, что любую в подобном проекте (а я говорю прежде всего не об объеме, а о низком качестве кода) очень сложно искать и устранять. По этой же причине старайтесь избегать любых несогласованных рефакторингов, которые затрагивают десятки файлов. Мне придётся проверять все ваши изменения перед мержем, и если их будут сотни, а мы об этом не договаривались, я просто откажусь от такого коммита, так как вероятность того, что вы допустили ошибку, а я её не нашёл крайне велика.

Для начала, думаю, хватит. Надеюсь, последний желающий поучаствовать в этой вакханалии закрыл страничку ещё на середине. :)
Ну, а если кто-то всё же рискнёт посотрудничать, то пишите письма!

Телеграмм: @albeoris
Скайп: albeoris
Гмыло: albeoris

Скайп и телеграмм проверяю как минимум раз в день.

Ну, а если кто-нибудь из вас хочет просто безвозмездно помочь проекту, то добро пожаловать!
Делайте то, что вам нравится, засылайте пул реквесты с пояснениями, и наслаждайтесь каждой минутой убитого времени вместе с нами! :-bd
Albeoris
UNIт
 
Сообщения: 146
Зарегистрирован: 19 окт 2013, 13:12

Вернуться в Предложение Работы

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 5