Plugins enhance the Crossfire game with optional features.
They allow for special behaviours that can't be done through simple mapping, or require complex scripting actions - the only thing limiting is the imagination of the coder!
Current examples include:
Currently existing plugins are :
Name | Description | Status | Title for the event object |
---|---|---|---|
cfanim | animate objects | experimental | Animator |
cfpython | run Python scripts | working | Python |
cflogger | logs events to a SQLITE database | experimental | SqliteLogger |
cfnewspaper | newspaper generation | experimental | Sqlite Newspaper, needs cflogger |
citylife | adds/removes NPCs in maps, to make towns lively | apparently stable | citylife |
cfrhg | random house generator, adds random maps to unlinked exits in specified maps | apparently stable | cfrhg |
| | | |
template | not a real plugin, but a skeleton to create new ones | up-to-date |
Plugin system works through hooks. A hook is merely an event taking place, to which plugins can respond. Default server behavior can sometimes be overridden totally.
The full list of events corresponds to the event_xxx archetypes, found in the /system folder of the archetypes.
Events are either global or object-specific. Examples of global events include a player joining or exiting the game, a map being loaded. Examples of object-specific events include an item being applied, a player attacking a monster.
check parameters and such :)
Just add an event_xxx object into your object's inventory, and fill the title
field with the plugin's title as defined in the table above. See the specific plugin documentation for optional arguments that need to be set to that object. Note that the slaying
field must be set too whatever the plugin.
When an object-specific event is raised, affected object is sent to the plugin.
For some events, returning a non-zero value will prevent the default server processing to take place, allowing to override things. be more specific :)
The plugin registered function will get the following parameters. See specific events for the meaning of the field.
object*
which is the current object containing the event.object*
, can be NULL.object*
, can be NULL.const char*
, can be NULL.int
{} meaning?object*
representing the actual event object linking to plugin. Will be NULL
for global events. Its fields mean:subtype
: event codetitle
: plugin nameslaying
: plugin specific valuetalk_info*
(see dialog.h file) containing dialog information. Only meaningful and not NULL for an EVENT_SAY event.Follows the full list of object-specific events.
Archetype: event_apply
This event is generated whenever the object is applied or unapplied.
Archetype: event_attack
Bound to an object, it is triggered when the object is attacked (with a weapon or a spell). In this case, “op” is the object, “activator” is what hits the item, “third” is the weapon or spell used (can be equal to “activator”). Returning a non-zero value cancels the attack.
Archetype: event_attacks
This event is used in two cases:
Archetype: event_bought
This event is triggered when the associated object was paid by a player, but not yet marked as paid.
“op” is the item being bought, “activator” is the player buying.
Returning a non-zero value leaves the item as unpaid and prevents the player from buying still unpaid items.
Archetype: event_close
Generated when a container is closed.
Archetype: event_death
Generated when the object dies.
Archetype: event_destroy
Used when the object is totally destroyed, either by a map reset or destruction by another item.
“op” is the object being destroyed.
The return value is ignored.
Archetype: event_drop
Generated when the object is dropped on the floor.
WhoAmI
is about to be dropped by WhoIsActivator
Return 0 to allow the drop, any other value to prevent dropping.
Archetype: event_pickup
WhoAmI
is about to be picked up by WhoIsActivator
and put into WhoIsOther
(will be WhoIsActivator if put into inventory, or container else). Event is called when all checks (weight, container, levitation, …) are done, creature picking up can really pick up.
Return 0 to allow to pickup, non zero to prevent from picking up.
Archetype: event_say
Generated when someone says something around the object.
Archetype: event_selling
Generated when op
is being sold by activator
. Return 1 to prevent selling, 0 to allow.
Archetype: event_stop
Generated for a thrown object, when the object is stopped for some reason (wall, max distance, …). Will not be called when hitting a living creature, but Attack
is called.
Note that the object is inside a dummy container at this time. It can be removed.
Archetype: event_time
Generated each time the object gets an opportunity to move.
Return non zero value to prevent the regular processing to occur.
Archetype: event_throw
Generated when the object is thrown. The object is still in the thrower's inventory, and can be removed to abort being thrown.
Archetype: event_trigger
Used for various objects, like traps, teleporters or triggers. Generated when those objects are used (for example, when a player passes through a teleporter).
op
is the item, activator
is the player, message
is what player is trying to write. Return non zero to prevent writing. Event is generated before length is checked for overflow.op
is the item, activator
is the player, third
is the spell being inscribed. Return non zero to prevent writing. Event is generated after all checks are done (player can really write the scroll, he didn't read it accidentally) but before sp/gr are decreased.op
is the door, activator
the player, and third
the lockpicking skillArchetype: event_timer
Generated when the timer connected triggered.
Archetype: event_user
Only triggered through a plugin call. This event can represent anything.
“op” is the object on which the event is triggered, other parameters are specific to the use-case.
Those concern the game as a whole or can't be bound to a specific object. Those events may be “registered” by a plugin (it means that the plugin requests to get a message each time one of those events happens).
Plugin should use provided server callbacks to register itself. See the specific plugin documentation.
Event | Description | Parameters | Note |
---|---|---|---|
Born | Generated when a new character is created. | object* pointing to player | |
Clock | Generated at each game loop. | (none) | When no player is logged, the loop “stops”, meaning that clock events are not generated anymore! |
Crash | Generated when a server crash does occur. It is not a recursive event, so if a crash occur from *inside* the crash event handling, it is not called a second time, preventing infinite loops to occur. | (none) | This event is not implemented for now. |
PLAYER_DEATH | Generated whenever a player dies. Note that CFPython calls this the 'death' event. | object* pointing to player, object* pointing to killer (can be NULL). | |
Gkill | Generated whenever something/someone is killed. | object* pointing to dead object, object* pointing to killer. | |
Kick | Generated when a player was kicked by a DM. | object* pointing to kicked player, const char* containing the parameter the DM used to kick | |
Login | Generated whenever a player logs into the game. | player* pointing to player, const char* containing the hostname of the client. | |
Logout | Generated whenever a player logs out of the game. | player* pointing to player, const char* containing the hostname of the client. | |
Mapenter | Generated whenever someone enters a map. | object* pointing to the player, map* pointing to the map | |
Mapleave | Generated whenever someone leaves a map. | object* pointing to the player, map* pointing to the map | |
Mapload | Generated when a map is loaded in memory. | map* pointing to the map | |
Mapreset | Generated each time a map is reset. | map* pointing to the map | |
Mapunload | Generated when a map is being unloaded from memory. | map* pointing to the map | |
Muzzle | Generated when a player was muzzled by a DM. | object* pointing to muzzled player, const char* containing the parameter the DM used to muzzle | |
Playerdeath | Generated whenever a player dies. | object* pointing to player, object* pointing to killer (can be NULL). | |
Remove | Generated when a player character is removed from the game (“quit” command). | object* pointing to the player | |
Shout | Generated whenever someone shouts something. | object* pointing to talking player, const char* containing the message, int containing the priority | |
Tell | Generated whenever someone tells something. | object* pointing to talking player, const char* containing the message, object* containing the recipient of the message |
A plugin can register a custom game command. This command will work exactly like other commands from the player's point of view, accepting arguments and such.
A plugin can override an existing Crossfire command simply by declaring a command with the same name. Server command will be ignored.
Creating a plugin requires some knowledge of Crossfire's internals.
Plugins should use the common
plugin helper library (located in plugins/common
directory), and use provided functions to manipulate Crossfire data. This library handles the actual communication with the server, and does some runtime checks on values.
Warning: a plugin can easily crash the server and/or corrupt files if care is not taken in data manipulation. Some checks are done through assert
, but there are times checks can't be done, thus it's the responsability of the plugin writer to take care of the plugin logic.
Some rules for plugin writing:
free
or equivalent on data the server allocated. Use provided free functions.common
, socket
, …) and call functions. This may work on some platforms (Linux seems to handle that just fine), but will not work on others (example is Windows). Thus always use provided common plugin interfaceexpand/check: is an object removed automatically from player's inventory? is view updated? is fix_object() called?
The simplest way is to look at the template plugin, available in plugins/template directory of the server sources
In trunk, there now is a script, plugins/template/create_plugin.sh
, that will automate all steps below, excluding running configure && make && make install
. The syntax is: create_plugin.sh cftest “Test plugin”
from the plugins/template
directory.
In case you want to create a plugin manually, here are step by step instructions to create a basic plugin. Plugin name will be assumed to be cftest
. Paths are relative to server root.
cftest
, in plugins
plugins/template/plugin_template.c
to plugins/template/cftest.c
plugins/template/include/plugin_template.h
to plugins/cftest/include/cftest.h
plugins/cftest/cftest.c
and plugins/cftest/include/cftest.h
to correct the file names in the includes and such. You may also want to change the plugin's namePLUGIN_NAME
should be set to a unique identifier that will be your plugin's name for the slaying
field of events.plugins/template/Makefile.am
to plugins/cftest/Makefile.am
plugin_template
to cftest
(that fixes paths and a few macros)plugins/Makefile.am
, add cftest
to the SUBDIRS
lineconfigure.ac
AC_OUTPUT([Makefile
plugins/cftest/Makefile
Whether you used the script or manually created a plugin, you need to do the following steps:
autoconf && automake && configure && make && make install
Now you need to write your actual plugin logic.
link to common plugin documentation (generated from doxygen hopefully)
specific Windows stuff / workspace/project issue
This paragraph only concerns people wishing to write a plugin without using the common
interface, or extending it. It is not intended for everyday use.
Important note: the API, and other parts, are valid for the trunk
. While it may still be correct for the branch
, one should ensure it works the same way. In particular, branch
uses returned void*
value to return values to plugin, whereas trunk
uses an additional pointer parameter.
All functions available to plugins share the same prototype, f_plug_api
, defined in include/plugin.h
.
All parameters are sent through the use of variable argument lists, using the macros va_start
, va_arg
and va_end
.
All functions accept as first parameter an int* type
. This parameter is used by server and plugin to exchange the type of a modified/returned value. Apart its presence for the variable argument list handling, it is used to check data coherence.
Data is exchanged as either a value or a pointer to a Crossfire structure. When the server needs to return a value to the plugin (for function wrapping, properties getting, …), it expects the last argument to be a pointer to a variable of the returned value's type.
Data types available are defined in include/plugin.h
.
Special case: when a CFAPI_STRING
needs to be transferred, 2 parameters are expected, a pointer to the buffer and an integer to the buffer's size (which can't be always be determined automatically).
Note that signed/unsigned variables are transferred as signed, and should be cast appropriately when needed.
Access (get/set) to properties of the Crossfire objects, maps, structures is done through property wrappers.
Syntax for wrapper is eg cf_object_get_property(int* type, object* ob, int propcode, (property type)* value)
.
expand
This wraps specific Crossfire functions. The calling convention is to send parameters in the same order as the wrapped function, and add as the last parameter a pointer to a variable of the same type as the return value which will receive the actual function return value. The hook return value will be a constant indicating whether the function was called or another error occurred ( expand/check/make that true in the code ;p)
Let's take for example the wrapper for get_ob_key_value
.
The function prototype is:
const char *get_ob_key_value(const object *op, const char *const key)
(the return value is a shared string)
The server-side
hook is (note the NULL return value, since this is being worked on remove when fixed):
void* cfapi_object_get_key(int* type, ...) { va_list args; const char* keyname; const char** value; object* op; va_start(args, type); op = va_arg(args, object*); keyname = va_arg(args, const char*); value = va_arg(args, const char**); va_end(args); *value = get_ob_key_value(op, keyname); *type = CFAPI_SSTRING; return NULL; }
The plugin wrapper function, defined in the common library
, is:
const char* cf_object_get_key(object* op, const char* keyname) { int type; const char* value; cfapiObject_get_key(&type, op, keyname, &value); return value; }
where cfapiObject_get_key
is the matching hook.
In some cases, objects are linked through the use of a next
field, with a first_
pointer somewhere (includes objects, friendly list, maps, archetypes). Since the next
field will certainly be a property for the object, a convention for getting the first_
item is to call this property getter with a NULL value for the object.
Thus, for the partylist
linked list, the server-side functions looks like (parts edited out):
void* cfapi_party_get_property(int* type, ...) { [snipped] case CFAPI_PARTY_PROP_NEXT: rparty = va_arg(args, partylist**); *rparty = (party ? party->next : get_firstparty()); *type = CFAPI_PPARTY; break;
The common library's function for first party is:
partylist* cf_party_get_first(void) { int type; partylist* value; cfapiParty_get_property(&type, NULL, CFAPI_PARTY_PROP_NEXT, &value); assert(type == CFAPI_PPARTY); return value; }
whereas access to the next party is done through:
partylist* cf_party_get_next(partylist* party) { int type; partylist* value; cfapiParty_get_property(&type, party, CFAPI_PARTY_PROP_NEXT, &value); assert(type == CFAPI_PPARTY); return value; }