socketmud/doc/
socketmud/help/
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

/* include main header file */
#include "mud.h"

LIST  *eventqueue[MAX_EVENT_HASH];
STACK *event_free = NULL;
LIST  *global_events = NULL;
int    current_bucket = 0;


/* function   :: enqueue_event()
 * arguments  :: the event to enqueue and the delay time.
 * ======================================================
 * This function takes an event which has _already_ been
 * linked locally to it's owner, and places it in the
 * event queue, thus making it execute in the given time.
 */
bool enqueue_event(EVENT_DATA *event, int game_pulses)
{
  int bucket, passes;

  /* check to see if the event has been attached to an owner */
  if (event->ownertype == EVENT_UNOWNED)
  {
    bug("enqueue_event: event type %d with no owner.", event->type);
    return FALSE;
  }

  /* An event must be enqueued into the future */
  if (game_pulses < 1)
    game_pulses = 1;

  /* calculate which bucket to put the event in,
   * and how many passes the event must stay in the queue.
   */
  bucket = (game_pulses + current_bucket) % MAX_EVENT_HASH;
  passes = game_pulses / MAX_EVENT_HASH;

  /* let the event store this information */
  event->passes = passes;
  event->bucket = bucket;

  /* attach the event in the queue */
  AttachToList(event, eventqueue[bucket]);

  /* success */
  return TRUE;
}

/* function   :: dequeue_event()
 * arguments  :: the event to dequeue.
 * ======================================================
 * This function takes an event which has _already_ been
 * enqueued, and removes it both from the event queue, and
 * from the owners local list. This function is usually
 * called when the owner is destroyed or after the event
 * is executed.
 */
void dequeue_event(EVENT_DATA *event)
{
  /* dequeue from the bucket */
  DetachFromList(event, eventqueue[event->bucket]);

  /* dequeue from owners local list */
  switch(event->ownertype)
  {
    default:
      bug("dequeue_event: event type %d has no owner.", event->type);
      break;
    case EVENT_OWNER_GAME:
      DetachFromList(event, global_events);
      break;
    case EVENT_OWNER_DMOB:
      DetachFromList(event, event->owner.dMob->events);
      break;
    case EVENT_OWNER_DSOCKET:
      DetachFromList(event, event->owner.dSock->events);
      break;
  }

  /* free argument */
  free(event->argument);

  /* attach to free stack */
  PushStack(event, event_free);
}

/* function   :: alloc_event()
 * arguments  :: none
 * ======================================================
 * This function allocates memory for an event, and
 * makes sure it's values are set to default.
 */
EVENT_DATA *alloc_event()
{
  EVENT_DATA *event;

  if (StackSize(event_free) <= 0)
    event = malloc(sizeof(*event));
  else
    event = (EVENT_DATA *) PopStack(event_free);

  /* clear the event */
  event->fun        = NULL;
  event->argument   = NULL;
  event->owner.dMob = NULL;  /* only need to NULL one of the union members */
  event->passes     = 0;
  event->bucket     = 0;
  event->ownertype  = EVENT_UNOWNED;
  event->type       = EVENT_NONE;

  /* return the allocated and cleared event */
  return event;
}

/* function   :: init_event_queue()
 * arguments  :: what section to initialize.
 * ======================================================
 * This function is used to initialize the event queue,
 * and the first section should be initialized at boot,
 * the second section should be called after all areas,
 * players, monsters, etc has been loaded into memory,
 * and it should contain all maintanence events.
 */
void init_event_queue(int section)
{
  EVENT_DATA *event;
  int i;

  if (section == 1)
  {
    for (i = 0; i < MAX_EVENT_HASH; i++)
    {
      eventqueue[i] = AllocList();
    }

    event_free = AllocStack();
    global_events = AllocList();
  }
  else if (section == 2)
  {
    event = alloc_event();
    event->fun = &event_game_tick;
    event->type = EVENT_GAME_TICK;
    add_event_game(event, 10 * 60 * PULSES_PER_SECOND);
  }
}

/* function   :: heartbeat()
 * arguments  :: none
 * ======================================================
 * This function is called once per game pulse, and it will
 * check the queue, and execute any pending events, which
 * has been enqueued to execute at this specific time.
 */
void heartbeat()
{
  EVENT_DATA *event;
  ITERATOR Iter;

  /* current_bucket should be global, it is also used in enqueue_event
   * to figure out what bucket to place the new event in.
   */
  current_bucket = (current_bucket + 1) % MAX_EVENT_HASH;

  AttachIterator(&Iter, eventqueue[current_bucket]);
  while ((event = (EVENT_DATA *) NextInList(&Iter)) != NULL)
  {
    /* Here we use the event->passes integer, to keep track of
     * how many times we have ignored this event.
     */
    if (event->passes-- > 0) continue;

    /* execute event and extract if needed. We assume that all
     * event functions are of the following prototype
     *
     * bool event_function ( EVENT_DATA *event );
     *
     * Any event returning TRUE is not dequeued, it is assumed
     * that the event has dequeued itself.
     */
    if (!((*event->fun)(event)))
      dequeue_event(event);
  }
  DetachIterator(&Iter);
}

/* function   :: add_event_mobile()
 * arguments  :: the event, the owner and the delay
 * ======================================================
 * This function attaches an event to a mobile, and sets
 * all the correct values, and makes sure it is enqueued
 * into the event queue.
 */
void add_event_mobile(EVENT_DATA *event, D_MOBILE *dMob, int delay)
{
  /* check to see if the event has a type */
  if (event->type == EVENT_NONE)
  {
    bug("add_event_mobile: no type.");
    return;
  }

  /* check to see of the event has a callback function */
  if (event->fun == NULL)
  {
    bug("add_event_mobile: event type %d has no callback function.", event->type);
    return;
  }

  /* set the correct variables for this event */
  event->ownertype  = EVENT_OWNER_DMOB;
  event->owner.dMob = dMob;

  /* attach the event to the mobiles local list */
  AttachToList(event, dMob->events);

  /* attempt to enqueue the event */
  if (enqueue_event(event, delay) == FALSE)
    bug("add_event_mobile: event type %d failed to be enqueued.", event->type);
}

/* function   :: add_event_socket()
 * arguments  :: the event, the owner and the delay
 * ======================================================
 * This function attaches an event to a socket, and sets
 * all the correct values, and makes sure it is enqueued
 * into the event queue.
 */
void add_event_socket(EVENT_DATA *event, D_SOCKET *dSock, int delay)
{
  /* check to see if the event has a type */
  if (event->type == EVENT_NONE)
  {
    bug("add_event_socket: no type.");
    return;
  }

  /* check to see of the event has a callback function */
  if (event->fun == NULL)
  {
    bug("add_event_socket: event type %d has no callback function.", event->type);
    return;
  }

  /* set the correct variables for this event */
  event->ownertype   = EVENT_OWNER_DSOCKET;
  event->owner.dSock = dSock;

  /* attach the event to the sockets local list */
  AttachToList(event, dSock->events);

  /* attempt to enqueue the event */
  if (enqueue_event(event, delay) == FALSE)
    bug("add_event_socket: event type %d failed to be enqueued.", event->type);
}

/* function   :: add_event_game()
 * arguments  :: the event and the delay
 * ======================================================
 * This funtion attaches an event to the list og game
 * events, and makes sure it's enqueued with the correct
 * delay time.
 */
void add_event_game(EVENT_DATA *event, int delay)
{
  /* check to see if the event has a type */
  if (event->type == EVENT_NONE)
  {
    bug("add_event_game: no type.");
    return;
  }

  /* check to see of the event has a callback function */
  if (event->fun == NULL)
  {
    bug("add_event_game: event type %d has no callback function.", event->type);
    return;
  }

  /* set the correct variables for this event */
  event->ownertype = EVENT_OWNER_GAME;

  /* attach the event to the gamelist */
  AttachToList(event, global_events);

  /* attempt to enqueue the event */
  if (enqueue_event(event, delay) == FALSE)
    bug("add_event_game: event type %d failed to be enqueued.", event->type);
}

/* function   :: event_isset_socket()
 * arguments  :: the socket and the type of event
 * ======================================================
 * This function checks to see if a given type of event
 * is enqueued/attached to a given socket, and if it is,
 * it will return a pointer to this event.
 */
EVENT_DATA *event_isset_socket(D_SOCKET *dSock, int type)
{
  EVENT_DATA *event;
  ITERATOR Iter;

  AttachIterator(&Iter, dSock->events);
  while ((event = (EVENT_DATA *) NextInList(&Iter)) != NULL)
  {
    if (event->type == type)
      break;
  }
  DetachIterator(&Iter);

  return event;
}

/* function   :: event_isset_mobile()
 * arguments  :: the mobile and the type of event
 * ======================================================
 * This function checks to see if a given type of event
 * is enqueued/attached to a given mobile, and if it is,
 * it will return a pointer to this event.
 */
EVENT_DATA *event_isset_mobile(D_MOBILE *dMob, int type)
{
  EVENT_DATA *event;
  ITERATOR Iter;

  AttachIterator(&Iter, dMob->events);
  while ((event = (EVENT_DATA *) NextInList(&Iter)) != NULL)
  {
    if (event->type == type)
      break;
  }
  DetachIterator(&Iter);

  return event;
}

/* function   :: strip_event_socket()
 * arguments  :: the socket and the type of event
 * ======================================================
 * This function will dequeue all events of a given type
 * from the given socket.
 */
void strip_event_socket(D_SOCKET *dSock, int type)
{
  EVENT_DATA *event;
  ITERATOR Iter;

  AttachIterator(&Iter, dSock->events);
  while ((event = (EVENT_DATA *) NextInList(&Iter)) != NULL)
  {
    if (event->type == type)
      dequeue_event(event);
  }
  DetachIterator(&Iter);
}

/* function   :: strip_event_mobile()
 * arguments  :: the mobile and the type of event
 * ======================================================
 * This function will dequeue all events of a given type
 * from the given mobile.
 */
void strip_event_mobile(D_MOBILE *dMob, int type)
{
  EVENT_DATA *event;
  ITERATOR Iter;

  AttachIterator(&Iter, dMob->events);
  while ((event = (EVENT_DATA *) NextInList(&Iter)) != NULL)
  {
    if (event->type == type)
      dequeue_event(event);
  }
  DetachIterator(&Iter);
}

/* function   :: init_events_mobile()
 * arguments  :: the mobile
 * ======================================================
 * this function should be called when a player is loaded,
 * it will initialize all updating events for that player.
 */
void init_events_player(D_MOBILE *dMob)
{
  EVENT_DATA *event;

  /* save the player every 2 minutes */
  event = alloc_event();
  event->fun = &event_mobile_save;
  event->type = EVENT_MOBILE_SAVE;
  add_event_mobile(event, dMob, 2 * 60 * PULSES_PER_SECOND);
}

/* function   :: init_events_socket()
 * arguments  :: the mobile
 * ======================================================
 * this function should be called when a socket connects,
 * it will initialize all updating events for that socket.
 */
void init_events_socket(D_SOCKET *dSock)
{
  EVENT_DATA *event;

  /* disconnect/idle */
  event = alloc_event();
  event->fun = &event_socket_idle;
  event->type = EVENT_SOCKET_IDLE;
  add_event_socket(event, dSock, 5 * 60 * PULSES_PER_SECOND);
}