#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);
}