Overview
This page contains complete, working examples from the official CoD4 Unleashed Server plugin codebase. These examples demonstrate real-world usage of the plugin API.All examples are from the
plugins/ directory in the source code.Hello World (C++)
A minimal C++ plugin that demonstrates the basic structure.Source Code
cpptest_plugin.cpp
#include "../pinc.h"
#include <cstring>
#include <string>
/*
===========================
OnInit callback
This is a function called
right after the plugin
is loaded.
===========================
*/
PCL int OnInit() {
Com_Printf("Hello, world! :D\n");
return 0; // 0 => Initialization successful
}
/*
============================
OnInfoRequest callback
This function is mandatory
============================
*/
PCL void OnInfoRequest(pluginInfo_t *info) {
// MANDATORY: Handler version
info->handlerVersion.major = PLUGIN_HANDLER_VERSION_MAJOR;
info->handlerVersion.minor = PLUGIN_HANDLER_VERSION_MINOR;
// OPTIONAL: Plugin metadata
info->pluginVersion.major = 1;
info->pluginVersion.minor = 0;
strncpy(info->fullName, "An example C++ plugin.", sizeof(info->fullName));
strncpy(info->shortDescription, "This is the plugin's short description.",
sizeof(info->shortDescription));
strncpy(info->longDescription, "This is the plugin's long description.",
sizeof(info->longDescription));
}
Build Script
makedll32
#!/bin/bash
gcc -m32 -Wall -O1 -s -fPIC -shared -o cpptest.so cpptest_plugin.cpp
Usage
Anti-Spam Plugin
A complete chat spam prevention plugin written in C. This demonstrates:- Event handling (
OnMessageSent) - CVars for configuration
- Player data tracking
- Memory management
- Time-based logic
Source Code
antispam_plugin.c
#include <time.h>
#include "../pinc.h"
#define ANTISPAM_MAXMESSAGES 30
// Per-player data structure
typedef struct {
int lastMessage;
int messages[ANTISPAM_MAXMESSAGES];
} userData_t;
// Plugin global data
typedef struct {
userData_t *players;
int maxPlayers;
cvar_t *maxMPM; // Max messages per minute
cvar_t *minAP; // Min admin power
cvar_t *minMD; // Min message delay
cvar_t *renMD; // Renewed message delay
} antispam_t;
antispam_t data;
void Antispam_Initialize() {
if (data.players != NULL) {
// Safe to call on already freed pointers
Plugin_Free(data.players);
}
data.maxPlayers = Plugin_GetSlotCount();
data.players = (userData_t *)Plugin_Malloc(sizeof(userData_t) * data.maxPlayers);
memset(data.players, 0x00, sizeof(userData_t) * data.maxPlayers);
}
PCL int OnInit() {
data.maxPlayers = Plugin_GetSlotCount();
// Register configuration cvars
data.maxMPM = Plugin_Cvar_RegisterFloat(
"antispam_maxMessagesPerMinute",
8, // Default: 8 messages per minute
0, // Min: 0 (disable chat)
30, // Max: 30 messages per minute
0,
"Count of maximum messages a player can send in a minute. 0 disables chat."
);
data.minAP = Plugin_Cvar_RegisterInt(
"antispam_minAdminPower",
50, // Default: 50 (admin level)
0,
100,
0,
"Minimum power points which disable the check. 0 means enabled for everyone."
);
data.minMD = Plugin_Cvar_RegisterInt(
"antispam_minMessageDelay",
4, // Default: 4 seconds between messages
0,
60,
0,
"Time after sending a message before player can chat again. 0 disables."
);
data.renMD = Plugin_Cvar_RegisterBool(
"antispam_renewedMessageDelay",
qfalse,
0,
"Do messages sent before minMessageDelay passes prolong the delay?"
);
Antispam_Initialize();
return 0;
}
PCL void OnMessageSent(char *message, int slot, qboolean *show, int type) {
// Don't process if message is already hidden
if (!(*show))
return;
if (!message) {
*show = qfalse;
return;
}
int uid = Plugin_GetPlayerUid(slot);
time_t t = time(NULL);
// Admins bypass the check
if (data.minAP->integer != 0 && uid >= data.minAP->integer) {
return;
}
// Check minimum delay between messages
if (data.minMD->integer != 0 &&
(t - data.players[slot].lastMessage < data.minMD->integer)) {
*show = qfalse;
if (data.renMD->boolean)
data.players[slot].lastMessage = t;
return;
}
// Max messages per minute check
int i, j = 0;
// Remove messages older than 60 seconds
for (i = 0;
(i < ANTISPAM_MAXMESSAGES) &&
(data.players[slot].messages[i] != 0) &&
(t - data.players[slot].messages[i] > 60);
++i);
// Shift array to remove old messages
if (i != 0) {
for (j = 0;
j < ANTISPAM_MAXMESSAGES &&
i < ANTISPAM_MAXMESSAGES &&
data.players[slot].messages[j] != 0;
++i, ++j) {
data.players[slot].messages[j] = data.players[slot].messages[i];
data.players[slot].messages[i] = 0;
}
}
// Clear remaining slots
for (i = j; i < ANTISPAM_MAXMESSAGES; ++i) {
data.players[slot].messages[i] = 0;
}
// j now holds count of messages in last 60 seconds
if (j < data.maxMPM->integer) {
// Allow the message
data.players[slot].messages[j] = t;
data.players[slot].lastMessage = t;
*show = qtrue;
} else {
// Block the message
*show = qfalse;
int waitTime = 60 - (t - data.players[slot].messages[0]);
Plugin_ChatPrintf(slot,
"^2AntiSpam: you can send next chat message in %d seconds.",
waitTime);
}
}
PCL void OnInfoRequest(pluginInfo_t *info) {
info->handlerVersion.major = PLUGIN_HANDLER_VERSION_MAJOR;
info->handlerVersion.minor = PLUGIN_HANDLER_VERSION_MINOR;
info->pluginVersion.major = 1;
info->pluginVersion.minor = 0;
strncpy(info->fullName,
"IceOps antispam plugin by TheKelm",
sizeof(info->fullName));
strncpy(info->shortDescription,
"This plugin is used to prevent spam in the ingame chat.",
sizeof(info->shortDescription));
strncpy(info->longDescription,
"This plugin is used to prevent spam in the ingame chat. "
"To personalize the settings, set corresponding cvars. "
"Copyright (c) 2013 IceOps. Visit us at www.iceops.in",
sizeof(info->longDescription));
}
Configuration
server.cfg
// Load the antispam plugin
loadPlugin antispam
// Configure settings
set antispam_maxMessagesPerMinute "8" // Max 8 messages per minute
set antispam_minAdminPower "50" // Admins (power 50+) bypass
set antispam_minMessageDelay "4" // 4 second delay between messages
set antispam_renewedMessageDelay "0" // Don't renew delay on spam
Usage Scenarios
- Basic Spam Prevention
- Strict Mode
- Admin-Only Chat
set antispam_maxMessagesPerMinute "10"
set antispam_minMessageDelay "2"
set antispam_maxMessagesPerMinute "5"
set antispam_minMessageDelay "5"
set antispam_renewedMessageDelay "1"
set antispam_maxMessagesPerMinute "0"
set antispam_minAdminPower "50"
Key Features
Time-Based Limiting
Tracks messages over a 60-second sliding window
Admin Bypass
Admins with sufficient power level bypass restrictions
Message Delay
Enforces minimum time between messages
User Feedback
Tells players how long to wait before chatting again
Custom Commands Example
Create custom server commands with this example.#include "../pinc.h"
// Command: /teleport <x> <y> <z>
void TeleportCommand_f(void) {
int argc = Plugin_Cmd_Argc();
int slot = Plugin_Cmd_GetInvokerSlot();
// Must be called by a player
if (slot < 0) {
Com_Printf("This command must be used by a player\n");
return;
}
// Check arguments
if (argc != 4) {
Plugin_ChatPrintf(slot, "^1Usage: teleport <x> <y> <z>");
return;
}
// Parse coordinates
float x = atof(Plugin_Cmd_Argv(1));
float y = atof(Plugin_Cmd_Argv(2));
float z = atof(Plugin_Cmd_Argv(3));
// Get player entity
gentity_t *ent = Plugin_GetGentityForEntityNum(slot);
if (ent == NULL) {
Plugin_ChatPrintf(slot, "^1Error: Could not get entity");
return;
}
// Set origin (teleport)
ent->r.currentOrigin[0] = x;
ent->r.currentOrigin[1] = y;
ent->r.currentOrigin[2] = z;
Plugin_ChatPrintf(slot, "^2Teleported to %.0f %.0f %.0f", x, y, z);
Com_Printf("%s teleported to %.0f %.0f %.0f\n",
Plugin_GetPlayerName(slot), x, y, z);
}
// Command: /stats
void StatsCommand_f(void) {
int slot = Plugin_Cmd_GetInvokerSlot();
if (slot < 0) {
Com_Printf("This command must be used by a player\n");
return;
}
// Get player stats
clientScoreboard_t stats = Plugin_GetClientScoreboard(slot);
char *name = Plugin_GetPlayerName(slot);
int uid = Plugin_GetPlayerUid(slot);
// Display stats
Plugin_BoldPrintf(slot, "^2=== Your Statistics ===");
Plugin_ChatPrintf(slot, "^3Name: ^7%s", name);
Plugin_ChatPrintf(slot, "^3UID: ^7%d", uid);
Plugin_ChatPrintf(slot, "^3Kills: ^2%d", stats.kills);
Plugin_ChatPrintf(slot, "^3Deaths: ^1%d", stats.deaths);
Plugin_ChatPrintf(slot, "^3Assists: ^7%d", stats.assists);
Plugin_ChatPrintf(slot, "^3Score: ^7%d", stats.score);
// Calculate K/D ratio
float kd = stats.deaths > 0 ? (float)stats.kills / stats.deaths : stats.kills;
Plugin_ChatPrintf(slot, "^3K/D Ratio: ^7%.2f", kd);
}
PCL int OnInit() {
// Register commands
Plugin_AddCommand("teleport", TeleportCommand_f, 80); // Admin only
Plugin_AddCommand("stats", StatsCommand_f, 0); // Anyone
Com_Printf("Custom commands loaded!\n");
Com_Printf(" - teleport <x> <y> <z> (power 80)\n");
Com_Printf(" - stats (power 0)\n");
return 0;
}
PCL void OnInfoRequest(pluginInfo_t *info) {
info->handlerVersion.major = PLUGIN_HANDLER_VERSION_MAJOR;
info->handlerVersion.minor = PLUGIN_HANDLER_VERSION_MINOR;
info->pluginVersion.major = 1;
info->pluginVersion.minor = 0;
strncpy(info->fullName, "Custom Commands Plugin", sizeof(info->fullName));
strncpy(info->shortDescription, "Adds teleport and stats commands",
sizeof(info->shortDescription));
}
Player Welcome Message
Simple plugin that welcomes players when they join.#include "../pinc.h"
cvar_t *welcome_enabled;
cvar_t *welcome_message;
PCL int OnInit() {
welcome_enabled = Plugin_Cvar_RegisterBool(
"welcome_enabled",
qtrue,
CVAR_ARCHIVE,
"Enable welcome messages"
);
welcome_message = Plugin_Cvar_RegisterString(
"welcome_message",
"^2Welcome to our server, ^7%s^2!",
CVAR_ARCHIVE,
"Welcome message (%s = player name)"
);
return 0;
}
PCL void OnClientEnterWorld(int clientNum) {
// Check if enabled
if (!Plugin_Cvar_GetBoolean(welcome_enabled)) {
return;
}
// Get player info
char *name = Plugin_GetPlayerName(clientNum);
const char *msg = Plugin_Cvar_GetString(welcome_message);
// Format and send message
char buffer[256];
snprintf(buffer, sizeof(buffer), msg, name);
Plugin_ChatPrintf(clientNum, buffer);
Plugin_BoldPrintf(clientNum, "^2Welcome!");
}
PCL void OnInfoRequest(pluginInfo_t *info) {
info->handlerVersion.major = PLUGIN_HANDLER_VERSION_MAJOR;
info->handlerVersion.minor = PLUGIN_HANDLER_VERSION_MINOR;
strncpy(info->fullName, "Welcome Message Plugin", sizeof(info->fullName));
}
GSC Integration Example
Extend GSC with custom functions.#include "../pinc.h"
// GSC Function: customDamage(player, amount)
void GScr_CustomDamage() {
if (Plugin_Scr_GetNumParam() != 2) {
Plugin_Scr_Error("Usage: customDamage(player, amount)");
return;
}
gentity_t *player = Plugin_Scr_GetEntity(0);
int damage = Plugin_Scr_GetInt(1);
if (player == NULL) {
Plugin_Scr_Error("Invalid player entity");
return;
}
// Apply damage (simplified)
player->health -= damage;
if (player->health <= 0) {
player->health = 0;
Plugin_Scr_AddString("killed");
} else {
Plugin_Scr_AddString("damaged");
}
}
// GSC Function: getServerUptime()
void GScr_GetServerUptime() {
int uptime = Plugin_GetServerTime() / 1000; // Convert to seconds
Plugin_Scr_AddInt(uptime);
}
PCL int OnInit() {
Plugin_ScrAddFunction("customDamage", GScr_CustomDamage);
Plugin_ScrAddFunction("getServerUptime", GScr_GetServerUptime);
Com_Printf("GSC functions registered\n");
return 0;
}
PCL void OnInfoRequest(pluginInfo_t *info) {
info->handlerVersion.major = PLUGIN_HANDLER_VERSION_MAJOR;
info->handlerVersion.minor = PLUGIN_HANDLER_VERSION_MINOR;
strncpy(info->fullName, "GSC Integration Plugin", sizeof(info->fullName));
}
result = customDamage(player, 50);
iPrintLn("Result: " + result);
uptime = getServerUptime();
iPrintLn("Server uptime: " + uptime + " seconds");
Best Practices from Examples
Memory Management
Memory Management
Always use
Plugin_Malloc() and Plugin_Free():// Good
data = Plugin_Malloc(size);
Plugin_Free(data);
// Bad - will cause issues!
data = malloc(size);
free(data);
Initialization
Initialization
Initialize all resources in
OnInit(), return 0 for success:PCL int OnInit() {
// Register cvars
// Allocate memory
// Register commands
return 0; // Success
// return -1; // Would fail loading
}
Error Checking
Error Checking
Always validate inputs and check for errors:
void *data = Plugin_Malloc(size);
if (data == NULL) {
Plugin_Error(P_ERROR_DISABLE, "Out of memory");
return;
}
Configuration with CVars
Configuration with CVars
Make plugins configurable with cvars:
cvar_t *enabled = Plugin_Cvar_RegisterBool(
"myplugin_enabled",
qtrue,
CVAR_ARCHIVE, // Save to config
"Enable plugin features"
);
Compilation
All examples use the same build script:makedll32
#!/bin/bash
# For C plugins
gcc -m32 -Wall -O1 -s -fPIC -shared -o myplugin.so myplugin.c
# For C++ plugins
g++ -m32 -Wall -O1 -s -fPIC -shared -o myplugin.so myplugin.cpp
The
-m32 flag is required - plugins must be compiled as 32-bit.Next Steps
API Reference
Detailed documentation of all plugin functions
Development Guide
Learn how to develop your own plugins