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

    Чтобы получить возможность писать на форуме, оставьте сообщение в этой теме.
    Удачи!
  • Друзья, доброго времени суток! Спешите принять участие в оценке работ на конкурсе "Таинственные миры" 2024!
    Ждем именно вас!

    Ссылка на конкурсную тему - тык
    Ссылка на тему с работами участников- тык

4. Работа с Gothic API. Кроссплатформенный плагин. Выводим текст на экран.

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.281
Благодарности
4.581
Баллы
625
  • Первое сообщение
  • #1

Русский English


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

    Для начала работы возьмем предыдущий плагин. Тестировать работу будем на Gothic II: NoTR, а затем распишем код для остальных.
    Установим конфигурацию проекта на G2A Release.

    Подготовка рабочего пространства
    Для удобства создадим новый исходный файл под каталогом Plugin:
    73955
    Подключим заголовок UnionAfx.h, а далее очень важно.

    Каждый интерфейс движка обернут под пространства имен (Gothic_I_Classic, Gothic_I_Addon, Gothic_II_Classic, Gothic_II_Addon соответственно). Поскольку мы пишем тестовый код под Gothic II NoTR, то определим пространство Gothic_II_Addon.

    В итоге получим файл примерно с такой начинкой:
    C++:
    #include <UnionAfx.h>
    
    namespace Gothic_II_Addon {
    
    }

    Однако если мы захотим изменить конфигурацию проекта под другой движок, компилятор выбьет ошибку, что код внутри Gothic_II_Addon ему непонятен.
    Поверх пространства допишем команду #ifdef __G2A. Благодаря этому компилятор сможет исключать из построения весь лишний код, когда тот будет не нужен.
    Эта конструкция будет базовой для всех кроссплатформенных проектов:
    C++:
    #include <UnionAfx.h>
    
    #ifdef __G2A
    namespace Gothic_II_Addon {
    
    }
    #endif



    Добавление циклической функции
    Самый простой способ вызывать функции из разных файлов исходного кода - ключевое слово extern. В конце нашего файла создадим функцию Loop, а в Application.cpp объявим ее через extern. Вызовем функцию из предопределенной функции Game_Loop.
    C++:
    // Example.cpp
    #include <UnionAfx.h>
    
    #ifdef __G2A
    namespace Gothic_II_Addon {
    
    }
    #endif
    
    void Loop() {
    
    }
    C++:
    // Application.cpp
    extern void Loop();
    
    void Game_Loop() {
      Loop();
    }



    Вывод текста на экран
    В Example.cpp в Gothic_II_Addon добавим функцию вывода текста. Текст, как можно догадаться, будет выводиться циклически вызовом из функции Loop.
    Вывод будем производить на вьюпорт screen.
    screen - это экземпляр zCView (можно посмотреть в файлах zView.h). Он имеет метод Print для вывода текста по координатам, PrintCX выравнивающий текст по горизонтали, PrintCY по вертикали и PrintCXY рисующий текст в центре экрана. Возьмем последнюю.
    Example: screen->PrintCXY( "Hello" );

    В качестве текста возьмем информацию об имени персонажа в фокусе игрока.
    Игрок определяется глобальной переменной player класса oCNpc.
    Класс имеет два подходящих метода GetFocusVob - берет любую сущность в фокусе и GetFocusNpc - берет конкретно нпс. Она нам и нужна.
    Example: oCNpc* focusNpc = playerGetFocusNpc();

    Далее необходимо получить имя npc. Вспоминаем скрипты - нпс имеет массив имен из 5 строк. Нам нужна первая. Воспользуемся методом GetName указав индекс строки 0.
    Example: zSTRING npcName = focusNpc ->GetName( 0 )

    Теперь собираем все воедино. Не забываем учесть, что npc не всегда присутствует в фокусе, по этому вывод необходимо производить когда указатель на focusNpc не равен null. Поэтому все операции вписываем под блок if( focusNpc )

    Код на данном этапе должен иметь такой вид:
    C++:
    #include <UnionAfx.h>
    
    #ifdef __G2A
    namespace Gothic_II_Addon {
    
      void PrintScreen() {
       
        oCNpc* focusNpc = player->GetFocusNpc();
        if( focusNpc ) {
    
          zSTRING npcName = focusNpc->GetName( 0 );
          screen->PrintCXY( npcName );
        }
      }
    
    }
    #endif
    
    void Loop() {
    
    }



    Расширение кода на все движки:
    Копируем код, меняя в нем пространство имен и определение препроцессора:
    73956

    В функции Loop добавляем код вызова функций PrintScreen.
    Чтобы вызывать только те функции, которые относятся к соответствующему движку - обратимся к экземпляру Union и запросим текущую версию движка методом GetEngineVersion.
    TEngineVersion engineVersion = Union.GetEngineVersion();

    Поставим проверки на соответствие и вызовем функции.
    C++:
    if( engineVersion == Engine_G1 )
        Gothic_I_Classic::PrintScreen();

    Но как говорилось ранее, собирая проект для одного движка - остальные будут выдавать ошибку:
    73957


    Поэтому также обернем вызовы под #ifdef, исключив из построения ненужные вызовы:
    C++:
    void Loop() {
      TEngineVersion engineVersion = Union.GetEngineVersion();
    
    #ifdef __G1
      if( engineVersion == Engine_G1 )
        Gothic_I_Classic::PrintScreen();
    #endif
    
    #ifdef __G1A
      if( engineVersion == Engine_G1A )
        Gothic_I_Addon::PrintScreen();
    #endif
    
    #ifdef __G2
      if( engineVersion == Engine_G2 )
        Gothic_II_Classic::PrintScreen();
    #endif
    
    #ifdef __G2A
      if( engineVersion == Engine_G2A )
        Gothic_II_Addon::PrintScreen();
    #endif
    }

    Тестовая компиляция и запуск
    Компилируем плагин в конфигурации G2A Release. Проект должен быстро собраться, поскольку отключены 3 движка. Запускаем плагин, заходим в игру и проверяем, чтобы при наведении фокуса на npc выводилось его имя.
    73958
    В центре экрана выводится дублирующее имя. Значит все работает исправно.



    Финальная компиляция и запуск:
    Включаем конфигурацию сборки Release. Замечаем, что стали активными все блоки, обернутые ранее под команду #ifdef. Это значит, что в игру вступили все движки и они готовы к работе в кроссплатформенном режиме. Компилируем проект, это займет значительно больше времени, чем при тестовом запуске.

    Результаты:
    Gothic I Classic
    73959

    Gothic I Addon
    73960

    Gothic II Classic
    73961

    Gothic II Addon
    73962





    Код без препроцессорных команд:
    C++:
    namespace Gothic_I_Classic {
    
      void PrintScreen() {
       
        oCNpc* focusNpc = player->GetFocusNpc();
        if( focusNpc ) {
    
          zSTRING npcName = focusNpc->GetName( 0 );
          screen->PrintCXY( npcName );
        }
      }
    
    }
    
    namespace Gothic_I_Addon {
    
      void PrintScreen() {
       
        oCNpc* focusNpc = player->GetFocusNpc();
        if( focusNpc ) {
    
          zSTRING npcName = focusNpc->GetName( 0 );
          screen->PrintCXY( npcName );
        }
      }
    
    }
    
    namespace Gothic_II_Classic {
    
      void PrintScreen() {
       
        oCNpc* focusNpc = player->GetFocusNpc();
        if( focusNpc ) {
    
          zSTRING npcName = focusNpc->GetName( 0 );
          screen->PrintCXY( npcName );
        }
      }
    
    }
    
    namespace Gothic_II_Addon {
    
      void PrintScreen() {
       
        oCNpc* focusNpc = player->GetFocusNpc();
        if( focusNpc ) {
    
          zSTRING npcName = focusNpc->GetName( 0 );
          screen->PrintCXY( npcName );
        }
      }
    
    }
    
    void Loop() {
      TEngineVersion engineVersion = Union.GetEngineVersion();
    
      if( engineVersion == Engine_G1 )
        Gothic_I_Classic::PrintScreen();
    
      if( engineVersion == Engine_G1A )
        Gothic_I_Addon::PrintScreen();
    
      if( engineVersion == Engine_G2 )
        Gothic_II_Classic::PrintScreen();
    
      if( engineVersion == Engine_G2A )
        Gothic_II_Addon::PrintScreen();
    }

  • * This tutorial will use preprocessor instructions to separately compile cross-platform code. If this is too complicated for you, I’ll add an example code without them at the bottom of the post. However, I strongly recommend that you delve into the implementation using a preprocessor.

    To get started, take the previous plugin. We will test the work on Gothic II: NoTR, and then we will write the code for the rest.
    Set the project configuration to G2A Release.

    Workspace preparation
    For convenience, create a new source file under the directory Plugin:
    73955
    We will include the header UnionAfx.h, and then it is very important.

    Each engine interface is wrapped under namespaces (Gothic_I_Classic, Gothic_I_Addon, Gothic_II_Classic, Gothic_II_Addon ). Because we are writing test code for Gothic II NoTR, we define the space Gothic_II_Addon.

    As a result, we get a file with something like this:
    C++:
    #include <UnionAfx.h>
    
    namespace Gothic_II_Addon {
    
    }

    However, if we want to change the configuration of the project for a different engine, the compiler will throw out an error that the code inside Gothic_II_Addon is unknown to it.
    We’ll add a command over namespace #ifdef __G2A. Due to it, the compiler will be able to exclude all unnecessary code from construction when it will not be needed.
    This code will be the base for all cross-platform projects:
    C++:
    #include <UnionAfx.h>
    
    #ifdef __G2A
    namespace Gothic_II_Addon {
    
    }
    #endif



    Adding a loop Function
    The easiest way to call functions from different source code files is to use the keyword extern. At the end of our file, create a function Loop, and in Application.cpp define it via extern. Call the function from a predefined function Game_Loop.
    C++:
    // Example.cpp
    #include <UnionAfx.h>
    
    #ifdef __G2A
    namespace Gothic_II_Addon {
    
    }
    #endif
    
    void Loop() {
    
    }
    C++:
    // Application.cpp
    extern void Loop();
    
    void Game_Loop() {
      Loop();
    }



    Display text
    In Example.cpp in Gothic_II_Addon let's add text output function. Текст, как можно догадаться, будет the text, as you might guess, will be displayed cyclically by calling from the function Loop.
    The output will be produced via the viewport.screen.
    screen - is an instance of zCView (you can see it in the file zView.h). It has the method Print for printing text with coordinates, PrintCX with text horizontal align, PrintCY with vertical align and PrintCXY text in the screen center. Let's use the last one.
    Example: screen->PrintCXY( "Hello" );

    As the text, we take information about the name of the character in the focus of the player.
    The player is defined in global variable player of the class oCNpc.
    The class has two methods GetFocusVob - any object in focus and GetFocusNpc - npc in focus
    Example: oCNpc* focusNpc = playerGetFocusNpc();

    Next, you need to get the name npc. NPC has an array of names of 5 lines. We need the first one. We use the method GetName with index 0.
    Example: zSTRING npcName = focusNpc ->GetName( 0 )

    Now we are putting it all together. Do not forget to take into account that npc is not always present in focus, therefore, the conclusion must be made when the pointer to focusNpc no equal null. Therefore, we write all operations under the blockif( focusNpc )

    The code at this stage should look like this:
    C++:
    #include <UnionAfx.h>
    
    #ifdef __G2A
    namespace Gothic_II_Addon {
    
      void PrintScreen() {
       
        oCNpc* focusNpc = player->GetFocusNpc();
        if( focusNpc ) {
    
          zSTRING npcName = focusNpc->GetName( 0 );
          screen->PrintCXY( npcName );
        }
      }
    
    }
    #endif
    
    void Loop() {
    
    }



    Code extension to all the engines
    We copy the code, changing the namespace and the definition of the preprocessor in it:
    73956

    In function Loop add function call codePrintScreen.
    To call only those functions that relate to the corresponding engine, we use the instance of Union and request the current version of the engine using the method GetEngineVersion.
    TEngineVersion engineVersion = Union.GetEngineVersion();

    We put the checks for compliance and call the function.
    C++:
    if( engineVersion == Engine_G1 )
        Gothic_I_Classic::PrintScreen();

    But as mentioned earlier, when collecting a project for one engine, the rest will give an error:
    73957


    Therefore, we also wrap the calls under #ifdef, eliminating unnecessary calls from the construction:
    C++:
    void Loop() {
      TEngineVersion engineVersion = Union.GetEngineVersion();
    
    #ifdef __G1
      if( engineVersion == Engine_G1 )
        Gothic_I_Classic::PrintScreen();
    #endif
    
    #ifdef __G1A
      if( engineVersion == Engine_G1A )
        Gothic_I_Addon::PrintScreen();
    #endif
    
    #ifdef __G2
      if( engineVersion == Engine_G2 )
        Gothic_II_Classic::PrintScreen();
    #endif
    
    #ifdef __G2A
      if( engineVersion == Engine_G2A )
        Gothic_II_Addon::PrintScreen();
    #endif
    }

    Test compilation and launch
    We compile the plugin in the G2A Release configuration. The project should be quickly assembled, since 3 engines are disabled. We launch the plugin, go into the game and check that when focusing on npc its name is displayed.
    73958
    A duplicate name is displayed in the center of the screen. So everything is working properly.



    Final compilation and launch:
    Enable build configuration Release. We notice that all blocks wrapped previously under the command have become active #ifdef. This means that all the engines have entered the game and they are ready to work in cross-platform mode. We compile the project, it will take much longer than with a test run.

    Results:
    Gothic I Classic
    73959

    Gothic I Addon
    73960

    Gothic II Classic
    73961

    Gothic II Addon
    73962





    Code without preprocessor commands:
    C++:
    namespace Gothic_I_Classic {
    
      void PrintScreen() {
       
        oCNpc* focusNpc = player->GetFocusNpc();
        if( focusNpc ) {
    
          zSTRING npcName = focusNpc->GetName( 0 );
          screen->PrintCXY( npcName );
        }
      }
    
    }
    
    namespace Gothic_I_Addon {
    
      void PrintScreen() {
       
        oCNpc* focusNpc = player->GetFocusNpc();
        if( focusNpc ) {
    
          zSTRING npcName = focusNpc->GetName( 0 );
          screen->PrintCXY( npcName );
        }
      }
    
    }
    
    namespace Gothic_II_Classic {
    
      void PrintScreen() {
       
        oCNpc* focusNpc = player->GetFocusNpc();
        if( focusNpc ) {
    
          zSTRING npcName = focusNpc->GetName( 0 );
          screen->PrintCXY( npcName );
        }
      }
    
    }
    
    namespace Gothic_II_Addon {
    
      void PrintScreen() {
       
        oCNpc* focusNpc = player->GetFocusNpc();
        if( focusNpc ) {
    
          zSTRING npcName = focusNpc->GetName( 0 );
          screen->PrintCXY( npcName );
        }
      }
    
    }
    
    void Loop() {
      TEngineVersion engineVersion = Union.GetEngineVersion();
    
      if( engineVersion == Engine_G1 )
        Gothic_I_Classic::PrintScreen();
    
      if( engineVersion == Engine_G1A )
        Gothic_I_Addon::PrintScreen();
    
      if( engineVersion == Engine_G2 )
        Gothic_II_Classic::PrintScreen();
    
      if( engineVersion == Engine_G2A )
        Gothic_II_Addon::PrintScreen();
    }
 
Последнее редактирование модератором:

neromont


Модостроитель
Регистрация
12 Мар 2011
Сообщения
674
Благодарности
655
Баллы
245
Ну у меня так и было, только через for :D
 

neromont


Модостроитель
Регистрация
12 Мар 2011
Сообщения
674
Благодарности
655
Баллы
245
Это как говорится дело вкуса : )
C++:
for (int i = 1; lstItems; i++) {
        oCItem* pItem = lstItems->GetData();
        zSTRING result = pItem->GetName(0);
        screen->PrintCX(i * screen->FontY(), result);
        lstItems = lstItems->GetNextInList();
}

Хотя while логически и понятней и наглядней.
А FontY() - Что это за функция? Вероятно высота шрифта?
 
Последнее редактирование:

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.281
Благодарности
4.581
Баллы
625
Это как говорится дело вкуса : )
C++:
for (int i = 1; lstItems; i++) {
        oCItem* pItem = lstItems->GetData();
        zSTRING result = pItem->GetName(0);
        screen->PrintCX(i *  FontY(), result);
        lstItems = lstItems->GetNextInList();
}

Хотя while логически и понятней и наглядней.
А FontY() - Что это за функция? Вероятно высота шрифта?
Да, высота шрифта в готишных попугаях. Ширина и высота исчисляется как объект 8192 на 8192. Есть еще функция FontSize. В нее передаешь строку и он считает ее ширину.
 

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.281
Благодарности
4.581
Баллы
625
А каких-то констант для этих значений не прописано?
Например screen->MaxWidth ну или что-то подобное?
В AST были, а тут не думал даже. Просто знай что 8192 это максимум
 

neromont


Модостроитель
Регистрация
12 Мар 2011
Сообщения
674
Благодарности
655
Баллы
245
Да выводит 8192.
 

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.281
Благодарности
4.581
Баллы
625
Не совсем. Этот параметр определяет размер объекта zView относительно родителя. У screen родителя нет, так как это вьюпорт. Поэтому да, screen будет выводить 8192. А вот для потомков такое не прокатит.
 

Slavemaster


Модостроитель
Регистрация
10 Июн 2019
Сообщения
1.039
Благодарности
1.816
Баллы
240
Этот параметр определяет размер объекта zView относительно родителя.
То есть если someView->vsizex == 4096, то это значит, что вьюха в 2 раза меньше предка, но необязательно в 2 раза меньше экрана?
 

Haart

Участник форума
Регистрация
24 Окт 2011
Сообщения
188
Благодарности
106
Баллы
185
Теперь возник естественный вопрос, а как получить список всех доступных в скриптах предметов инвентаря?

Проверял только на Г2 НВ, за остальные не отвечаю.)
C++:
zCArray<int> insts; // все инстансы oCItem из скриптов
zCArray<zSTRING> instNames; // все инстансы из скриптов в ввиде строки. (ITMI_GOLD итд.)
void ParseItems() {
    int C_ITEM = parser->GetIndex("C_ITEM");
    // думаю и 100к хватит, но поставил 1кк, все равно парсить будет меньше секунды, а нужно это один раз.
    for (int i = 0; i < 1000000; i++) {
        zCPar_Symbol* ps = parser->GetSymbol(i);
        if (ps && ps->type == zPAR_TYPE_INSTANCE && ps->flags == zPAR_FLAG_CONST) {

            int idx = parser->GetBase(i);
            zSTRING psName = ps->name;
            // проверяем инстанс, родителем должен быть класс C_ITEM и instanceName не ITEM
            if (C_ITEM == idx && A psName != A"ITEM") {
                insts.Insert(i);
                instNames.Insert(psName);
            } else {
                ps = parser->GetSymbol(idx);
                // если вдруг родитель не C_ITEM, то смотрим, вдруг это прототип
                if (ps && ps->type == zPAR_TYPE_PROTOTYPE) {
                    // тогда идем глубже и повторяем действие
                    idx = parser->GetBase(idx);
                    if (C_ITEM == idx && A psName != A"ITEM") {
                        insts.Insert(i);
                        instNames.Insert(psName);
                    }
                }
            }
        }
    }
}
// TODO
 

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.281
Благодарности
4.581
Баллы
625
То есть если someView->vsizex == 4096, то это значит, что вьюха в 2 раза меньше предка, но необязательно в 2 раза меньше экрана?
Абсолютно. Вьюхи работают с относительными координатами. Для перевода пиксельных величин в относительные используется метод view->anx/any. А из относительных в пиксельные - nax/nay.
 

Slavemaster


Модостроитель
Регистрация
10 Июн 2019
Сообщения
1.039
Благодарности
1.816
Баллы
240
// думаю и 100к хватит, но поставил 1кк, все равно парсить будет меньше секунды, а нужно это один раз.
for (int i = 0; i < parser->symtab.GetNumInList(); i++)
Пост автоматически объединён:

По-проще будет, для LHiver тот же список итемов выдает.
C++:
    std::ofstream out("D:\\output.txt");
    auto c_item = parser->GetSymbol("C_ITEM");
    int badIndex = parser->GetIndex("ITEM");

    for (int i = 0; i < parser->symtab.GetNumInList(); i++)
    {
        auto sym = parser->GetSymbolInfo(i);

        if (i == badIndex || sym->type != zPAR_TYPE_INSTANCE || !sym->HasFlag(zPAR_FLAG_CONST))
        {
            continue;
        }

        for (auto parent = sym->GetParent(); parent; parent = parent->parent)
        {
            if (parent == c_item)
            {
                out << (string)sym->name << std::endl;
                break;   
            }
        }
    }
 
Последнее редактирование:

neromont


Модостроитель
Регистрация
12 Мар 2011
Сообщения
674
Благодарности
655
Баллы
245
auto, судя по всему, автоматически определяет тип объекта?
 
Последнее редактирование:

neromont


Модостроитель
Регистрация
12 Мар 2011
Сообщения
674
Благодарности
655
Баллы
245
Как по мне указание типа нагляднее.
 

JKow

Участник форума
Регистрация
5 Июл 2019
Сообщения
5
Благодарности
0
Баллы
95
How can I make function call every second? I want to create mana regeneration plugin.
 
Сверху Снизу