-
Notifications
You must be signed in to change notification settings - Fork 0
Creating Plugins
This page will guide you through writing your first halfMod plugin.
#include "halfmod.h"This header contains everything you need to interact with the halfMod API. It also includes all of its dependencies:
#include <vector>
#include <unordered_map>
#include <regex>
#include <string>
#include <chrono>
#include <ratio>So you don't need to include any of these headers yourself.
#include "str_tok.h"
#include "nbtmap.h"Both of these headers come included with halfMod and add some useful features.
str_tok.h is a string manipulation library catered towards specific tasks.
nbtmap.h parses NBT string data with easy and fast reading or creating of NBT string data to an organized map.
Plugins each have their own scope that other plugins cannot see, but extensions can. This means when creating extensions, one must be careful to use extremely specific variable names, otherwise the variable will be shared across both.
In other words, avoid simple variable names like bool enabled = true;.
For this reason, it is best to use pre-processor definitions for things like the version number:
#define VERSION "v0.1.0"Every plugin must contain an int onPluginStart() function or it will fail to load.
This function must be extern "C" so that halfMod will be able to find and call it upon loading.
#include "halfmod.h"
#include <cstdlib> // Used for rand
#define VERSION "v0.1.0"
extern "C" int onPluginStart(hmHandle &handle, hmGlobal *global)
{
recallGlobal(global);recallGlobal will simply store the hmGlobal pointer.
The hmGlobal struct contains all the information about the Minecraft server, connected players, admins, cvars, extensions, and more.
Most importantly this struct contains the file descriptor to the open socket with halfShell. Without this, your plugin will not be able to communicate with the Minecraft server.
Later in the code, you can retrieve the pointer by calling the function again:
hmGlobal *global = recallGlobal(NULL);But this is mostly used internally, we won't be using this on our first plugin.
handle.pluginInfo(
"First", // Name of the plugin
"nigel", // Author of the plugin
"My First Plugin Ever!", // Description of the plugin
VERSION, // Version number of the plugin
"http://halfmod.justca.me/" // URL for the plugin
);This is also required to be within the onPluginStart event for the plugin to load. This defines basic info about the plugin.
handle.createConVar(
"first_version", // Name of the ConVar
VERSION, // Default value of the ConVar
"My first ConVar!", // Description of the ConVar
FCVAR_CONSTANT // ConVar flags
);This will create a cvar that will be set to the plugin version number.
FCVAR_CONSTANT means this cvar's value cannot change.
This is entirely optional, but is good practice.
handle.hookConVarChange(
handle.createConVar(
"first_enable", // Name of the ConVar
"true", // Default value of the ConVar
"Enable or disable my first plugin!", // Description of the ConVar
FCVAR_NOTIFY, // ConVar flags
true, // Whether or not this ConVar has a minimum value restraint
0.0, // The minimum value this ConVar can be set to
true, // Whether or not this ConVar has a maximum value restraint
1.0 // The maximum value this ConVar can be set to
),
"enablePlugin" // The name of the function to call when this ConVar changes
);This will create a cvar that will be used to enable or disable the plugin. It defaults to true.
FCVAR_NOTIFY means a message will be displayed to everyone on the server when this cvar's value changes.
This cvar's value can only be set between 0.0 and 1.0, 0.0 means false, anything else is true.
"enablePlugin" is a string providing the name of a function to call when the cvar's value is changed. We will define this function later.
handle.regAdminCmd(
"hm_diamonds", // Command name
"diamondsCmd", // Name of function to call
FLAG_ADMIN, // Flag required to use this command
"Have some diamonds!" // Description of the command
);This will register a new admin command called hm_diamonds that only admins with the FLAG_ADMIN flag will be able to use.
"diamondsCmd" is the name of the function that will be called when this command is used. We will define this function later.
Commands that begin with hm_ can be used from the chat by replacing the hm_ with a !, for example: !diamonds.
handle.regConsoleCmd(
"hm_hello", // Command name
"helloCmd", // Name of function to call
"Hello World!" // Description of the command
);This will register a command that any user can use called hm_hello. We will define the helloCmd function later.
This is all we will have in our onPluginStart event, so let's recap the current code:
#include "halfmod.h"
#include <cstdlib> // Used for rand
#define VERSION "v0.1.0"
extern "C" int onPluginStart(hmHandle &handle, hmGlobal *global)
{
recallGlobal(global);
handle.pluginInfo(
"First", // Name of the plugin
"nigel", // Author of the plugin
"My First Plugin Ever!", // Description of the plugin
VERSION, // Version number of the plugin
"http://halfmod.justca.me/" // URL for the plugin
);
handle.createConVar(
"first_version", // Name of the ConVar
VERSION, // Default value of the ConVar
"My first ConVar!", // Description of the ConVar
FCVAR_CONSTANT // ConVar flags
);
handle.hookConVarChange(
handle.createConVar(
"first_enable", // Name of the ConVar
"true", // Default value of the ConVar
"Enable or disable my first plugin!", // Description of the ConVar
FCVAR_NOTIFY, // ConVar flags
true, // Whether or not this ConVar has a minimum value restraint
0.0, // The minimum value this ConVar can be set to
true, // Whether or not this ConVar has a maximum value restraint
1.0 // The maximum value this ConVar can be set to
),
"enablePlugin" // The name of the function to call when this ConVar changes
);
handle.regAdminCmd(
"hm_diamonds", // Command name
"diamondsCmd", // Name of function to call
FLAG_ADMIN, // Flag required to use this command
"Have some diamonds!" // Description of the command
);
handle.regConsoleCmd(
"hm_hello", // Command name
"helloCmd", // Name of function to call
"Hello World!" // Description of the command
);
srand(time(NULL));
return 0;
}By returning 0, we are allowing the plugin to load. If you return non-0, halfMod will assume the plugin ran into a problem and needs to be unloaded.
This can be useful if your plugin requires an extension and the extension is not loaded.
We are seeding time to rand so we can use it later.
bool firstEnabled = true;We will use this as the variable to hold the value of our first_enable cvar.
extern "C" int enabledPlugin(hmConVar &cvar, std::string oldVal, std::string newVal)
{
firstEnabled = cvar.getAsBool();
return 0;
}Since we hooked the function as a string, we need to extern "C" the function so halfMod can find it. We can also pass a function pointer when hooking if you prefer that method, but for the case of this exercise, we will continue like this.
By returning 0, we are allowing halfMod to process any other possible hooks as well. If we return non-0 then any other hooks from this plugin or others will not process. Of course, this cvar shouldn't have any other hooks, but it is good practice to return 0 unless you know what you're doing.
extern "C" int diamondsCmd(hmHandle &handle, const hmPlayer &client, std::string args[], int argc)
{
if (firstEnabled)
hmSendRaw("give " + client.name + " minecraft:diamond " + std::to_string((rand() % 64) + 1));
return 0;
}client is the player that ran the command.
args contains all the arguments that were passed to the command. args[0] is always the name of the command.
For this command, we don't care about the arguments.
argc is the size of args.
hmSendRaw will execute Minecraft commands as the server.
Returning 0 allows halfMod to keep processing the message, meaning other commands or events like onPlayerText can still be triggered.
Returning non-0 prevents this behaviour.
extern "C" int helloCmd(hmHandle &handle, const hmPlayer &client, std::string args[], int argc)
{
if (firstEnabled)
hmSendRaw("say Hello " + client.name + "!");
return 0;
}Command callback functions are always the same regardless if they were registered as admin commands or not.
args are allocated and deleted internally, you don't need to worry about it from your plugins.
That about wraps up this exercise. Click here for more info about the halfMod API.
Use ./src/plugins/compile.sh to compile the plugin.
Full plugin code:
#include "halfmod.h"
#include <cstdlib> // Used for rand
#define VERSION "v0.1.0"
extern "C" int onPluginStart(hmHandle &handle, hmGlobal *global)
{
recallGlobal(global);
handle.pluginInfo(
"First", // Name of the plugin
"nigel", // Author of the plugin
"My First Plugin Ever!", // Description of the plugin
VERSION, // Version number of the plugin
"http://halfmod.justca.me/" // URL for the plugin
);
handle.createConVar(
"first_version", // Name of the ConVar
VERSION, // Default value of the ConVar
"My first ConVar!", // Description of the ConVar
FCVAR_CONSTANT // ConVar flags
);
handle.hookConVarChange(
handle.createConVar(
"first_enable", // Name of the ConVar
"true", // Default value of the ConVar
"Enable or disable my first plugin!", // Description of the ConVar
FCVAR_NOTIFY, // ConVar flags
true, // Whether or not this ConVar has a minimum value restraint
0.0, // The minimum value this ConVar can be set to
true, // Whether or not this ConVar has a maximum value restraint
1.0 // The maximum value this ConVar can be set to
),
"enablePlugin" // The name of the function to call when this ConVar changes
);
handle.regAdminCmd(
"hm_diamonds", // Command name
"diamondsCmd", // Name of function to call
FLAG_ADMIN, // Flag required to use this command
"Have some diamonds!" // Description of the command
);
handle.regConsoleCmd(
"hm_hello", // Command name
"helloCmd", // Name of function to call
"Hello World!" // Description of the command
);
srand(time(NULL));
return 0;
}
bool firstEnabled = true;
extern "C" int enabledPlugin(hmConVar &cvar, std::string oldVal, std::string newVal)
{
firstEnabled = cvar.getAsBool();
return 0;
}
extern "C" int diamondsCmd(hmHandle &handle, const hmPlayer &client, std::string args[], int argc)
{
if (firstEnabled)
hmSendRaw("give " + client.name + " minecraft:diamond " + std::to_string((rand() % 64) + 1));
return 0;
}
extern "C" int helloCmd(hmHandle &handle, const hmPlayer &client, std::string args[], int argc)
{
if (firstEnabled)
hmSendRaw("say Hello " + client.name + "!");
return 0;
}