С Днём России!
  • 2.175
  • 6
Дорогие друзья! Поздравляю вас с Днём России! Этот праздник напоминает нам о богатой истории и культуре нашей страны, о её величии и непоколебимом духе народа! Желаю вам крепкого здоровья...
Новости Анонсирован Atomic Heart II
Новости Анонсирован ремастер Final Fantasy Tactics
  • 784
  • 1
Культовая пошаговая тактическая RPG от Square Enix получит второе дыхание. На проходящей выставке State of Play, где анонсируются игры для Play Station, состоялся анонс ремастера Final Fantasy...
Новости Моддеры Oblivion Remastered нашли способ внедрения абсолютно любых скриптов
  • 965
  • 4
Сообщество моддинга Oblivion Remastered (ORM) переживает настоящий расцвет. После долгих месяцев работы, энтузиасты научили игру работать с мощным скриптовым языком Lua, открыв перед создателями...
Создание кроссплатформенного плагина для Gothic на Union API: фокус на NPC и текст на экране

Гайд Создание кроссплатформенного плагина для 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();
}
Сверху Снизу