1. Уважаемые гости и новички, приветствуем Вас на нашем форуме
    Здесь вы можете найти ответы практически на все свои вопросы о серии игр «Готика» (в том числе различных модах на нее), «Ведьмак», «Ризен», «Древние свитки», «Эра дракона» и о многих других играх. Можете также узнать свежие новости о разработке новых проектов, сыграть в увлекательные ФРПГ, восхититься творчеством наших форумчан, либо самим показать, что вы умеете. Ну и наконец, можете обсудить общие увлечения или просто весело пообщаться с посетителями «Таверны».

    Чтобы получить возможность писать на форуме, оставьте сообщение в этой теме.
    Удачи!
    Скрыть объявление
  2. Внимание!
    — Требуется примерно по 3-5 человек на каждую из версий ОС:: - Windows® XP SP3, Windows® Vista SP2, Windows® 7 SP1, Windows® 8, Windows® 8.1, Windows® 10(build 10 1607) и Windows® 10(build 10 1703). Для стационарных ПК и ноутбуков. Заявку на участие можно оставить здесь...
    Скрыть объявление

Туториал: Работа с фокусной надписью.

Тема в разделе "Уpоки AST", создана пользователем Jr13San, 13 янв 2018.

Статус темы:
Закрыта.
  1. Jr13San

    Jr13San Участник форума

    Регистрация:
    1 апр 2010
    Сообщения:
    461
    Благодарности:
    144
    Баллы:
    210
    Пол:
    Мужской
    Пример 1 - Изменение цвета текста

    1) Для начала определимся с тех. заданием. В качестве примера напишем обработчики для следующих объектов:
    • НПС
    - Если он входит в состав группы игрока, то подкрашиваем его имя зелёным цветом.
    - Если он находится в состоянии атаки на игрока, то окрашиваем его имя в красный цвет.
    - Если он мёртв и у него нечего взять, то делаем надпись полупрозрачной (чтобы не привлекал внимание).
    - В остальных случаях цвет фокусной надписи не меняется.​
    • Предмет
    - Если предмет является игровой валютой, то подкрашиваем надпись в жёлтый цвет.
    - В остальных случаях - используется цвет по умолчанию.​
    • Сундук
    - Если сундук пустой, то его название становится полупрозрачным (чтобы не привлекал внимание).
    - В остальных случаях цвет фокусной надписи не меняется.
    2) Открываем рабочий проект плагина для версии "003" в Visual Studio, и добавляем в раздел “Plugin main” новый файл, под названием “UpdateFocusColor.cpp”, как показано на рисунках:

    1.jpg 2.jpg

    3) Добавляем в файл следующий код:
    PHP:

    #include "AST.h"

    // Функция обработки цвета текста фокусной надписи
    // (изменяет цвет текста вьюпорта перед печатью, или не изменяет)
    void UpdateFocusColor()
    {
        //*****
        // НПС
        //*****

        // Пытаемся преобразовать указатель на объект в указатель на НПС
        oCNpc* pNpc = dynamic_cast<oCNpc*>(player->focus_vob);

        // Если преобразование в НПС прошло успешно
        if (pNpc)
        {
            // Если НПС входит в состав группы игрока
            if (pNpc->m_nAivar[15])
                // Устанавливаем зелёный цвет фокусной надписи
                screen->SetFontColor(zCOLOR(100,255,100));
     
            // Если ГГ является врагом НПС
            if (pNpc->enemy == player)
            {
                // Получаем название состояния в котором находится НПС
                zSTRING stateName = pNpc->state.curState.name;

                // Если это название состояния атаки для людей или животных
                if (!stateName.Compare("ZS_ATTACK") || !stateName.Compare("ZS_MM_ATTACK"))
                    // Устанавливаем красный цвет фокусной надписи
                    screen->SetFontColor(zCOLOR(255,60,60));
            }

            // Получаем указатель на инвентарь НПС
            oCNpcInventory* pInv = pNpc->GetInventory();

            // Если НПС мёртв и у него нечего взять
            if (pNpc->IsDead() && pInv && pInv->IsEmpty() && pInv->packString.IsEmpty())
                // Делаем цвет текста полупрозрачным
                screen->SetFontColor(zCOLOR(255,255,255,128));

            // Выходим из функции
            return;
        }

        //*********
        // ПРЕДМЕТ
        //*********

        // Пытаемся преобразовать указатель на объект в указатель на предмет
        oCItem* pItem = dynamic_cast<oCItem*>(player->focus_vob);
     
        // Если преобразование прошло успешно
        if (pItem)
        {
            // Выполняем поиск игровой валюты:
            // Ищем текстовую переменную в скриптах с названием "TRADE_CURRENCY_INSTANCE"
            zCPar_Symbol* ps = parser->GetSymbol("TRADE_CURRENCY_INSTANCE");

            // Если нашли
            if (ps)
            {
                // Объявляем переменную для хранения названия
                zSTRING gold_inst;

                // Получаем название инстанции и записываем его в переменную
                ps->GetValue(gold_inst, 0);

                // Если название инстанции предмета в фокусе совпадает с названием игровой валюты
                if (!pItem->GetInstanceName().Compare(gold_inst))
                    // Подкрашиваем текст в жёлтый цвет
                    screen->SetFontColor(zCOLOR(255,255,0));
            }

            // Выходим из функции
            return;
        }

        //********
        // СУНДУК
        //********

        // Пытаемся преобразовать указатель на объект в указатель на сундук
        oCMobContainer* pMobCont = dynamic_cast<oCMobContainer*>(player->focus_vob);

        // Если преобразование в сундук прошло успешно
        if (pMobCont)
        {
            // Если сундук пустой
            if (!pMobCont->containList.GetNumInList())
                // Делаем цвет текста полупрозрачным
                screen->SetFontColor(zCOLOR(255,255,255,128));

            // Выходим из функции
            return;
        }
    }


    //****************************************************************
    // Перехват функции, в которой вызывается печать фокусной надписи
    //****************************************************************
    //0x006C3140 public: void __thiscall oCGame::UpdatePlayerStatus()
    void __fastcall UpdatePlayerStatus(int _this);
    CInvoke <void(__thiscall*) (int)> pUpdatePlayerStatus(0x006C3140, UpdatePlayerStatus, IVK_AUTO);
    void __fastcall UpdatePlayerStatus(int _this)
    {
        // Запоминаем цвет текста вьюпорта
        zCOLOR color = screen->fontColor;

        // Если указатель на игрока, а также на его фокусный объект существует, то
        if (player && player->focus_vob)
            // Вызываем наш обработчик цвета
            UpdateFocusColor();

        // Вызываем оригинальную функцию,
        // в которой происходит печать текста
        pUpdatePlayerStatus(_this);

        // Если цвет текста вьюпорта был изменён
        if (screen->fontColor != color)
            // Восстанавливаем ему ранее сохранённый цвет
            screen->SetFontColor(color);
    }
     
    4) В коде, в разделе обработчика сундука, находим класс "oCMobContainer", нажимаем на него правой кнопкой мыши и выбираем пункт меню: "Перейти к определению", как показано на рисунке:

    3.jpg
    Мы быстро перешли к заголовку "ocmob.h". Класс контейнера нас не интересует. Листаем вверх до класса oCMobInter или через поиск(Ctrl+F) и обязательно исправляем недочёт, добавив в класс недостающие поля, как показано на рисунке:

    4.jpg

    Недостающие поля класса (открыть)
    Код (Text):

    zVEC3               startPos;
    zREAL               aniCombHeight;
    zCVob*              inUseVob;
    zREAL               timerEnd;


    5) Компилируем проект, смотрим результат.
    Тестовые скриншоты (открыть)

    ASTSTART_9.jpg ASTSTART_10.jpg ASTSTART_11.jpg
    ASTSTART_12.jpg ASTSTART_13.jpg ASTSTART_14.jpg ASTSTART_16.jpg ASTSTART_17.jpg
    ASTSTART_23.jpg ASTSTART_24.jpg
    ASTSTART_18.jpg ASTSTART_20.jpg ASTSTART_21.jpg
    ASTSTART_6.jpg ASTSTART_7.jpg


    Скачать готовый плагин: "UpdateFocusColor.rar"


    Пример 2 - Добавление значка перед текстом
    1) Тех. задание следующее:
    - При наведении фокуса камеры на запертый контейнер, перед текстом выводим “значок закрытого замка”, а при наведении на взломанный или отпертый – “значок открытого замка”.
    - При наведении фокуса камеры на персонажа под инстанцией SH (StoryHelper), к его имени (спереди) добавляется “значок короны”.

    2) Чтобы не повторять одни и те же действия, продолжаем работать в примере 1. Только изменим название нашего рабочего файла с “UpdateFocusColor.cpp” на более подходящий “UpdateFocusText.cpp” с помощью клавиши переименования “F2” (если хотите, можете оставить как есть, т.к. это нужно лишь для удобства восприятия).

    5.jpg
    3) Заменим весь код этого файла следующим:
    PHP:
    #include "AST.h"

    // Слой для значка фокусной надписи
    static zCView* pFocusIcon = NULL;

    // Флаг ограничивающий обработку только фокусного текста
    // (чтобы не затрагивать другие тексты, выводимые на вьюпорте)
    static bool OnUpdateFunc = false;

    // Флаг входа в функцию печати текста
    // (именно для фокусной надписи)
    static bool OnPrintFunc = false;

    // Названия текстур значков для различных ситуаций
    static const zSTRING icon_ContLock        = "icon_ContLock";    // Закрытый замок
    static const zSTRING icon_ContUnlock    = "icon_ContUnlock";// Открытый замок
    static const zSTRING icon_Leader        = "icon_Leader";    // Корона

    // Расстояние между значком и текстом по оси Х (в пикселях)
    static int icon_space = 1;

    // Функция управления иконкой фокусной надписи
    void CheckIconForFocus()
    {
        // Создание значка.
        // Если слой значка ещё не был создан, то
        // создаём его один раз на всю игровую сессию
        if (!pFocusIcon)
        {
            // Получаем высоту текста во вьюпорте (в пикселях)
            int py = screen->nay(screen->FontY()) + 1;

            // Создаём квадратный слой значка со сторонами, равными высоте текста во вьюпорте
            pFocusIcon = new zCView(0, 0, zpixelx(py), zpixely(py));
     
            // Если слой не создан, то выходим из функции
            // (потому что дальнейшие действия без него бессмысленны)
            if (!pFocusIcon) return;
            // Или идем дальше:
            // Включаем поддержку прозрачных пикселей у созданного слоя
            // (чтобы можно было скрывать и показывать значок)
            pFocusIcon->alphafunc = zRND_ALPHA_FUNC_BLEND;
     
            // Добавляем значок на главный вьюпорт
            screen->InsertItem(pFocusIcon);
        }

        // Сокрытие/показ значка.
        // Если указатель на значок есть, то работаем с ним
        if (pFocusIcon)
        {
            // Получаем высоту текста во вьюпорте (в пикселях)
            int py = screen->nay(screen->FontY()) + 1;

            // Если высоты в виртуальных координатах отличаются
            if (pFocusIcon->vsizey != zpixely(py))
            {
                // Значит было изменение разрешения экрана.
                // Обновляем размеры.
                pFocusIcon->SetSize(zpixelx(py), zpixely(py));
            }


            // Если в фокусе игрока нет объекта или игрок находится в режиме диалога,
            // значит нужно временно спрятать значок
            if (!player->focus_vob || player->m_nAivar[4])
            {
                // Если слой значка не спрятан
                if (pFocusIcon->alpha != 0)
                    // Скрываем слой значка с помощью альфа-канала
                    pFocusIcon->alpha = 0;
     
                // Выходим из функции
                return;
            }
            // В остальных случаях:
            // Если в фокусе что-то есть и игрок не в режиме диалога
            else
            {
                // Если включена игровая пауза с помощью меню
                if (ogame->pause_screen)
                {
                    // Если слой значка не спрятан
                    if (pFocusIcon->alpha != 0)
                        // Скрываем слой значка с помощью альфа-канала
                        pFocusIcon->alpha = 0;
                }
                // В остальных случаях:
                // Если включена другая пауза, не скрывающая фокусный текст,
                // или паузы вообще нет
                else
                {
                    // Если слой был спрятан с помощью альфа-канала
                    if (pFocusIcon->alpha != 255)
                        // Показываем слой
                        pFocusIcon->alpha = 255;
                }
            }


            // Визуализация значка.
            // Временная переменная для хранения названия текстуры значка
            zSTRING texName = "";

            // Пытаемся преобразовать указатель на воб в указатель на НПС
            oCNpc* pNpc = dynamic_cast<oCNpc*>(player->focus_vob);

            // Если преобразование прошло успешно
            if (pNpc)
            {
                // Работаем с НПС
                // Получаем название инстанции НПС
                zSTRING inst = pNpc->GetInstanceName();
     
                // Если это StoryHelper
                if (inst == "SH")
                {
                    // Значит пусть на время тестов
                    // носит рядом с именем значок короны
                    texName = icon_Leader;
                }
            }
            // Иначе, это объект другого класса
            else
            {
                //Пытаемся преобразовать указатель на объект в указатель на моб контейнер (сундук)
                oCMobContainer* pMob = dynamic_cast<oCMobContainer*>(player->focus_vob);

                // Если преобразование прошло успешно
                if (pMob)
                {
                    // Если контейнер закрыт
                    if (pMob->locked)
                        // Устанавливаем название значка для закрытого сундука
                        texName = icon_ContLock;
                    // Иначе, контейнер открыт
                    else
                        // Устанавливаем название значка для открытого сундука
                        texName = icon_ContUnlock;
                }
            }

            // Если название текстуры значка не пустое
            if (!texName.IsEmpty())
            {
                // Устанавливаем данную текстуру
                // в качестве заднего плана у слоя значка
                pFocusIcon->InsertBack(texName);
            }
            // Иначе, для данного объекта, название текстуры не задано
            else
            {
                // Если значок не спрятан
                if (pFocusIcon->alpha != 0)
                    // Скрываем слой значка с помощью альфа-канала
                    pFocusIcon->alpha = 0;
            }
        }
    }


    //****************************************************************
    // Перехват функции, в которой вызывается печать фокусной надписи
    //****************************************************************
    //0x006C3140 public: void __thiscall oCGame::UpdatePlayerStatus()
    void __fastcall UpdatePlayerStatus(int _this);
    CInvoke <void(__thiscall*) (int)> pUpdatePlayerStatus(0x006C3140, UpdatePlayerStatus, IVK_AUTO);
    void __fastcall UpdatePlayerStatus(int _this)
    {
        // Для теста.
        // Состояние обоих клавиш "SHIFT"
        // (если нажата хоть одна, то результат "1", иначе "0")
        bool shift = KeyPress(KEY_LSHIFT) || KeyPress(KEY_RSHIFT);

        // Если нажата кнопка "+" (не на цифровой клавиатуре) или зажата вместе с клавишей "SHIFT"
        if (KeyClick(KEY_EQUALS) || (KeyPress(KEY_EQUALS) && (shift)))
        {
            // Увеличиваем отступ между значком и текстом на 1 пиксель
            icon_space++;
        }
        // Или если нажата кнопка "-" (не на цифровой клавиатуре) или зажата вместе с клавишей "SHIFT"
        else if (KeyClick(KEY_MINUS) || (KeyPress(KEY_MINUS) && (shift)))
        {
            // Уменьшаем отступ
            icon_space--;
        }
        // Или если нажата кнопка "\" или зажата вместе с клавишей "SHIFT"
        else if (KeyClick(KEY_BACKSLASH) || (KeyPress(KEY_BACKSLASH) && (shift)))
        {
            // Если в фокусе игрока есть какой-нибудь НПС
            if (oCNpc* pNpc = player->GetFocusNpc())
                // Прибавляем к имени НПС новый символ
                pNpc->SetName(pNpc->m_sNameNpc + zSTRING("#"));
        }
        // Или если нажата кнопка "←" или зажата вместе с клавишей "SHIFT"
        else if (KeyClick(KEY_BACKSPACE) || (KeyPress(KEY_BACKSPACE) && (shift)))
        {
            // Если в фокусе игрока есть какой-нибудь НПС
            if (oCNpc* pNpc = player->GetFocusNpc())
                // Удаляем один символ с конца его имени
                pNpc->m_sNameNpc.DeleteRight(1);
        }

        // Выводим отладочную информацию на экран
        screen->PrintCXY("icon_space = " + zSTRING(icon_space));



        // Вызываем функцию подбора подходящей иконки
        // для объекта, находящегося в фокусе игрока
        CheckIconForFocus();

        // Включаем обработчик только для фокусной надписи
        OnUpdateFunc = true;

        // Вызываем оригинальную функцию
        pUpdatePlayerStatus(_this);

        // По завершению - выключаем
        OnUpdateFunc = false;
     
        // Если фокусный текст не выводился на экран, а значок есть и не спрятан
        if (!OnPrintFunc && pFocusIcon && pFocusIcon->alpha != 0)
            // Скрываем слой значка с помощью альфа-канала
            pFocusIcon->alpha = 0;

        // Если был вызов функции печати фокусного текста
        if (OnPrintFunc)
            // Сбрасываем флаг состояния
            OnPrintFunc = false;
    }


    //******************************************
    // Перехват функции вывода текста на экран
    //******************************************
    //0x007A9A40 public: void __thiscall zCView::Print(int,int,class zSTRING const &)
    void __fastcall View_Print(zCView* _this, void* vt, int x, int y, zSTRING& text);
    CInvoke <void(__thiscall*) (zCView*, int, int, zSTRING&)> pView_Print(0x007A9A40, View_Print, IVK_AUTO);
    void __fastcall View_Print(zCView* _this, void* vt, int x, int y, zSTRING& text)
    {
        // Корректируем позицию текста и иконки.
        // Работаем только с фокусной надписью.
        // Если значок существует и не спрятан
        if (OnUpdateFunc && pFocusIcon && pFocusIcon->alpha != 0)
        {
            // Устанавливаем флаг входа в функцию
            OnPrintFunc = true;
     
            // Длина текста в виртуальных координатах
            int textSize = screen->FontSize(text);

            // Выравниваем позицию текста по оси Х
            // с учётом длины значка и величины отступа между значком и текстом.
            // Т.е. текст смещается вправо на половину значка и половину отступа.
            int vx = x + zpixelx(pFocusIcon->psizex / 2) + zpixelx(icon_space / 2);

            // Если центрированный текст не умещается справа
            if ((vx + textSize) > 8191)
            {
                // Предварительно выравниваем текст по правому краю
                x = 8191 - textSize;

                // А также расчитываем координату значка смещаясь влево,
                // вычитая его длину, размер отступа и учитывая погрешность вью-слоя
                // в виде одного пикселя
                vx = x - pFocusIcon->vsizex - zpixelx(icon_space) + zpixelx(1);
            }
            // Иначе, текст умещается справа
            else
            {
                // Координата центрированного текста уже расчитана ранее,
                // поэтому записываем её в качестве аргумента для дальнейшей передачи в функцию
                x = vx;

                // Расчитываем смещение значка относительно текста.
                // Т.е. из координат текста вычитаем размер значка, смещаясь влево,
                // также вычитаем размер отступа и учитываем погрешность вью-слоя в виде одного пикселя
                vx = x - pFocusIcon->vsizex - zpixelx(icon_space) + zpixelx(1);
            }

            // Наивысший приоритет остаётся у выравнивания по левому краю, если текст очень длинный.
            // Проверяем, умещается ли текст слева.
            // Если не умещается, то
            if (vx < 0)
            {
                // Выравниваем значок по левому краю
                vx = 0;

                // Расчитываем координаты текста, прибавляя размер значка, смещаясь вправо, а также размер отступа,
                // и не забываем про погрешность вью-слоя в виде одного пикселя.
                x = pFocusIcon->vsizex + zpixelx(icon_space) - zpixelx(1);
            }

            // Устанавливаем рассчитанную позицию значка.
            // Координату Y берём от текста.
            pFocusIcon->SetPos(vx, y);
        }

        // Вызываем оригинальную функцию вывода текста на экран
        pView_Print(_this, x, y, text);
    }
    Вся суть сводится к тому, что во время перехвата, имеются виртуальные координаты текста, а также его высота и ширина:

    PicSizes.jpg
    И относительно них рассчитываются размеры значка, а также его положение. Например, значок будет иметь размеры: fontY x fontY.
    При этом текст заново выравнивается по оси Х, т.к. к нему добавляется значок. И в конце проверяется, не выходит ли элемент за границы экрана. По результатам проверки, вносятся поправки в координаты.

    3) Компилируем проект, смотрим результат.
    Тестовые скриншоты (открыть)

    GOTHICGAME_0.jpg GOTHICGAME_1.jpg GOTHICGAME_3.jpg GOTHICGAME_4.jpg
    GOTHICGAME_5.jpg GOTHICGAME_6.jpg GOTHICGAME_7.jpg GOTHICGAME_8.jpg
    GOTHICGAME_9.jpg GOTHICGAME_10.jpg


    Управление в плагине

    Регулирование интервала между названием фокусного объекта и его значком (icon_space):
    • Кнопка "+" - увеличение интервала
    • Кнопка "-" - уменьшение интервала
    Редактирование имени персонажа в фокусе:
    • Кнопка "\" - добавление в конец строки символа "#"
    • Кнопка "←" - удаление одного символа с конца строки.
    Общее примечание: с любой зажатой клавишей "SHIFT" - непрерывное редактирование.
    Скачать готовый мод (плагин + значки): UpdateFocusText.rar


    Пример 3 - Использование в названии мульти-строчного текста
    1) Тех. задание следующее:

    а) Напишем самодельный парсер текстовой строки, с поддержкой следующих тегов:
    • “RGBA([0-255] [0-255] [0-255] [0-255])” – этот тег указывается в начале подстроки и задаёт ей цвет. Цветовые составляющие должны быть указаны в пределах от 0 до 255 и разделены пробелами. Альфа-канал также поддерживается. Если цветовая составляющая выходит за пределы [0-255], то она преобразуется с учётом переполнения типа byte.
    • “\n” – тег переноса строк. Может использоваться один или несколько раз в начале, в середине или в конце общей текстовой строки.
    Примечание 1: Текстовая строка может не содержать тег с пометкой цвета, тогда будет использоваться цвет по умолчанию: RGBA(255 255 255 255).

    Примечание 2: Подкрашиваться может только целая строка, а не каждое слово в строке.

    Вот как выглядит преобразование строки:
    Logo1.jpg

    б) Введём следующие защитные установки:
    • Если наш плагин загружен, то скриптовая константа "AgamaST" примет значение "1", иначе останется по умолчанию: "0".
    • Если AgamaST равен "1", используем много-строчный текст, иначе используем обычный одно-строчный текст.
    в) Введём функцию предварительной обработки текста, в которой будем добавлять префиксы для контейнеров.

    г) В качестве примера напишем обработчики для следующих объектов:
    • НПС (StoryHelper)
    - Присвоим ему двухстрочное название: "My name is\nStoryHelper". Цвет текста оставим по умолчанию.​
    • Контейнер
    - Если закрыт, то добавляем к его названию префикс “Запертый”. И, к примеру, подкрашиваем красным цветом.
    - Если открыт, то добавляем к названию префикс “Отпертый”. И также подкрашиваем его, только в зелёный цвет.

    2) Чтобы вновь не повторять одни и те же действия, продолжаем работать в примере 1 или примере 2. Название нашего рабочего файла: “UpdateFocusText.cpp”.
    Заменяем весь код файла следующим:
    PHP:
    #include "AST.h"

    // Объявляем новую структуру
    // для хранения цветного текста
    struct ColoredText
    {
        zCOLOR color; //цвет текста
        zSTRING text; //текст
    };

    // Объявляем временный массив для хранения цветного текста
    static zCArray<ColoredText> focusNames;

    // Флаг ограничивающий обработку только фокусного текста
    // (чтобы не затрагивать другие тексты, выводимые на вьюпорте)
    static bool OnFocusFunc = false;

    // Флаг разрешающий выполнить разбивку исходний строки на несколько строк
    // (используется во время перехвата функции получения размеров текста)
    static bool bParseString = false;

    // Функция предварительной обработки фокусной надписи,
    // где text - это ссылка на редактируемую текстовую строку
    static void UpdateFocusText(zSTRING& text)
    {
        // Если указатель на игрока и на его фокусный объект существует
        if (player && player->focus_vob)
        {
            // Пытаемся преобразовать указатель на объект в указатель на моб контейнер (сундук)
            oCMobContainer* pMob = dynamic_cast<oCMobContainer*>(player->focus_vob);
     
            // Если преобразование прошло успешно
            if (pMob)
            {
                // Если контейнер закрыт
                if (pMob->locked)
                    // Присваиваем к названию соответствующую приставку с переходом на следующую строку
                    text = "RGBA(255 60 60 255)Запертый\\n" + text;
                // Иначе, контейнер открыт
                else
                    // Присваиваем к названию соответствующую приставку с переходом на следующую строку
                    text = "RGBA(50 255 50 255)Отпертый\\n" + text;
            }
        }
    }

    //****************************************************************
    // Перехват функции, в которой вызывается печать фокусной надписи
    //****************************************************************
    //0x006C3140 public: void __thiscall oCGame::UpdatePlayerStatus()
    static void __fastcall UpdatePlayerStatus(int _this);
    static CInvoke <void(__thiscall*) (int)> pUpdatePlayerStatus(0x006C3140, UpdatePlayerStatus, IVK_AUTO);
    static void __fastcall UpdatePlayerStatus(int _this)
    {
        // Включаем обработчик только для фокусной надписи
        OnFocusFunc = true;

        // Разрешаем разбивку строки
        bParseString = true;

        // Вызываем оригинальную функцию
        pUpdatePlayerStatus(_this);

        // По завершению - выключаем
        OnFocusFunc = false;
    }


    // Парсер строки (или разбивка строки),
    // где sourceText - это ссылка на строку.
    static void ParseString(zSTRING& sourceText)
    {
        // Очищаем массив от старых записей
        focusNames.DeleteList();

        // Доп. обработка текстовой строки
        // (исправляет или не исправляет фокусный текст)
        UpdateFocusText(sourceText);

        // Рабочая структура
        ColoredText TxtData;

        // Значения по умолчанию (для цвета и текста)
        TxtData.color = zCOLOR(255,255,255,255);
        TxtData.text = "";

        // Был ли открыващий тег цвета
        bool openTag = false;

        // Был ли закрывающий тег цвета
        bool closeTag = false;

        // Разрешение сбросить структуру в массив
        // и очистить флаги, цвет, текст для нового поиска
        bool bDump = false;

        // Рабочая позиция для тегов
        int pos = 0;

        // Цикл перебора символов рабочей строки
        for (int i = 0; i < sourceText.len; i++)
        {
            // Поиск открывающего тега "RGBA("
            // Если открывающего тега цвета не было и встречается последовательность символов "RGBA("
            if (openTag == false && sourceText.GetChar(i) == 'R' && sourceText.GetChar(i + 1) == 'G' && sourceText.GetChar(i + 2) == 'B' && sourceText.GetChar(i + 3) == 'A' && sourceText.GetChar(i + 4) == '(')
            {
                // Устанавливаем флаг наличия открывающего тега цвета
                openTag = true;

                // Пропускаем этот тэг и идём дальше - на следующий символ
                i += 5;

                // Запоминаем позицию открывающего тега
                pos = i - 1;
            }


            // Если открывающий тэг был а закрывающего тега не было
            if (openTag && !closeTag)
            {
                // И если обнаружили закрывающий тэг
                if (sourceText.GetChar(i) == ')')
                {
                    // Устанавливаем флаг наличия закрывающего тега цвета
                    closeTag = true;

                    // Если тег не имеет информации о цвете:
                    // GRBA()
                    if (pos == i - 1)
                    {
                        // Если цвет текста не по умолчанию
                        if (TxtData.color.r != 255 || TxtData.color.g != 255 || TxtData.color.b != 255 || TxtData.color.a != 255)
                            // Задаём рабочий цвет текста по умолчанию
                            TxtData.color = zCOLOR(255,255,255,255);
                    }
                    // Иначе, внутри тега что-то есть
                    else
                    {
                        // Извлекаем информацию о цвете внутри тега в виде текстовой строки
                        zSTRING sRGBA = sourceText.Substr(pos + 1, i - pos - 1);

                        // Преобразуем текстовую строку в цвет структуры zCOLOR()
                        // и устанавливаем в качестве рабочего цвета
                        TxtData.color.SetByDescription(sRGBA);
                    }

                    // Пропускаем закрытие тега и идём дальше
                    // По идее, дальше должен идти текст
                    pos = i + 1;
                }
            }
            // Или если RGBA-тэг был уже завершён или открывающего RGBA-тега ещё не было
            else if ((openTag && closeTag) || !openTag)
            {
                // Ищем тег завершения строки.
                // Если последовательность символов совпадает с тегом завершения строки
                if (sourceText.GetChar(i) == '\\' && sourceText.GetChar(i + 1) == 'n')
                {
                    // Извлекаем текст до этого тега и записываем его в рабочую структуру
                    TxtData.text = sourceText.Substr(pos, i - pos);

                    // Пропускаем тег завершения строки
                    pos = i + 2;

                    // Разрешаем записать структуру в массив
                    bDump = true;
                }
                // Или если обнаружен конец рабочей строки
                else if (i == (sourceText.len - 1))
                {
                    // Извлекаем остатки строки до конца
                    TxtData.text = sourceText.Substr(pos);

                    // Разрешаем записать структуру в массив
                    bDump = true;
                }

                // Если есть разрешение на запись
                if (bDump)
                {
                    // Записываем копию рабочей структуры в конец массива
                    focusNames.InsertEnd(TxtData);

                    // Обнуляем флаги состояния открывающего и закрывающего RGBA-тега
                    openTag = closeTag = false;

                    // Устанавливаем цвет рабочей структуры - по умолчанию,
                    // потому что он может остаться от предыдущей
                    TxtData.color = RGBA(255,255,255,255);

                    // Отмена флага
                    bDump = false;
                }
            }
        }
    }


    //************************************************************************************
    // Перехват функции, в которой расчитывается длина текста (в вирутальных координатах)
    //************************************************************************************
    //0x007A9A10 public: int __thiscall zCView::FontSize(class zSTRING &)
    static int __fastcall View_FontSize(zCView* _this, void* vt, zSTRING& text);
    static CInvoke <int(__thiscall*) (zCView*, zSTRING&)> pFontSize(0x007A9A10, View_FontSize, IVK_AUTO);
    static int __fastcall View_FontSize(zCView* _this, void* vt, zSTRING& text)
    {
        // Если это не фокусный текст
        if (!OnFocusFunc)
            // Вызываем оригинальную функцию
            return pFontSize(_this, text);
        // Иначе:
        // Если разрешена разбивка строки на отдельные слова
        if (bParseString)
        {
            // Разбиваем строку с учётом тегов и заполняем ею массив "focusNames"
            ParseString(text);

            // Защита от повторной разбивки за один заход
            bParseString = false;
        }

        // Если слов в массиве нет, значит фокусный объект без названия
        if (!focusNames.GetNum())
            // Вызываем оригинальную функцию
            return pFontSize(_this, text);
        // Иначе:
        // Ищем наибольшую строку в массиве и возвращаем её размер,
        // т.к. габариты блока текста будут определяться именно самой длинной строкой.
     
        // Максимальный размер строки
        int maxSize = 0;

        // Текущий размер строки
        int currentSize = 0;

        // Пробегаемся по всем строкам в массиве
        for (int i = 0; i < focusNames.GetNum(); i++)
        {
            // Если строка не пустая
            if (!focusNames[i].text.IsEmpty())
            {
                // Получаем размер текущей строки
                currentSize = pFontSize(_this, focusNames[i].text);
     
                // Если размер текущей строки больше предыдущего размера
                if (maxSize < currentSize)
                {
                    // Запоминаем максимальный размер строки
                    maxSize = currentSize;
                }
            }
        }
     
        // Возвращаем максимальный размер строки
        return maxSize;
    }


    //******************************************
    // Перехват функции вывода текста на экран
    //******************************************
    //0x007A9A40 public: void __thiscall zCView::Print(int,int,class zSTRING const &)
    static void __fastcall View_Print(zCView* _this, void* vt, int x, int y, zSTRING& text);
    static CInvoke <void(__thiscall*) (zCView*, int, int, zSTRING&)> pView_Print(0x007A9A40, View_Print, IVK_AUTO);
    static void __fastcall View_Print(zCView* _this, void* vt, int x, int y, zSTRING& text)
    {
        // Если это не фокусный текст или текстовая строка пустая
        if (!OnFocusFunc || text.IsEmpty())
        {
            // Вызываем оригинальную функцию
            pView_Print(_this, x, y, text);

            // Выходим из функции перехвата
            return;
        }
        // Иначе:
        // Находим центр блока текста,
        // прибавляя к текущим координатам текста половину самой длинной строки в массиве
        int centerX = x + screen->FontSize(text) / 2;

        // Находим верхний край блока текста,
        // вычитая из текущих координат высоту одной строки, умноженную на кол-во строк минус один
        int oY = y - screen->FontY() * (focusNames.GetNum() - 1);

        // Если верхний край блока выходит за допустимые границы
        if (oY < screen->FontY())
        {
            // Поправляем ему координаты
            oY = screen->FontY();
        }

        // Запоминаем базовый цвет текста вьюпорта
        zCOLOR color = screen->fontColor;

        // Пробегаемся по всем строкам массива
        for (int i = 0; i < focusNames.GetNum(); i++)
        {
            // Если текст строки пустой, то не выводим его на экран
            if (focusNames[i].text.IsEmpty())
                // Продолжаем цикл со следующего элемента
                continue;
            // Иначе:
            // Задаём цвет текста вьюпорта согласно цвету строки
            screen->fontColor = focusNames[i].color;

            // Выводим строку на экран
            pView_Print(_this, zpixelx(screen->nax(centerX) - screen->nax(pFontSize(screen, focusNames[i].text)) / 2), zpixely(screen->nay(oY) + screen->nay(screen->FontY() * i)), focusNames[i].text);
     
            // Примечания:
            // - Здесь используем указатель на оригинальную функцию pFontSize(), иначе будет возвращается одна и та же длина
            //     самого длинного слова из за перехвата функции и весь текст будет ориентирован по левому краю.
            // - Также координаты переводятся из виртуальных единиц в пиксели и обратно во избежании дребезжания текста на экране.
        }

        //восстанавливаем цвет текста вьюпорта
        screen->fontColor = color;
    }
    Примечание: Приставка static здесь используется для ограничения области видимости функций и переменных в пределах одного модуля. В данном примере её использование может и не обязательно, но иногда это бывает необходимо, чтобы избежать конфликты с похожими именами обычных функций или функций-перехватчиков, находящихся в разных файлах одного проекта.


    3) Переходим в файл "dllRefApp.cpp" нашего проекта, находим в нём функцию "_cb_Game_Init()" и вставляем в неё следующий защитный код, который поможет избежать случаев с неправильной обработкой мульти-строчного текста:
    PHP:
    void _cb_Game_Init()
    {
        // Ищем в скриптах переменную под названием "AgamaST"
        zCPar_Symbol* ps = parser->GetSymbol("AgamaST");
     
        // Если нашли эту целочисленную переменную
        if (ps && ps->type == zPAR_TYPE_INT)
        {
            // Пристваиваем ей значение "1",
            // сообщая в скрипты о том, что плагин загрузился.
            ps->SetValue(1, 0);
        }
    }
    4) Компилируем проект.

    5) Переходим в рабочие скрипты и находим в них удобное место для нашей защитной константы, например, здесь: "_intern\Constants.d" и добавляем следующий код (в любом месте - cверху/снизу):
    PHP:

    //"1" - плагин загружен, "0" - не загружен
    const int AgamaST = 0;
    6) Контейнеры(сундуки) уже обработаны внутри плагина в общем виде, осталось разобраться с НПС.
    Находим тестового НПС "StoryHelper" в следующем скрипте: "AI\Test_Scripts\StoryHelper.d"
    Или можете взять любого другого для теста.
    И добавляем в его конструктор следующий код (выделен комментариями):
    PHP:
    instance SH(Npc_Default)
    {
        // если плагин загружен
        if (AgamaST == 1)
        {
            // составляем имя из двух строк
            name[0] = "My name is\nStoryhelper";
        }
        // иначе, плагин не загружен
        else
        {
            // оставляем имя по умолчанию
            name[0] = "Storyhelper";
        };
     
        guild = GIL_NONE;
        id = 9999;
        voice = 15;
        flags = 0;
        npcType = NPCTYPE_FRIEND;
        B_SetAttributesToChapter(self,1);
        B_GiveNpcTalents(self);
        fight_tactic = FAI_HUMAN_MASTER;
        B_CreateAmbientInv(self);
        B_SetNpcVisual(self,MALE,"Hum_Head_Pony",Face_N_Player,BodyTex_Player,-1);
        Mdl_SetModelFatness(self,0);
        Mdl_ApplyOverlayMds(self,"Humans_Relaxed.mds");
        daily_routine = Rtn_Start_9999;
    };
    Примечание:
    Также стоит отметить, что некоторые текстовые константы можно менять в зависимости от поддержки много-строчного текста, в скрипте "Story\Startup.d", следующим образом:
    PHP:
    func void init_global()
    {
        Game_InitGerman();
     
        // если много-строчный текст поддерживается
        if (AgamaST == 1)
        {
            // К примеру, меняем названия надгробным табличкам - с переносом даты на следующую строку
            MOBNAME_GRAVE_18 = "Иотар\n721 - 762";
            MOBNAME_GRAVE_19 = "Мидос\n757 - 759";
            MOBNAME_GRAVE_20 = "Оскар Сорн\n703 - 736";
        };
    };
    8) Компилируем скрипты, прописываем плагин в ini, смотрим результат.

    Скачать готовый плагин: UpdateFocusMultiText.rar

    Тестовые скриншоты (открыть)

    GOTHICGAME_3.png GOTHICGAME_4.png GOTHICGAME_5.png GOTHICGAME_6.png GOTHICGAME_7.png GOTHICGAME_8.png Writen.jpg SpacerGrave.jpg
     

    Вложения:

    Последнее редактирование: 9 мар 2018
Статус темы:
Закрыта.

Поделиться этой страницей