Jr13San
Модостроитель
- Регистрация
- 1 Апр 2010
- Сообщения
- 462
- Благодарности
- 285
- Баллы
- 230
Пример 1 - Изменение цвета текста
1) Для начала определимся с тех. заданием. В качестве примера напишем обработчики для следующих объектов:
- НПС
- Если он входит в состав группы игрока, то подкрашиваем его имя зелёным цветом.
- Если он находится в состоянии атаки на игрока, то окрашиваем его имя в красный цвет.
- Если он мёртв и у него нечего взять, то делаем надпись полупрозрачной (чтобы не привлекал внимание).
- В остальных случаях цвет фокусной надписи не меняется.
- Если он находится в состоянии атаки на игрока, то окрашиваем его имя в красный цвет.
- Если он мёртв и у него нечего взять, то делаем надпись полупрозрачной (чтобы не привлекал внимание).
- В остальных случаях цвет фокусной надписи не меняется.
- Предмет
- Если предмет является игровой валютой, то подкрашиваем надпись в жёлтый цвет.
- В остальных случаях - используется цвет по умолчанию.
- В остальных случаях - используется цвет по умолчанию.
- Сундук
- Если сундук пустой, то его название становится полупрозрачным (чтобы не привлекал внимание).
- В остальных случаях цвет фокусной надписи не меняется.
- В остальных случаях цвет фокусной надписи не меняется.
2) Открываем рабочий проект плагина для версии "003" в Visual Studio, и добавляем в раздел “Plugin main” новый файл, под названием “UpdateFocusColor.cpp”, как показано на рисунках:
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", нажимаем на него правой кнопкой мыши и выбираем пункт меню: "Перейти к определению", как показано на рисунке:
Мы быстро перешли к заголовку "ocmob.h". Класс контейнера нас не интересует. Листаем вверх до класса oCMobInter или через поиск(Ctrl+F) и обязательно исправляем недочёт, добавив в класс недостающие поля, как показано на рисунке:
C++:
zVEC3 startPos;
zREAL aniCombHeight;
zCVob* inUseVob;
zREAL timerEnd;
5) Компилируем проект, смотрим результат.
Скачать готовый плагин: "UpdateFocusColor.rar"
Пример 2 - Добавление значка перед текстом
1) Тех. задание следующее:
- При наведении фокуса камеры на запертый контейнер, перед текстом выводим “значок закрытого замка”, а при наведении на взломанный или отпертый – “значок открытого замка”.
- При наведении фокуса камеры на персонажа под инстанцией SH (StoryHelper), к его имени (спереди) добавляется “значок короны”.
2) Чтобы не повторять одни и те же действия, продолжаем работать в примере 1. Только изменим название нашего рабочего файла с “UpdateFocusColor.cpp” на более подходящий “UpdateFocusText.cpp” с помощью клавиши переименования “F2” (если хотите, можете оставить как есть, т.к. это нужно лишь для удобства восприятия).
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);
}
Вся суть сводится к тому, что во время перехвата, имеются виртуальные координаты текста, а также его высота и ширина:
И относительно них рассчитываются размеры значка, а также его положение. Например, значок будет иметь размеры: fontY x fontY.
При этом текст заново выравнивается по оси Х, т.к. к нему добавляется значок. И в конце проверяется, не выходит ли элемент за границы экрана. По результатам проверки, вносятся поправки в координаты.
Управление в плагине
- Кнопка "+" - увеличение интервала
- Кнопка "-" - уменьшение интервала
- Кнопка "\" - добавление в конец строки символа "#"
- Кнопка "←" - удаление одного символа с конца строки.
Скачать готовый мод (плагин + значки): UpdateFocusText.rarПример 3 - Использование в названии мульти-строчного текста
а) Напишем самодельный парсер текстовой строки, с поддержкой следующих тегов:
б) Введём следующие защитные установки:
г) В качестве примера напишем обработчики для следующих объектов:
- “RGBA([0-255] [0-255] [0-255] [0-255])” – этот тег указывается в начале подстроки и задаёт ей цвет. Цветовые составляющие должны быть указаны в пределах от 0 до 255 и разделены пробелами. Альфа-канал также поддерживается. Если цветовая составляющая выходит за пределы [0-255], то она преобразуется с учётом переполнения типа byte.
- “\n” – тег переноса строк. Может использоваться один или несколько раз в начале, в середине или в конце общей текстовой строки.
Примечание 1: Текстовая строка может не содержать тег с пометкой цвета, тогда будет использоваться цвет по умолчанию: RGBA(255 255 255 255).
Примечание 2: Подкрашиваться может только целая строка, а не каждое слово в строке.
Вот как выглядит преобразование строки:
Примечание 2: Подкрашиваться может только целая строка, а не каждое слово в строке.
Вот как выглядит преобразование строки:
б) Введём следующие защитные установки:
- Если наш плагин загружен, то скриптовая константа "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