Your first plugin
What you build
A plugin named example.greeter with one PlayerScript: when a character enters the world, the server sends it a
system message. On the way you create the manifest, build the library against the LonelyIce core, load it in your
LonelyIce, pack it and install it from a local catalog, the way players get plugins.
The reference for every file used here is the plugin format; this guide uses only what a minimal plugin needs.
Before you start
- A LonelyIce build environment: the requirements and commands in Building LonelyIce. Build the
same LonelyIce release you run (the same
external/azerothcorecommit): a plugin library only works with the core build it was compiled against. - A LonelyIce installation that has been set up once (the wizard ran), to test the plugin in.
1. Create the folder
example.greeter/
plugin.json manifest
CMakeLists.txt calls AddPlugin()
src/greeter.cpp the script
plugin/plugin.cpp the library's entry point
The folder name does not matter to the build; the installed folder is always named after the manifest's id.
AddPlugin lays out plugin.json, the folders data, sql, conf, lua and client, and the files
settings.json, icon.png, LICENSE and README.md when they exist. Files elsewhere in the source folder are not
part of the plugin.
2. Write the manifest
plugin.json:
{
"format": 1,
"id": "example.greeter",
"version": "1.0.0",
"name": { "en": "Greeter" },
"description": { "en": "Greets every player on login." },
"authors": [ "You" ],
"license": "GPL-2.0-or-later",
"homepage": "https://github.com/you/example-greeter",
"core": { "abi": "lonelyice-ac-2" },
"platforms": [ "windows-x64" ],
"server": { "library": "example_greeter" }
}
| Field | Why it is here |
|---|---|
format |
Required. The loader skips a manifest without "format": 1 ("unsupported manifest format 0"). |
id |
Lowercase [a-z0-9.-]. Use author.feature for your own plugins. It cannot change once published. |
version |
x.y.z. The catalog and the package manager compare it. |
core.abi |
The AC_PLUGIN_ABI of the core you build against. LonelyIce releases use lonelyice-ac-2. |
platforms |
The server/<platform> folders the package carries. List only what you actually build. |
server.library |
Base name of the library: example_greeter.dll on Windows, libexample_greeter.so on Linux. |
server.apps is left out, so only the world server loads the plugin. All fields are described in
the manifest reference.
3. Write the script
src/greeter.cpp:
#include "Chat.h"
#include "Player.h"
#include "ScriptMgr.h"
class GreeterPlayerScript : public PlayerScript
{
public:
GreeterPlayerScript() : PlayerScript("GreeterPlayerScript", { PLAYERHOOK_ON_LOGIN }) { }
void OnPlayerLogin(Player* player) override
{
ChatHandler(player->GetSession()).PSendSysMessage("Welcome, {}! This server runs example.greeter.", player->GetName());
}
};
void AddGreeterScripts()
{
new GreeterPlayerScript();
}
This is ordinary AzerothCore script code: a ScriptObject subclass created in an Add*Scripts() function. A
PlayerScript lists the hooks it uses in its constructor.
4. Add the entry point
plugin/plugin.cpp:
#include "PluginApi.h"
void AddGreeterScripts();
AC_PLUGIN(AddGreeterScripts)
AC_PLUGIN exports the functions the loader looks for (AcorePlugin_Abi, AcorePlugin_Platform,
AcorePlugin_OnLoad, AcorePlugin_AddScripts). The world server calls AddGreeterScripts() after the static
modules' scripts. The other entry macros, AC_PLUGIN_ON_LOAD and AC_PLUGIN_ENTRY, are described in
Server library.
Keeping the entry point in its own file lets the same src/ build as a classic static module too, where the core's
module loader calls Add*Scripts() instead.
5. Write CMakeLists.txt
AddPlugin(example-greeter SOURCES src/greeter.cpp plugin/plugin.cpp)
AddPlugin(<target> SOURCES <files...> [CORE <library>] [LINK <libraries...>] [INCLUDES <dirs...>] [RUNTIME_FILES <files...>] [EXPORT_ALL]) is defined by the core (src/cmake/macros/AcorePlugin.cmake). It
reads id and server.library from plugin.json, builds the shared library, links it against the core's game
library (CORE shared for a plugin that also loads in authserver and dbimport), adds your src/ folder to the
include path and lays the plugin out under plugins/<id>/ next to the built programs.
6. Build it
Add your folder to the LonelyIce build with LONELYICE_PLUGIN_DIRS:
cmake -S . -B build -DBOOST_ROOT=<boost> -DOPENSSL_ROOT_DIR=<openssl> "-DLONELYICE_PLUGIN_DIRS=C:/dev/example.greeter"
cmake --build build --config RelWithDebInfo --target example-greeter
LONELYICE_PLUGIN_DIRSreplaces the default list (everyplugins/*folder of the LonelyIce repository with aplugin.json). Separate folders with;, and list the plugins yours depends on as well.- The LonelyIce build sets the core options itself: shared libraries,
AC_PLUGIN_ABI=lonelyice-ac-2, andAC_PLUGIN_SOURCE_DIRSfromLONELYICE_PLUGIN_DIRS. SettingAC_PLUGIN_SOURCE_DIRSdirectly has no effect there. - In a build of the core fork alone, pass the folder with
-DAC_PLUGIN_SOURCE_DIRS=<dir>and configure the core with-DWITH_DYNAMIC_LINKING=ON -DAC_PLUGIN_ABI=lonelyice-ac-2. Without an explicitAC_PLUGIN_ABIthe core usesazerothcore-dev, and LonelyIce refuses the plugin.
The result is laid out in build/bin/RelWithDebInfo/plugins/example.greeter/ (Visual Studio; single-configuration
generators use build/bin/plugins/):
example.greeter/
plugin.json
server/windows-x64/example_greeter.dll and the .pdb and link files, which packing leaves out
7. Try it in LonelyIce
-
Stop the server in the launcher. Plugins are loaded once when the server starts, and while it runs the package manager refuses every plugin change (the server holds
plugins/.cache/server.lock). -
Copy
example.greeterinto thepluginsfolder next toLonelyIce.exe(the launcher's About page in Settings shows the folder). -
Start the server. The Console tab shows the plugin being loaded:
Plugin example.greeter 1.0.0 (Greeter) -
Log in with a character. The chat shows
Welcome, <name>! This server runs example.greeter.
If the plugin is not loaded, the log says why on a line starting with Plugin example.greeter; see
Debugging plugins.
8. Pack it
LonelyIce.exe --pkg pack build\bin\RelWithDebInfo\plugins\example.greeter C:\dev\catalog
This writes C:\dev\catalog\example.greeter-1.0.0.zip (the plugin folder under a top folder named after the id,
without .pdb, .ilk, .exp, .lib and .a files) and prints the package's catalog entry on one line. With an
icon.png in the plugin folder it also copies the icon next to the zip as example.greeter-1.0.0.png and names it
in the entry.
9. Install it from a local catalog
--pkg pack does not write an index. Create C:\dev\catalog\index.json and paste the printed entry into
packages:
{
"format": 1,
"name": { "en": "My test catalog" },
"packages": [
{ "id": "example.greeter", "version": "1.0.0", "name": { "en": "Greeter" },
"description": { "en": "Greets every player on login." }, "core": "lonelyice-ac-2",
"platforms": [ "windows-x64" ], "url": "example.greeter-1.0.0.zip", "sha256": "…", "size": 12345 }
]
}
Then install it the way a player would:
-
Stop the server, then remove the copy from step 7, or the package manager keeps it as already installed:
LonelyIce.exe --pkg remove example.greeter. -
On the Plugins page open Catalogs… and add
C:\dev\catalog. The catalog view now lists Greeter; install it there. From the command line instead:LonelyIce.exe --pkg install example.greeter --index C:\dev\catalog--indexreplaces the configured catalogs for this one command. -
Start the server and log in again.
The launcher only offers packages whose core is its own ABI and whose platforms include the running platform.
The index format and hosting a catalog for others are covered in Hosting your own catalog.
Depending on another plugin
A plugin that uses another plugin's code names it in the manifest and links its target:
"depends": { "playerbots": ">=1.0.0" }
AddPlugin(example-greeter SOURCES src/greeter.cpp plugin/plugin.cpp LINK mod-playerbots)
Both folders go into LONELYICE_PLUGIN_DIRS. The loader loads dependencies first and skips your plugin when one is
missing, out of range or failed to load, or when a range cannot be read. Ask for the lowest version that has what
you use: a plugin that registers strategies, actions or triggers through PlayerbotExternalContexts needs
"playerbots": ">=1.0.4", the first version whose registries reach playerbots from another library
(Plugin cookbook). lonelyice.qol is a small real example.
Next steps
- Plugin settings: a config file and a group in the launcher's Settings.
- Shipping SQL: tables and rows in the server's databases.
- Client patches: new spells and other DBC rows without shipping game data.
- Publishing a package: putting it into the LonelyIce catalog.