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

    Чтобы получить возможность писать на форуме, оставьте сообщение в этой теме.
    Удачи!
  • Друзья, доброго времени суток!
    Стартовал новый литературный конкурс от "Ордена Хранителей" - "Пираты Миртанского моря".
    Каждый может принять в нём участие и снискать славу и уважение, а в случае занятия призового места ещё и получить награду. Дерзайте

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

Статус
В этой теме нельзя размещать новые ответы.

Jr13San


Модостроитель
Регистрация
1 Апр 2010
Сообщения
457
Благодарности
276
Баллы
230
Пример 1 - Изменение цвета текста

1) Для начала определимся с тех. заданием. В качестве примера напишем обработчики для следующих объектов:
  • НПС
- Если он входит в состав группы игрока, то подкрашиваем его имя зелёным цветом.
- Если он находится в состоянии атаки на игрока, то окрашиваем его имя в красный цвет.
- Если он мёртв и у него нечего взять, то делаем надпись полупрозрачной (чтобы не привлекал внимание).
- В остальных случаях цвет фокусной надписи не меняется.

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

  • Сундук
- Если сундук пустой, то его название становится полупрозрачным (чтобы не привлекал внимание).
- В остальных случаях цвет фокусной надписи не меняется.

2) Открываем рабочий проект плагина для версии "003" в Visual Studio, и добавляем в раздел “Plugin main” новый файл, под названием “UpdateFocusColor.cpp”, как показано на рисунках:

1.jpg 2.jpg

3) Добавляем в файл следующий код:
C++:
#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

C++:
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) Заменим весь код этого файла следующим:
C++:
#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”.
Заменяем весь код файла следующим:
C++:
#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()" и вставляем в неё следующий защитный код, который поможет избежать случаев с неправильной обработкой мульти-строчного текста:
C++:
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верху/снизу):
Daedalus:
//"1" - плагин загружен, "0" - не загружен
const int AgamaST = 0;

6) Контейнеры(сундуки) уже обработаны внутри плагина в общем виде, осталось разобраться с НПС.
Находим тестового НПС "StoryHelper" в следующем скрипте: "AI\Test_Scripts\StoryHelper.d"
Или можете взять любого другого для теста.
И добавляем в его конструктор следующий код (выделен комментариями):
Daedalus:
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", следующим образом:
Daedalus:
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.pngWriten.jpg SpacerGrave.jpg
 

Вложения

  • UpdateFocusColor.rar
    9,6 KB · Просмотры: 107
  • UpdateFocusText.rar
    11,4 KB · Просмотры: 70
  • UpdateFocusMultiText.rar
    10,2 KB · Просмотры: 68
Статус
В этой теме нельзя размещать новые ответы.
Сверху Снизу