Free Web Hosting Provider - Web Hosting - E-commerce - High Speed Internet - Free Web Page
Search the Web

/* ************************************************************************
*  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)       *
************************************************************************ */

Death Gate Events

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



1. What are Events, and why do I want them?


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.



2. Event Functions and Event Objects


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).



3. Functions for Manipulating Events


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.

struct event *event_create(EVENTFUNC(*func), void *event_obj, long when);
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.

void event_cancel(struct event *event);
This function cancels an event currently in the queue. The event and the event_obj are freed by this call.

void event_process(void);
This function is called once each pulse to process any pending events. It should not be used outside of the main loop.

long event_time(struct event *event);
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.



4. Steps to Create a New Event Type


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:

  1. Declare an event object structure with a structure define. As well, this is a good time to define where the event object will be stored.
  2. Create your event function. This is easily found by looking for any function that is called with EVENTFUNC().
  3. Construct your event object, and call event_create() where needed.
  4. Any place that the 'owner' of the event can be destroyed, call event_cancel(). For players and mobs, this is in the extract_char() function. For objects, this is the extract_obj() function. Most other 'owner' types (rooms, zones, global, etc.) won't ever be destroyed, so an event_cancel will not be needed.



5. An Example 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.

Step 1: Declare Event Object Structure


In act.wizard.c, add the following lines near the beginning (after the #includes)
struct shutdown_event {
  int type;
  int time_left;
  char *arg;
};

struct event *global_shutdown = NULL;

These two are fairly self explanatory. The event structure contains all data needed by the shutdown event. The use of the *arg is used to keep a word record, for use in any logs, or global echos.

Step 2: Create the Event Function


Later on in act.wizard.c (just before ACMD(do_shutdown) is best) add this:
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;
}

Here is a quick run-down of the event. If the shutdown event has 0 seconds left, it shuts down the MUD. If the time left is under 10 seconds, it reduces the time by 1 second, sends a message to the MUD saying how long is left, and sets the event to go off again after 1 second. This will simulate a 1 second count-down. If the time left is 30 seconds, it sends a message to the MUD, and sets the event to fire off in 20 seconds, at which point only 10 seconds will be left. If the time left is 60 seconds, it will send a message to the MUD and set the event to fire off in 30 seconds, at which point 30 seconds will be left. If there is over a minute to go, it sends a message to the MUD saying how many minutes are left, reduces the time by one minute, and sets the event to fire off in one minute.
As you can see, the procedure of the event is quite simple. Say the event was set to go off in a total of 3 minutes. Here is what would happen:
Message to the MUD (3 minutes left)
Event set to go off in 1 minute, time decreased 1 minute
wait 1 minute
Message to the MUD (2 minutes left)
Event set to go off in 1 minute, time decreased 1 minute
wait 1 minute
Message to the MUD (1 minute left)
Event set to go off in 30 seconds, time decreased 30 seconds
wait 30 seconds
Message to the MUD (30 seconds left)
Event set to go off in 20 seconds, time decreased 20 seconds
wait 20 seconds
Message to the MUD (10 seconds left)
Event set to go off in 1 second, time decreased 1 second
wait 1 second
Message to the MUD (9 seconds left)
...
countdown to 1 second
...
Message to MUD (1 second left)
Event set to go off in 1 second, time decreased 1 second
wait 1 second
Shutdown MUD

Step 3: Construct the Event Object


At this point, we need to create the event itself. This means that we have to modify the ACMD(doshutdown) to accept timed shutdowns, as well as normal ones. Here is the replacement function:
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));
  }
}

Now, this is almost identical to the standard do_shutdown, except that it gets a new parameter. If you type "shutdown reboot" as usual, it will do it immediately. However, if you type "shutdown reboot 3" it will start a 3 minute countdown. Now, to explain what is going on. If the time to shutdown is called at 1 minute, it will set the event to go off in 30 seconds, just as the event function does. If it is more than 1 minute, it will set it to go off in one minute, starting the process above. If there is no time set, it will have the event time set at 0 seconds.

Step 4: Owner cleanup


In this case, there is no owner to clean up. The event is global, and as such, does not need to be cleaned up. Also, since the event terminates the program itself, no freeing of memory is needed.
However, to show an example of this step, say that we had defined an event named *spell in the char_data structure. In the extract_char function, you would add a few lines that look like this:
  if (ch->spell) {
    event_cancel(ch->spell);
    ch->spell = NULL;
  }

What this does is cancels the event when the character quits the MUD, and frees up the memory. If this is not done, the event will try to go off, and generally crash the MUD, as the character the event was attached to no longer exists.



6. Tips for Creating Events


Tips for creating events: