/* ************************************************************************ * File: events.doc * * * * Usage: An explanation of how to use events * * * * Originally Written by Eric Green (ejg3@cornell.edu) * * Updated chapters by Doppleganger Software (baator@geocities.com) * ************************************************************************ */
Table of Contents
1. What are Events, and why do I want them?
2. Event Functions and Event Objects
3. Functions for Manipulating Events
4. Steps to Create a New Event Type
5. An Example Event Type
6. Tips for Create Events
For those who are unfamilir with the way CircleMUD works, here is a simple explanation.
The MUD runs, counting upwards continuously. When the MUD hits a multiple of a certain number, certain things happen. For example, every 2 seconds, the MUD checks to see if anyone is enganged in combat. If they are, it lets them try to hit each other. Every 10 seconds, mobs do their own functions. Every 75 seconds, the MUD also fires off what is called a tick. During a tick, a player is healed, hunger, thirst and drunkeness are changed, and spells are reduced in duration. This approach, however, is flawed. Smart players tend to count how long a tick lasts, and then get into the best position to heal, sleeping, just before then, to gain as much as they can. Also, by quitting the game, just before a tick, then returning immediately, you can avoid ever loosing time on a spell.
Events work quite differently. For example, say healing was controlled by events, using the Event Regeneration patch. If Piffer the Thief healed 25 points of damage every tick, it means that every 3 seconds in a tick, he would be healed one point. With event reneration, he would actually heal one hit point every 3 seconds. If spell durations were controlled by events, spells would be getting lower in duration every second the player is online.
Not only do events reduce players using loopholes, but they also can add to the realism of the game. For example, instead of spells firing off the second they are cast, you can have spells take a certain amount of time, to account for the casting of the spell itself.
From this point on, the chapters are fairly technical. If you want to learn how to use them (without learning all the nitty gritty details of WHY they work) skip ahead to chapter 4. However, it is useful to know all the functions, so skimming chapter 3 is highly recommended.
Each event type needs an:
Event function: An event function is a function with a prototype of: long (event_function)(void *event_obj) This function is called when the event occurs, and is passed the event object (see below). If the function returns a positive value, the event is reenqueued using the return value as the number of pulses in the future the event will reoccur. If the function returns 0 or a negative number, the event is not reenqueued. If the event is not to be reenqueued, the event function is responsible for freeing the event object. There is a define: #define EVENTFUNC(name) long (name)(void *event_obj) to be used when declaring an event function. Event object: The event object is any structure with the fields to store any data needed by the event function. The event object should not be a game object (such as a struct char_data), since this object is freed when events are canceled (see cancel_event() below). All unique data contained by the object should be freed by a call of free(event_obj). In other words, don't have event_obj include pointers to other structures which aren't pointed to elsewhere. It is also not advisable to have pointers in the event object unless the thing they point to has a pointer to this event and cancels the event when it is freed. Passing NULL as an event object is valid (providing the event function doesn't need any data).
The prototypes for the interface functions for events are provided in
events.h. They are:
void event_init(void);
This function initializes the event queue for all events. It is only called once, at the initialization of the game.
This function creates a new event. At the current time plus 'when', the function call func(event_obj); will be made. A pointer to the created event is returned. Never free() the event returned. Use event_cancel instead if you want to get rid of it prematurely.
This function cancels an event currently in the queue. The event and the event_obj are freed by this call.
This function is called once each pulse to process any pending events. It should not be used outside of the main loop.
Given event, this function returns the number of pulses until the event occurs. One example of a place it is used is to get the pulses left before an object timer expires, so its current state can be saved and restored later.
To add a new event type, you do not need to know anything about what's in
events.c, queue.c, or queue.h, the core of the event code. To create an
event type:
The best kind of example of code is one that can actually be used in a MUD.
To this end, a shutdown event is the example. This example is fairly simple
to install, and as it is based on existing code, it is pretty simple to
understand.
struct shutdown_event {
int type;
int time_left;
char *arg;
};
struct event *global_shutdown = NULL;
EVENTFUNC(shutdown)
{
struct shutdown_event *se = (struct shutdown_event *) event_obj;
int time_left, type;
char *arg;
extern int circle_shutdown, circle_reboot;
time_left = se->time_left;
type = se->type;
arg = str_dup(se->arg);
if (time_left == 0) {
switch (type) {
case 1:
touch(FASTBOOT_FILE);
circle_shutdown = circle_reboot = 1;
break;
case 2:
touch(KILLSCRIPT_FILE);
circle_shutdown = 1;
break;
case 3:
touch(PAUSE_FILE);
circle_shutdown = 1;
break;
}
} else if (time_left <= 10) {
se->time_left = time_left - 1;
sprintf(buf, "%s in %d second%s.\r\n", arg, time_left, ((time_left == 1) ? "" : "s"));
send_to_all(buf);
if (se->time_left == 0)
if (type == 1)
send_to_all("Rebooting.. come back in a minute or two.\r\n");
else
send_to_all("Shutting down for maintenance.\r\n");
return (1 RL_SEC);
} else if (time_left == 30) {
se->time_left = 10;
sprintf(buf, "%s in %d seconds.\r\n", arg, time_left);
send_to_all(buf);
return (20 RL_SEC);
} else if (time_left == 60) {
se->time_left = 30;
sprintf(buf, "%s in one minute.\r\n", arg);
send_to_all(buf);
return (30 RL_SEC);
} else {
se->time_left = time_left - 60;
sprintf(buf, "%s in %d minutes.\r\n", arg, (time_left/60));
send_to_all(buf);
return (60 RL_SEC);
}
free(event_obj);
return 0;
}
ACMD(do_shutdown)
{
int time_left, ttg = 0;
struct shutdown_event *event;
if (subcmd != SCMD_SHUTDOWN) {
send_to_char("If you want to shut something down, say so!\r\n", ch);
return;
}
two_arguments(argument, arg, buf);
if (!*arg) {
sprintf(buf, "(GC) Shutdown by %s.", REAL_NAME(ch));
log(buf);
send_to_all("Shutting down.\r\n");
circle_shutdown = 1;
} else {
CREATE(event, struct shutdown_event, 1);
if (!*buf)
time_left = 0;
else
time_left = atoi(buf);
if ((time_left < 0) || (time_left > 10)) {
send_to_char("That is not a valid amount of time!\r\n", ch);
return;
}
if (!str_cmp(arg, "reboot")) {
event->type = 1;
event->arg = str_dup("Reboot");
} else if (!str_cmp(arg, "die")) {
event->type = 2;
event->arg = str_dup("Shutdown");
} else if (!str_cmp(arg, "pause")) {
event->type = 3;
event->arg = str_dup("Shutdown");
} else if (!str_cmp(arg, "stop")) {
if (global_shutdown != NULL) {
send_to_all("Shutdown cancelled.\r\n");
log("(GC) Shutdown cancelled by %s.", GET_NAME(ch));
event_cancel(global_shutdown);
} else
send_to_char("There is no shutdown in the works!\r\n", ch);
return;
} else {
send_to_char("Unknown shutdown option.\r\n", ch);
return;
}
if (time_left == 0) {
ttg = 0;
event->time_left = 0;
log("(GC) %s by %s.", event->arg, GET_NAME(ch));
if (event->time_left == 1)
send_to_all("Rebooting.. come back in a minute or two.\r\n");
else
send_to_all("Shutting down for maintenance.\r\n");
} else if (time_left == 1) {
ttg = 30;
event->time_left = 30;
log("(GC) %s by %s in one minute.", event->arg, GET_NAME(ch));
send_to_all("The MUD will be going down in one minute.\r\n");
} else {
ttg = 60;
event->time_left = (time_left - 1) * 60;
log("(GC) %s by %s in %d minutes.", event->arg, GET_NAME(ch), time_left);
sprintf(buf, "The MUD will be going down in %d minutes.\r\n", time_left);
send_to_all(buf);
}
global_shutdown = event_create(shutdown, event, (ttg RL_SEC));
}
}
if (ch->spell) {
event_cancel(ch->spell);
ch->spell = NULL;
}
Tips for creating events: