/* cque.c */
#include <stdio.h>
#include <ctype.h>
#include <fcntl.h>
#include <string.h>
#ifdef WANT_ANSI
#ifdef __STDC__
#include <stdlib.h>
#endif /* __STDC */
#endif /* WANT_ANSI */
#ifdef XENIX
#include <sys/signal.h>
#else
#include <signal.h>
#endif /* xenix */
#include "mudconf.h"
#include "config.h"
#include "db.h"
#include "htab.h"
#include "interface.h"
#include "match.h"
#include "externs.h"
#include "flags.h"
void process_command(dbref player, dbref cause, int interactive,
char *command, char *args[], int nargs);
/* ---------------------------------------------------------------------------
* clean_que: Free command and arguments passed to queue routines, if needed
*/
static void clean_que(int key, char *arg1, char *argv[], int nargs)
{
int i;
if (key & RU_ARG1_TAKE) {
if (arg1 != NULL) free_lbuf(arg1);
}
if (key & RU_ARG2_TAKE) {
for (i=0; i<nargs; i++) {
if (argv[i] != NULL) free_lbuf(argv[i]);
}
}
}
/* ---------------------------------------------------------------------------
* add_to: Adjust an object's queue or semaphore count.
*/
static int add_to(dbref player, int am, int attrnum)
{
int num, aflags;
dbref aowner;
char buff[20];
char *atr_gotten;
num = atoi(atr_gotten = atr_get(player, attrnum, &aowner, &aflags));
free_lbuf(atr_gotten);
num += am;
if (num)
sprintf(buff, "%d", num);
else
*buff = '\0';
atr_add_raw(player, attrnum, buff);
return (num);
}
/* ---------------------------------------------------------------------------
* give_que: Thread a queue block onto the high or low priority queue
*/
static void give_que (BQUE *tmp)
{
tmp->next = NULL;
tmp->left = -1;
/* Thread the command into the correct queue */
if (Typeof(tmp->cause) == TYPE_PLAYER) {
if (mudstate.qlast != NULL) {
mudstate.qlast->next = tmp;
mudstate.qlast = tmp;
} else
mudstate.qlast = mudstate.qfirst = tmp;
} else {
if (mudstate.qllast) {
mudstate.qllast->next = tmp;
mudstate.qllast = tmp;
} else
mudstate.qllast = mudstate.qlfirst = tmp;
}
}
/* ---------------------------------------------------------------------------
* que_want: Do we want this queue entry?
*/
static int que_want (BQUE *entry, dbref ptarg, dbref otarg)
{
if ((ptarg != NOTHING) && (ptarg != Owner(entry->player))) return 0;
if ((otarg != NOTHING) && (otarg != entry->player)) return 0;
return 1;
}
/* ---------------------------------------------------------------------------
* halt_que: Remove all queued commands from a certain player
*/
int halt_que(dbref player, dbref object)
{
BQUE *trail, *point, *next;
int numhalted;
numhalted = 0;
if ((player == NOTHING) && (object == NOTHING))
return 0;
/* Player queue */
for (point = mudstate.qfirst; point; point = point->next)
if (que_want(point, player, object)) {
numhalted++;
point->player = 0;
}
/* Object queue */
for (point = mudstate.qlfirst; point; point = point->next)
if (que_want(point, player, object)) {
numhalted++;
point->player = 0;
}
/* Wait queue */
for (point=mudstate.qwait,trail=NULL; point; point = next)
if (que_want(point, player, object)) {
numhalted++;
if (trail)
trail->next = next = point->next;
else
mudstate.qwait = next = point->next;
clean_que(RU_ARG1_TAKE|RU_ARG2_TAKE, point->comm,
point->env, point->nargs);
free_qentry(point);
} else
next = (trail = point)->next;
/* Semaphore queue */
for (point=mudstate.qsemfirst,trail=NULL; point; point = next)
if (que_want(point, player, object)) {
numhalted++;
if (trail)
trail->next = next = point->next;
else
mudstate.qsemfirst = next = point->next;
if (point == mudstate.qsemlast)
mudstate.qsemlast = trail;
add_to(point->sem, -1, A_SEMAPHORE);
clean_que(RU_ARG1_TAKE|RU_ARG2_TAKE, point->comm,
point->env, point->nargs);
free_qentry(point);
} else
next = (trail = point)->next;
if (player == NOTHING)
player = Owner(object);
giveto(player, (mudconf.waitcost * numhalted));
if (object == NOTHING)
atr_clr(player, A_QUEUE);
else
add_to(player, -numhalted, A_QUEUE);
return numhalted;
}
/* ---------------------------------------------------------------------------
* do_halt: Command interface to halt_que.
*/
void do_halt (dbref player, dbref cause, int key, char *target)
{
dbref player_targ, obj_targ;
int numhalted;
/* Figure out what to halt */
if (!target || !*target) {
obj_targ = NOTHING;
if (key & HALT_ALL) {
player_targ = NOTHING;
} else {
player_targ = Owner(player);
if (Typeof(player) != TYPE_PLAYER)
obj_targ = player;
}
} else {
obj_targ = match_controlled(player, target);
if (obj_targ == NOTHING) return;
if (key & HALT_ALL) {
notify(player, "Can't specify a target and /all");
return;
}
if (Typeof(obj_targ) == TYPE_PLAYER) {
player_targ = obj_targ;
obj_targ = NOTHING;
} else {
player_targ = NOTHING;
}
}
numhalted = halt_que(player_targ, obj_targ);
if (Quiet(player)) return;
if (numhalted == 1)
notify(Owner(player), "1 queue entries removed.");
else
notify(Owner(player),
tprintf("%d queue entries removed.", numhalted));
}
/* ---------------------------------------------------------------------------
* nfy_que: Notify commands from the queue and perform or discard them.
*/
static int nfy_que(dbref sem, int key, int count)
{
BQUE *point, *trail, *next;
int num, aflags;
dbref aowner;
char *str;
str = atr_get(sem, A_SEMAPHORE, &aowner, &aflags);
num = atoi(str);
free_lbuf(str);
if (num > 0) {
num = 0;
for (point=mudstate.qsemfirst,trail=NULL; point; point = next) {
if (point->sem == sem) {
num++;
if (trail)
trail->next = next = point->next;
else
mudstate.qsemfirst = next = point->next;
if (point == mudstate.qsemlast)
mudstate.qsemlast = trail;
/* Either run or discard the command */
if (key != NFY_DRAIN) {
give_que(point);
} else {
giveto(point->player,
mudconf.waitcost);
clean_que(RU_ARG1_TAKE|RU_ARG2_TAKE,
point->comm, point->env,
point->nargs);
free_qentry(point);
}
} else {
next = (trail = point)->next;
}
/* If we've notified enough, exit */
if ((key == NFY_NFY) && (num >= count))
next = NULL;
}
} else {
num = 0;
}
/* Update the sem waiters count */
if (key == NFY_NFY)
add_to(sem, -count, A_SEMAPHORE);
else
atr_clr(sem, A_SEMAPHORE);
return num;
}
/* ---------------------------------------------------------------------------
* do_notify: Command interface to nfy_que
*/
void do_notify (dbref player, dbref cause, int key, char *what, char *count)
{
dbref thing;
int loccount;
init_match(player, what, NOTYPE);
match_exit();
match_neighbor();
match_possession();
match_me();
match_here();
match_absolute();
match_player();
if ((thing = noisy_match_result()) < 0) {
notify(player, "No match.");
} else if (!controls(player, thing) && !(Flags(thing) & LINK_OK)) {
notify(player, "Permission denied.");
} else {
if (count && *count)
loccount = atoi(count);
else
loccount = 1;
if (loccount > 0)
nfy_que(thing, key, loccount);
}
}
/* ---------------------------------------------------------------------------
* setup_que: Set up a queue entry.
*/
static BQUE *setup_que(dbref player, dbref cause, int key, char *command,
char *args[], int nargs)
{
int a;
BQUE *tmp;
/* Can we run commands at all? */
if (Halted(player))
return NULL;
/* make sure player can afford to do it */
a = mudconf.waitcost;
if ((random() % mudconf.machinecost) == 0) a++;
if (!payfor(player, a)) {
notify(Owner(player), "Not enough money to queue command.");
return NULL;
}
/* Wizards and their objs may queue up to db_top+1 cmds. Players are
* limited to QUEUE_QUOTA. -mnp */
a = mudconf.queuemax;
if (Wizard(player))
a += mudstate.db_top + 1;
if (add_to(Owner(player), 1, A_QUEUE) > a) {
notify(Owner(player),
"Run away objects: too many commands queued. Halted.");
halt_que(Owner(player), NOTHING);
/* halt also means no command execution allowed */
s_Flags(player, Flags(player) | HALT);
return NULL;
}
/* We passed all the tests. Create the queue entry. */
tmp = alloc_qentry("setup_que.qblock");
if (key & RU_ARG1_TAKE) {
tmp->comm = command; /* save the lbuf */
} else {
tmp->comm = alloc_lbuf("setup_que.arg1");
strcpy(tmp->comm, command);
}
tmp->player = player;
tmp->left = -1;
tmp->next = NULL;
tmp->sem = NOTHING;
tmp->cause = cause;
/* Set up the environment */
if (nargs > NUM_ENV_VARS) {
if (key & RU_ARG2_TAKE) {
for (a=NUM_ENV_VARS; a<nargs; a++) {
free_lbuf(args[a]);
}
}
nargs = NUM_ENV_VARS;
}
for (a=0; a<nargs; a++) {
if (args[a] != NULL) {
if (key & (RU_ARG2_TAKE|RU_ARG2_TMPL)) {
tmp->env[a] = args[a];
} else {
tmp->env[a] = alloc_lbuf("setup_que.argv");
strcpy(tmp->env[a], args[a]);
}
} else {
tmp->env[a] = NULL;
}
}
tmp->nargs = nargs;
return tmp;
}
/* ---------------------------------------------------------------------------
* wait_que: Add commands to the wait or semaphore queues.
*/
void wait_que(dbref player, dbref cause, int key, int wait, dbref sem,
char *command, char *args[], int nargs)
{
BQUE *tmp;
if (mudconf.control_flags & CF_INTERP)
tmp = setup_que(player, cause, key, command, args, nargs);
else
tmp = NULL;
if (tmp == NULL) {
clean_que(key, command, args, nargs);
return;
}
tmp->left = wait;
tmp->sem = sem;
if (sem == NOTHING) {
/* No semaphore, put on wait queue if wait value specified.
* Otherwise put on the normal queue. */
if (wait <= 0) {
give_que(tmp);
} else {
tmp->next = mudstate.qwait;
mudstate.qwait = tmp;
}
} else {
tmp->next = NULL;
if (mudstate.qsemlast != NULL)
mudstate.qsemlast->next = tmp;
else
mudstate.qsemfirst = tmp;
mudstate.qsemlast = tmp;
}
}
/* ---------------------------------------------------------------------------
* do_wait: Command interface to wait_que
*/
void do_wait (dbref player, dbref cause, int key, char *event, char *cmd,
char *cargs[], int ncargs)
{
dbref thing, aowner;
int howlong, num, aflags;
char *what;
/* If arg1 is all numeric, do simple (non-sem) timed wait. */
if (is_number(event)) {
howlong = atol(event);
wait_que (player, cause, key, howlong, NOTHING, cmd,
cargs, ncargs);
return;
}
/* Semaphore wait with optional timeout */
what = parse_to(&event, '/', 0);
init_match(player, what, NOTYPE);
match_exit();
match_neighbor();
match_possession();
match_me();
match_here();
match_absolute();
match_player();
if ((thing = noisy_match_result()) < 0) {
notify(player, "No match.");
} else if (!controls(player, thing) && !(Flags(thing) & LINK_OK)) {
notify(player, "Permission denied.");
} else {
/* Get timeout, default -1 */
if (event && *event)
howlong = atol(event);
else
howlong = -1;
add_to(thing, 1, A_SEMAPHORE);
what = atr_get(thing, A_SEMAPHORE, &aowner, &aflags);
num = atoi(what);
free_lbuf(what);
if (num <= 0)
thing = NOTHING;
wait_que(player, cause, key, howlong, thing, cmd,
cargs, ncargs);
}
}
/* ---------------------------------------------------------------------------
* do_second: Check the wait and semaphore queues for commands to remove.
*/
void do_second(int nsecs)
{
BQUE *trail, *point, *next;
char *cmdsave;
/* move contents of low priority queue onto end of normal one
* this helps to keep objects from getting out of control since
* its affects on other objects happen only after one seconds
* this should allow @halt to be type before getting blown away
* by scrolling text */
if ((mudconf.control_flags & CF_DEQUEUE) == 0) return;
cmdsave = mudstate.debug_cmd;
mudstate.debug_cmd = (char *)"< do_second >";
if (mudstate.qlfirst) {
if (mudstate.qlast)
mudstate.qlast->next = mudstate.qlfirst;
else
mudstate.qfirst = mudstate.qlfirst;
mudstate.qlast = mudstate.qllast;
mudstate.qllast = mudstate.qlfirst = NULL;
}
/* Note: the point->left test would be 0 except the command is being
* put in the low priority queue to be done in one second anyways
*/
/* Do the wait queue */
for (point=mudstate.qwait,trail=NULL; point; point=next) {
point->left -= nsecs;
if (point->left <= 1) {
if (trail != NULL)
trail->next = next = point->next;
else
mudstate.qwait = next = point->next;
give_que(point);
} else
next = (trail = point)->next;
}
/* Check the semaphore queue for expired timed-waits */
for (point=mudstate.qsemfirst,trail=NULL; point; point=next) {
if (point->left < 0) {
next = (trail = point)->next;
continue; /* Skip if not timed-wait */
}
point->left -= nsecs;
if (point->left <= 1) {
if (trail != NULL)
trail->next = next = point->next;
else
mudstate.qsemfirst = next = point->next;
if (point == mudstate.qsemlast)
mudstate.qsemlast = trail;
add_to(point->sem, -1, A_SEMAPHORE);
point->sem = NOTHING;
give_que(point);
} else
next = (trail = point)->next;
}
mudstate.debug_cmd = cmdsave;
return;
}
/* ---------------------------------------------------------------------------
* do_top: Execute the command at the top of the queue
*/
int do_top(int ncmds)
{
BQUE *tmp;
dbref player;
int count;
char *command, *cp, *cmdsave;
if ((mudconf.control_flags & CF_DEQUEUE) == 0) return 0;
cmdsave = mudstate.debug_cmd;
mudstate.debug_cmd = (char *)"< do_top >";
for (count=0; count<ncmds; count++) {
if (!test_top()) {
mudstate.debug_cmd = cmdsave;
return count;
}
player = mudstate.qfirst->player;
if ((player > 0) && !(Flags(player) & GOING)) {
giveto(player, mudconf.waitcost);
mudstate.curr_enactor = mudstate.qfirst->cause;
mudstate.curr_player = player;
add_to(Owner(player), -1, A_QUEUE);
mudstate.qfirst->player = 0;
if (!Halted(player)) {
command = mudstate.qfirst->comm;
while (command) {
cp = parse_to(&command, ';', 0);
if (cp && *cp) {
process_command(player,
mudstate.qfirst->cause,
0, cp,
mudstate.qfirst->env,
mudstate.qfirst->nargs);
}
}
}
}
tmp = mudstate.qfirst->next;
clean_que(RU_ARG1_TAKE|RU_ARG2_TAKE, mudstate.qfirst->comm,
mudstate.qfirst->env, mudstate.qfirst->nargs);
free_qentry(mudstate.qfirst);
if (!(mudstate.qfirst = tmp))
mudstate.qlast = NULL;
}
mudstate.debug_cmd = cmdsave;
return count;
}
/* ---------------------------------------------------------------------------
* do_ps: tell player what commands they have pending in the queue
*/
static void show_que(dbref player, int key, BQUE *queue,
int *qtot, int *qent, int *qdel,
dbref player_targ, dbref obj_targ, const char *header)
{
BQUE *tmp;
char *bp, *bufp;
int i;
*qtot = 0;
*qent = 0;
*qdel = 0;
for (tmp = queue; tmp; tmp = tmp->next) {
(*qtot)++;
if (que_want(tmp, player_targ, obj_targ)) {
(*qent)++;
if (key == PS_SUMM) continue;
if (*qent == 1)
notify(player,
tprintf("----- %s Queue -----",
header));
bufp = unparse_object(player, tmp->player);
if ((tmp->left >= 0) && (Good_obj(tmp->sem)))
notify(player,
tprintf("[#%d/%d]%s:%s",
tmp->sem, tmp->left,
bufp, tmp->comm));
else if (tmp->left >= 0)
notify(player,
tprintf("[%d]%s:%s", tmp->left,
bufp, tmp->comm));
else if (Good_obj(tmp->sem))
notify(player,
tprintf("[#%d]%s:%s", tmp->sem,
bufp, tmp->comm));
else
notify(player,
tprintf("%s:%s", bufp, tmp->comm));
bp = bufp;
if (key == PS_LONG) {
for (i=0; i<(tmp->nargs); i++) {
if (tmp->env[i] != NULL) {
safe_str((char *)"; Arg",
bufp, &bp);
safe_chr(i+'0', bufp, &bp);
safe_str((char *)"='",
bufp, &bp);
safe_str(tmp->env[i],
bufp, &bp);
safe_chr('\'', bufp, &bp);
}
}
*bp = '\0';
bp = unparse_object(player, tmp->cause);
notify(player,
tprintf(" Enactor: %s%s",
bp, bufp));
free_lbuf(bp);
}
free_lbuf(bufp);
} else if (tmp->player == 0) {
(*qdel)++;
}
}
return;
}
void do_ps(dbref player, dbref cause, int key, char *target)
{
char *bufp;
dbref player_targ, obj_targ;
int pqent, pqtot, pqdel, oqent, oqtot, oqdel, wqent, wqtot, sqent, sqtot, i;
/* Figure out what to list the queue for */
if (!target || !*target) {
obj_targ = NOTHING;
if (key & PS_ALL) {
player_targ = NOTHING;
} else {
player_targ = Owner(player);
if (Typeof(player) != TYPE_PLAYER)
obj_targ = player;
}
} else {
player_targ = Owner(player);
obj_targ = match_controlled(player, target);
if (obj_targ == NOTHING) return;
if (key & PS_ALL) {
notify(player, "Can't specify a target and /all");
return;
}
if (Typeof(obj_targ) == TYPE_PLAYER) {
player_targ = obj_targ;
obj_targ = NOTHING;
}
}
key = key & ~PS_ALL;
switch (key) {
case PS_BRIEF:
case PS_SUMM:
case PS_LONG:
break;
default:
notify(player, "Illegal combination of switches.");
return;
}
/* Go do it */
show_que(player, key, mudstate.qfirst, &pqtot, &pqent, &pqdel,
player_targ, obj_targ, "Player");
show_que(player, key, mudstate.qlfirst, &oqtot, &oqent, &oqdel,
player_targ, obj_targ, "Object");
show_que(player, key, mudstate.qwait, &wqtot, &wqent, &i,
player_targ, obj_targ, "Wait");
show_que(player, key, mudstate.qsemfirst, &sqtot, &sqent, &i,
player_targ, obj_targ, "Semaphore");
/* Display stats */
bufp = alloc_mbuf("do_ps");
if (Wizard(player))
sprintf(bufp,"Totals: Player...%d/%d[%ddel] Object...%d/%d[%ddel] Wait...%d/%d Semaphore...%d/%d",
pqent, pqtot, pqdel, oqent, oqtot, oqdel,
wqent, wqtot, sqent, sqtot);
else
sprintf(bufp,"Totals: Player...%d/%d Object...%d/%d Wait...%d/%d Semaphore...%d/%d",
pqent, pqtot, oqent, oqtot, wqent, wqtot, sqent, sqtot);
notify(player, bufp);
free_mbuf(bufp);
}
/* ---------------------------------------------------------------------------
* do_queue: Queue management
*/
void do_queue (dbref player, dbref cause, int key, char *arg)
{
int i, ncmds, was_disabled;
was_disabled = 0;
if (key == QUEUE_KICK) {
i = atol(arg);
if ((mudconf.control_flags & CF_DEQUEUE) == 0) {
was_disabled = 1;
mudconf.control_flags |= CF_DEQUEUE;
notify(player,"Warning: automatic dequeueing is disabled.");
}
ncmds = do_top(i);
if (was_disabled)
mudconf.control_flags &= ~CF_DEQUEUE;
if (!Quiet(player))
notify(player,
tprintf("%d commands processed.", ncmds));
} else if (key == QUEUE_WARP) {
i = atol(arg);
if ((mudconf.control_flags & CF_DEQUEUE) == 0) {
was_disabled = 1;
mudconf.control_flags |= CF_DEQUEUE;
notify(player,"Warning: automatic dequeueing is disabled.");
}
do_second(i);
if (was_disabled)
mudconf.control_flags &= ~CF_DEQUEUE;
if (Quiet(player)) return;
if (i > 0)
notify(player,
tprintf("WaitQ timer advanced %d seconds.", i));
else if (i < 0)
notify(player,
tprintf("WaitQ timer set back %d seconds.", i));
else
notify(player,
"Object queue appended to player queue.");
}
}