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();
}