Важно Форуму RPGRUSSIA 15 лет!
  • 2.237
  • 19
Друзья, сегодня нашему форуму исполняется 15 лет! Кажется, только вчера мы открывали первые разделы, спорили о правилах и радовались каждому новому участнику. Но годы пролетели - а мы всё здесь, и...
Новости Path of Exile 2: Патч 0.2.0 «Dawn of the Hunt» - краткое описание
  • 1.284
  • 0
Вчера вечером, в 22.00 по МСК, в прямом эфире вышла презентация по будущему патчу 0.2.0. В целом, игроки ждали нового класса и ребаланса существующих умений, но то что выкатили GGG на публику...
Новости Gothic 1 Remake - Demo (Nyras Prologue)
  • 4.689
  • 2
Ну что, заключённые, готовы к встрече с колонией? Мир, где каждый встречный мечтает вас зарезать за кусок хлеба, а единственный закон - сила. Вас ждёт совершенно новый пролог к легендарной...
Новости Большое интервью с HotA Crew - часть 2
  • 3.028
  • 0
HotA Crew о Кронверке и будущих обновлениях (часть 2) Какие герои будут вести армии Кронверка? Герои-воины зовутся Вожди, маги — Старейшины. Их параметры и способности подчеркнут сильные стороны...
4. Создание кроссплатформенного плагина для Gothic на Union API: фокус на NPC и текст на экране

Гайд 4. Создание кроссплатформенного плагина для Gothic на Union API: фокус на NPC и текст на экране

Gothic Union API: выводим имя NPC в фокусе на экран
In this tutorial, we’ll use preprocessor directives to create a cross-platform plugin for all Gothic engine versions. If this sounds complicated, there’s a version at the bottom of the post without preprocessor usage — but it’s highly recommended to understand how the version with preprocessing works.

Getting Started
We’ll continue from the previous plugin. The testing will be done in Gothic II: Night of the Raven (G2A), and then the code will be extended to other versions.
Make sure the project configuration is set to G2A Release.

Preparing the Workspace
To keep things clean, create a new source file under the Plugin directory:

Include the UnionAfx.h header — and this part is important.
Each engine interface is wrapped in a namespace:
  • Gothic_I_Classic
  • Gothic_I_Addon
  • Gothic_II_Classic
  • Gothic_II_Addon

Since we’re writing test code for Gothic II: NoTR, we define the namespace Gothic_II_Addon.

You should now have something like:
Код:
#include <UnionAfx.h>

namespace Gothic_II_Addon {

}
However, if you later change the project configuration to another engine, the compiler will throw an error — it doesn’t understand the contents of Gothic_II_Addon.

To fix that, wrap the namespace with a preprocessor check like #ifdef __G2A, so it’s excluded from builds for other engines:
Код:
#include <UnionAfx.h>

#ifdef __G2A
namespace Gothic_II_Addon {

}
#endif
This becomes the base pattern for all cross-platform Union plugins.

Adding a Cyclic Function
The easiest way to call functions across source files is with the extern keyword.
Let’s define a Loop() function in our file, and declare it in Application.cpp, where it’ll be called inside the pre-defined Game_Loop():

Example.cpp:
Код:
#include <UnionAfx.h>

#ifdef __G2A
namespace Gothic_II_Addon {

}
#endif

void Loop() {

}
Application.cpp:
Код:
extern void Loop();

void Game_Loop() {
  Loop();
}

Displaying Text On-Screen
Let’s now display text from Example.cpp.
The function will live in Gothic_II_Addon and will be called cyclically from Loop().

We’ll use the screen viewport (an instance of zCView) — found in zView.h.
Useful methods:
  • Print – draws text at given coordinates
  • PrintCX – centers horizontally
  • PrintCY – centers vertically
  • PrintCXY – centers both axes — we’ll use this one.
Example:
Код:
screen->PrintCXY("Hello");
Instead of a static string, let’s show the name of the NPC in the player’s focus.
  • The player is accessed via the global player (of type oCNpc)
  • To get the target NPC: player->GetFocusNpc()
  • To get their name: GetName(0) – returns the first of five name strings
So:
Код:
oCNpc* focusNpc = player->GetFocusNpc();
if (focusNpc) {
  zSTRING npcName = focusNpc->GetName(0);
  screen->PrintCXY(npcName);
}
Wrap that inside a function:
Код:
#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() {

}

Expanding to All Engines
We now duplicate this function for each engine version, replacing the namespace and preprocessor define:

Inside Loop(), call the right PrintScreen() based on the engine.
Use Union’s GetEngineVersion():
Код:
TEngineVersion engineVersion = Union.GetEngineVersion();
However, compiling all engines in one config causes errors from incompatible namespaces.
So wrap these calls with the same #ifdef blocks:
Код:
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 Build and Execution
Compile the plugin with G2A Release configuration.
The build will be fast since only one engine is enabled.
Launch the plugin, load into the game and check: if you aim at an NPC, their name should appear in the center of the screen.

If it works — great!

Final Build (All Engines)
Now switch to the Release config.
All previously wrapped code under #ifdef becomes active.

The build will take longer, but the plugin will now support all 4 engines cross-platform.
  • Gothic I Classic
  • Gothic I Addon
  • Gothic II Classic
  • Gothic II Addon
Version Without Preprocessor Macros
You can also write everything directly without using #ifdef.
This isn’t scalable but is easier to read at first:
Код:
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();
}
Сверху Снизу