mux2.0/
mux2.0/game/bin/
mux2.0/game/data/
mux2.0/src/tools/
// cque.cpp -- commands and functions for manipulating the command queue.
//
// $Id: cque.cpp,v 1.13 2000/08/03 21:42:18 sdennis Exp $
//
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"

#include <signal.h>

#include "mudconf.h"
#include "db.h"
#include "htab.h"
#include "interface.h"
#include "match.h"
#include "attrs.h"
#include "flags.h"
#include "powers.h"
#include "command.h"
#include "alloc.h"

extern int FDECL(a_Queue, (dbref, int));
extern void FDECL(s_Queue, (dbref, int));
extern int FDECL(QueueMax, (dbref));

CLinearTimeDelta GetProcessorUsage(void)
{
    CLinearTimeDelta ltd;
#ifdef WIN32
    if (platform == VER_PLATFORM_WIN32_NT)
    {
        FILETIME ftCreate;
        FILETIME ftExit;
        FILETIME ftKernel;
        FILETIME ftUser;
        fpGetProcessTimes(hGameProcess, &ftCreate, &ftExit, &ftKernel, &ftUser);
        ltd.Set100ns(*(INT64 *)(&ftUser));
        return ltd;
    }

    // Win9x - We can really only report the system time with a
    // high-resolution timer. This doesn't seperate the time from
    // this process from other processes that are also running on
    // the same machine, but it's the best we can do on Win9x.
    //
    // The scheduling in Win9x is totally screwed up, so even if
    // Win9x did provide the information, it would certainly be
    // wrong.
    //
    if (bQueryPerformanceAvailable)
    {
        // The QueryPerformanceFrequency call at game startup
        // succeeded. The frequency number is the number of ticks
        // per second.
        //
        INT64 li;
        if (QueryPerformanceCounter((LARGE_INTEGER *)&li))
        {
            li = QP_A*li + (QP_B*li+QP_C)/QP_D;
            ltd.Set100ns(li);
            return ltd;
        }
        bQueryPerformanceAvailable = FALSE;
    }
#endif // WIN32

#if !defined(WIN32) && defined(HAVE_GETRUSAGE)

    struct rusage usage;
    getrusage(RUSAGE_SELF, &usage);
    ltd.SetTimeValueStruct(&usage.ru_utime);
    return ltd;

#else // !WIN32 && HAVE_GETRUSAGE

    // Either this Unix doesn't have getrusage or this is a
    // fall-through case for Win32.
    //
    CLinearTimeAbsolute ltaNow;
    ltaNow.GetLocal();
    ltd = ltaNow - mudstate.start_time;
    return ltd;

#endif // !WIN32 && HAVE_GETRUSAGE
}

/*
 * ---------------------------------------------------------------------------
 * * add_to: Adjust an object's queue or semaphore count.
 */

static int add_to(dbref player, int am, int attrnum)
{
    int aflags;
    dbref aowner;

    char *atr_gotten = atr_get(player, attrnum, &aowner, &aflags);
    int num = Tiny_atol(atr_gotten);
    free_lbuf(atr_gotten);
    num += am;

    char buff[20];
    int nlen = 0;
    *buff = '\0';
    if (num)
    {
        nlen = Tiny_ltoa(num, buff);
    }
    atr_add_raw_LEN(player, attrnum, buff, nlen);
    return num;
}

// This Task assumes that pEntry is already unlinked from any lists it may have been related to.
//
void Task_RunQueueEntry(void *pEntry, int iUnused)
{
    BQUE *point = (BQUE *)pEntry;
    dbref player = point->player;

    if ((player >= 0) && !Going(player))
    {
        giveto(player, mudconf.waitcost);
        mudstate.curr_enactor = point->cause;
        mudstate.curr_player = player;
        a_Queue(Owner(player), -1);
        point->player = NOTHING;
        if (!Halted(player))
        {
            // Load scratch args.
            //
            for (int i = 0; i < MAX_GLOBAL_REGS; i++)
            {
                if (point->scr[i])
                {
                    int n = strlen(point->scr[i]);
                    memcpy(mudstate.global_regs[i], point->scr[i], n+1);
                    mudstate.glob_reg_len[i] = n;
                }
                else 
                {
                    mudstate.global_regs[i][0] = '\0';
                    mudstate.glob_reg_len[i] = 0;
                }
            }

            char *command = point->comm;
            while (command)
            {
                char *cp = parse_to(&command, ';', 0);
                if (cp && *cp)
                {
                    int numpipes = 0;
                    while (  command
                          && (*command == '|')
                          && (numpipes < mudconf.ntfy_nest_lim))
                    {
                        command++;
                        numpipes++;
                        mudstate.inpipe = 1;
                        mudstate.poutnew = alloc_lbuf("process_command.pipe");
                        mudstate.poutbufc = mudstate.poutnew;
                        mudstate.poutobj = player;

                        // No lag check on piped commands.
                        //
                        process_command(player, point->cause, 0, cp, point->env, point->nargs);
                        if (mudstate.pout)
                        {
                            free_lbuf(mudstate.pout);
                            mudstate.pout = NULL;
                        }
                    
                        *mudstate.poutbufc = '\0';
                        mudstate.pout = mudstate.poutnew;
                        cp = parse_to(&command, ';', 0);
                    } 
                    mudstate.inpipe = 0;

                    CLinearTimeAbsolute ltaBegin;
                    ltaBegin.GetUTC();
                    CLinearTimeDelta ltdUsageBegin = GetProcessorUsage();

                    process_command(player, point->cause, 0, cp, point->env, point->nargs);

                    CLinearTimeAbsolute ltaEnd;
                    ltaEnd.GetUTC();

                    CLinearTimeDelta ltdUsageEnd = GetProcessorUsage();
                    CLinearTimeDelta ltd = ltdUsageEnd - ltdUsageBegin;
                    db[player].cpu_time_used += ltd;

                    if (mudstate.pout)
                    {
                        free_lbuf(mudstate.pout);
                        mudstate.pout = NULL;
                    }

                    ltd = ltaEnd - ltaBegin;
                    if (ltd.ReturnSeconds() >= mudconf.max_cmdsecs)
                    {
                        STARTLOG(LOG_PROBLEMS, "CMD", "CPU");
                        log_name_and_loc(player);
                        char *logbuf = alloc_lbuf("do_top.LOG.cpu");
                        long ms = ltd.ReturnMilliseconds();
                        sprintf(logbuf, " queued command taking %d.%02d secs (enactor #%d): ", ms/100, ms%100, point->cause);
                        log_text(logbuf);
                        free_lbuf(logbuf);
                        //log_text(log_cmdbuf);
                        ENDLOG;
                    }
                }
            }
        }
        MEMFREE(point->text);
        free_qentry(point);
    }

    for (int i = 0; i < MAX_GLOBAL_REGS; i++)
    {
        mudstate.global_regs[i][0] = '\0';
        mudstate.glob_reg_len[i] = 0;
    }
}

/*
 * ---------------------------------------------------------------------------
 * * 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;
}

void Task_SemaphoreTimeout(void *pExpired, int iUnused)
{
    // A semaphore has timed out.
    //
    BQUE *point = (BQUE *)pExpired;
    add_to(point->sem, -1, point->attr);
    point->sem = NOTHING;
    Task_RunQueueEntry(point, 0);
}

dbref Halt_Player_Target;
dbref Halt_Object_Target;
int Halt_HaltedEntries;

int CallBack_HaltQueue(PTASK_RECORD p)
{
    if (p->fpTask == Task_RunQueueEntry || p->fpTask == Task_SemaphoreTimeout)
    {
        // This is a @wait or timed semaphore task.
        //
        BQUE *point = (BQUE *)(p->arg_voidptr);
        if (que_want(point, Halt_Player_Target, Halt_Object_Target))
        {
            Halt_HaltedEntries++;
            if (p->fpTask == Task_SemaphoreTimeout)
            {
                add_to(point->sem, -1, point->attr);
            }
            MEMFREE(point->text);
            free_qentry(point);
            return IU_REMOVE_TASK;
        }
    }
    return IU_NEXT_TASK;
}

/*
 * ---------------------------------------------------------------------------
 * * halt_que: Remove all queued commands from a certain player
 */

int halt_que(dbref player, dbref object)
{
    Halt_Player_Target = player;
    Halt_Object_Target = object;
    Halt_HaltedEntries = 0;

    // Process @wait, timed semaphores, and untimed semaphores.
    //
    scheduler.TraverseUnordered(CallBack_HaltQueue);

    if (player == NOTHING)
    {
        player = Owner(object);
    }
    giveto(player, (mudconf.waitcost * Halt_HaltedEntries));
    if (object == NOTHING)
    {
        s_Queue(player, 0);
    }
    else
    {
        a_Queue(player, -Halt_HaltedEntries);
    }
    return Halt_HaltedEntries;
}

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

    if ((key & HALT_ALL) && !(Can_Halt(player)))
    {
        notify(player, NOPERM_MESSAGE);
        return;
    }

    // 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
    {
        if (Can_Halt(player))
            obj_targ = match_thing(player, target);
        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));
}

int Notify_Key;
int Notify_Num_Requested;
int Notify_Num_Done;
int Notify_Num_Max;
int Notify_Sem;
int Notify_Attr;

int CallBack_NotifySemaphore(PTASK_RECORD p)
{
    // If we've notified enough, exit.
    //
    if ((Notify_Key == NFY_NFY) && (Notify_Num_Done >= Notify_Num_Max))
    {
        return IU_DONE;
    }

    if (p->fpTask == Task_SemaphoreTimeout)
    {
        // This represents a semaphore.
        //
        BQUE *point = (BQUE *)(p->arg_voidptr);
        if (  (point->sem == Notify_Sem)
           && ((point->attr == Notify_Attr) || !Notify_Attr))
        {
            if (Notify_Key == NFY_DRAIN)
            {
                // Discard the command
                //
                giveto(point->player, mudconf.waitcost);
                a_Queue(Owner(point->player), -1);
                MEMFREE(point->text);
                free_qentry(point);
                return IU_REMOVE_TASK;
            }
            else
            {
                // Allow the command to run. The priority may have been
                // PRIORITY_SUSPEND, so we need to change it.
                //
                if (Typeof(point->cause) == TYPE_PLAYER)
                {
                    p->iPriority = PRIORITY_PLAYER;
                }
                else
                {
                    p->iPriority = PRIORITY_OBJECT;
                }
                p->fpTask = Task_RunQueueEntry;
                return IU_UPDATE_TASK;
            }
        }
    }
    return IU_NEXT_TASK;
}

/*
 * ---------------------------------------------------------------------------
 * * nfy_que: Notify commands from the queue and perform or discard them.
 */

int nfy_que(dbref sem, int attr, int key, int count)
{
    int aflags;
    dbref aowner;
    char *str;

    int Notify_Num_Requested = 1;
    if (attr)
    {
        str = atr_get(sem, attr, &aowner, &aflags);
        Notify_Num_Requested = Tiny_atol(str);
        free_lbuf(str);
    }
    
    int Notify_Num_Done = 0;
    if (Notify_Num_Requested > 0)
    {
        Notify_Key  = key;
        Notify_Sem  = sem;
        Notify_Attr = attr;
        Notify_Num_Max  = count;
        scheduler.TraverseUnordered(CallBack_NotifySemaphore);
    }
    
    // Update the sem waiters count.
    //   
    if (key == NFY_NFY)
    {
        add_to(sem, -count, attr);
    }
    else
    {
        atr_clr(sem, attr);
    }

    return Notify_Num_Done;
}

/*
 * ---------------------------------------------------------------------------
 * * do_notify: Command interface to nfy_que
 */


void do_notify(dbref player, dbref cause, int key, char *what, char *count)
{
    dbref thing, aowner;
    int loccount, attr = 0, aflags;
    ATTR *ap;
    char *obj;
    
    obj = parse_to(&what, '/', 0);
    init_match(player, obj, NOTYPE);
    match_everything(0);
    
    if ((thing = noisy_match_result()) < 0)
    {
        notify(player, "No match.");
    }
    else if (!controls(player, thing) && !Link_ok(thing))
    {
        notify(player, NOPERM_MESSAGE);
    }
    else
    {
        if (!what || !*what)
        {
            ap = NULL;
        }
        else
        {
            ap = atr_str(what);
        }
        
        if (!ap)
        {
            attr = A_SEMAPHORE;
        }
        else
        {
            /* Do they have permission to set this attribute? */
            atr_pget_info(thing, ap->number, &aowner, &aflags);
            
            if (Set_attr(player, thing, ap, aflags))
            {
                attr = ap->number;
            }
            else
            {
                notify_quiet(player, NOPERM_MESSAGE);
                return;
            }
        }
        
        if (count && *count)
            loccount = Tiny_atol(count);
        else
            loccount = 1;
        if (loccount > 0)
        {
            nfy_que(thing, attr, key, loccount);
            if (!(Quiet(player) || Quiet(thing)))
            {
                if (key == NFY_DRAIN)
                    notify_quiet(player, "Drained.");
                else
                    notify_quiet(player, "Notified.");
            }
        }
    }
}

/*
 * ---------------------------------------------------------------------------
 * * setup_que: Set up a queue entry.
 */

static BQUE *setup_que(dbref player, dbref cause, char *command, char *args[], int nargs, char *sargs[])
{
    int a;
    BQUE *tmp;
    char *tptr;

    // Can we run commands at all?
    //
    if (Halted(player))
        return NULL;

    // Make sure player can afford to do it.
    //
    a = mudconf.waitcost;
    if (mudconf.machinecost && RandomLong(0, mudconf.machinecost-1) == 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 = QueueMax(Owner(player));
    if (a_Queue(Owner(player), 1) > 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_Halted(player);
        return NULL;
    }

    // We passed all the tests.
    //

    // Calculate the length of the save string.
    //
    unsigned int tlen = 0;
    static unsigned int nCommand;
    static unsigned int nLenEnv[NUM_ENV_VARS];
    static unsigned int nLenRegs[MAX_GLOBAL_REGS];

    if (command)
    {
        nCommand = strlen(command) + 1;
        tlen = nCommand;
    }
    if (nargs > NUM_ENV_VARS)
    {
        nargs = NUM_ENV_VARS;
    }
    for (a = 0; a < nargs; a++)
    {
        if (args[a])
        {
            nLenEnv[a] = strlen(args[a]) + 1;
            tlen += nLenEnv[a];
        }
    }
    if (sargs)
    {
        for (a = 0; a < MAX_GLOBAL_REGS; a++)
        {
            if (sargs[a])
            {
                nLenRegs[a] = strlen(sargs[a]) + 1;
                tlen += nLenRegs[a];
            }
        }
    }

    // Create the qeue entry and load the save string.
    //
    tmp = alloc_qentry("setup_que.qblock");
    tmp->comm = NULL;

    tptr = tmp->text = (char *)MEMALLOC(tlen);
    ISOUTOFMEMORY(tptr);

    if (command)
    {
        memcpy(tptr, command, nCommand);
        tmp->comm = tptr;
        tptr += nCommand;
    }
    for (a = 0; a < nargs; a++)
    {
        if (args[a])
        {
            memcpy(tptr, args[a], nLenEnv[a]);
            tmp->env[a] = tptr;
            tptr += nLenEnv[a];
        }
        else
        {
            tmp->env[a] = NULL;
        }
    }
    for ( ; a < NUM_ENV_VARS; a++)
    {
        tmp->env[a] = NULL;
    }
    for (a = 0; a < MAX_GLOBAL_REGS; a++)
    {
        tmp->scr[a] = NULL;
    }
    if (sargs)
    {
        for (a = 0; a < MAX_GLOBAL_REGS; a++)
        {
            if (sargs[a])
            {
                memcpy(tptr, sargs[a], nLenRegs[a]);
                tmp->scr[a] = tptr;
                tptr += nLenRegs[a];
            }
        }
    }

    // Load the rest of the queue block.
    //
    tmp->player = player;
    tmp->IsTimed = FALSE;
    tmp->sem = NOTHING;
    tmp->attr = 0;
    tmp->cause = cause;
    tmp->nargs = nargs;
    return tmp;
}

/*
 * ---------------------------------------------------------------------------
 * * wait_que: Add commands to the wait or semaphore queues.
 */

void wait_que(dbref player, dbref cause, int wait, dbref sem, int attr, char *command, char *args[], int nargs, char *sargs[])
{
    if (!(mudconf.control_flags & CF_INTERP)) return;

    BQUE *tmp = setup_que(player, cause, command, args, nargs, sargs);
    if (!tmp) return;

    int iPriority;
    if (Typeof(tmp->cause) == TYPE_PLAYER)
    {
        iPriority = PRIORITY_PLAYER;
    }
    else
    {
        iPriority = PRIORITY_OBJECT;
    }

    if (wait == 0)
    {
        // This means that waitime is not used.
        //
        tmp->IsTimed = FALSE;
    }
    else
    {
        tmp->IsTimed = TRUE;
        tmp->waittime.GetUTC();
        CLinearTimeDelta ltdWait;
        ltdWait.SetSeconds(wait);
        tmp->waittime += ltdWait;
    }

    tmp->sem = sem;
    tmp->attr = attr;

    if (sem == NOTHING)
    {
        // Not a semaphore, so let it run it immediately or put it on
        // the wait queue.
        //
        if (tmp->IsTimed)
        {
            scheduler.DeferTask(tmp->waittime, iPriority, Task_RunQueueEntry, tmp, 0);
        }
        else
        {
            scheduler.DeferImmediateTask(iPriority, Task_RunQueueEntry, tmp, 0);
        }
    }
    else
    {
        if (!tmp->IsTimed)
        {
            // In this case, the timeout task below will never run,
            // but it allows us to manage all semaphores together in
            // the same data structure.
            //
            iPriority = PRIORITY_SUSPEND;
        }
        scheduler.DeferTask(tmp->waittime, iPriority, Task_SemaphoreTimeout, tmp, 0);
    }
}

/*
 * ---------------------------------------------------------------------------
 * * 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, attr, aflags;
    char *what;
    ATTR *ap;
    

    /*
     * If arg1 is all numeric, do simple (non-sem) timed wait. 
     */

    if (is_number(event))
    {
        howlong = Tiny_atol(event);
        wait_que(player, cause, howlong, NOTHING, 0, cmd, cargs, ncargs, mudstate.global_regs);
        return;
    }
    /*
     * Semaphore wait with optional timeout 
     */

    what = parse_to(&event, '/', 0);
    init_match(player, what, NOTYPE);
    match_everything(0);

    thing = noisy_match_result();
    if (!Good_obj(thing))
    {
        notify(player, "No match.");
    }
    else if (!controls(player, thing) && !Link_ok(thing))
    {
        notify(player, NOPERM_MESSAGE);
    }
    else 
    {

        /*
         * Get timeout, default 0 
         */

        if (event && *event && is_number(event))
        {
            attr = A_SEMAPHORE;
            howlong = Tiny_atol(event);
        }
        else
        {
            attr = A_SEMAPHORE;
            howlong = 0;
        }

        if (event && *event && !is_number(event))
        {
            ap = atr_str(event);
            if (!ap)
            {
                attr = mkattr(event);
                if (attr <= 0) {
                    notify_quiet(player, "Invalid attribute.");
                    return;
                }
                ap = atr_num(attr);
            }
            atr_pget_info(thing, ap->number, &aowner, &aflags);
            if (attr && Set_attr(player, thing, ap, aflags))
            {
                attr = ap->number;
                howlong = 0;
            }
            else
            {
                notify_quiet(player, NOPERM_MESSAGE);
                return;
            }
        }
        
        num = add_to(thing, 1, attr);
        if (num <= 0)
        {

            /*
             * thing over-notified, run the command immediately 
             */

            thing = NOTHING;
            howlong = 0;
        }
        wait_que(player, cause, howlong, thing, attr, cmd, cargs, ncargs, mudstate.global_regs);
    }
}

CLinearTimeAbsolute Show_lsaNow;
int Total_SystemTasks;
int Total_RunQueueEntry;
int Shown_RunQueueEntry;
int Total_SemaphoreTimeout;
int Shown_SemaphoreTimeout;
dbref Show_Player_Target;
dbref Show_Object_Target;
int Show_Key;
dbref Show_Player;
int Show_bFirstLine;

#ifdef WIN32
void Task_FreeDescriptor(void *arg_voidptr, int arg_Integer);
#endif
void dispatch_DatabaseDump(void *pUnused, int iUnused);
void dispatch_FreeListReconstruction(void *pUnused, int iUnused);
void dispatch_IdleCheck(void *pUnused, int iUnused);
void dispatch_CheckEvents(void *pUnused, int iUnused);
#ifndef MEMORY_BASED
void dispatch_CacheTick(void *pUnused, int iUnused);
#endif

int CallBack_ShowDispatches(PTASK_RECORD p)
{
    Total_SystemTasks++;
    CLinearTimeDelta ltd = p->ltaWhen - Show_lsaNow;
    if (p->fpTask == dispatch_DatabaseDump)
    {
        notify(Show_Player, tprintf("[%d]auto-@dump", ltd.ReturnSeconds()));
    }
    else if (p->fpTask == dispatch_FreeListReconstruction)
    {
        notify(Show_Player, tprintf("[%d]auto-@dbck", ltd.ReturnSeconds()));
    }
    else if (p->fpTask == dispatch_IdleCheck)
    {
        notify(Show_Player, tprintf("[%d]Check for idle players", ltd.ReturnSeconds()));
    }
    else if (p->fpTask == dispatch_CheckEvents)
    {
        notify(Show_Player, tprintf("[%d]Test for @daily time", ltd.ReturnSeconds()));
    }
#ifndef MEMORY_BASED
    else if (p->fpTask == dispatch_CacheTick)
    {
        notify(Show_Player, tprintf("[%d]Database cache tick", ltd.ReturnSeconds()));
    }
#endif // MEMORY_BASED
    else if (p->fpTask == Task_ProcessCommand)
    {
        notify(Show_Player, tprintf("[%d]Further command quota", ltd.ReturnSeconds()));
    }
#ifdef WIN32
    else if (p->fpTask == Task_FreeDescriptor)
    {
        notify(Show_Player, tprintf("[%d]Delayed descriptor deallocation", ltd.ReturnSeconds()));
    }
#endif // WIN32
    else
    {
        Total_SystemTasks--;
    }
    return IU_NEXT_TASK;
}

void ShowPsLine(BQUE *tmp)
{
    char *bufp = unparse_object(Show_Player, tmp->player, 0);
    if (tmp->IsTimed && (Good_obj(tmp->sem)))
    {
        CLinearTimeDelta ltd = tmp->waittime - Show_lsaNow;
        notify(Show_Player, tprintf("[#%d/%d]%s:%s", tmp->sem, ltd.ReturnSeconds(), bufp, tmp->comm));
    }
    else if (tmp->IsTimed)
    {
        CLinearTimeDelta ltd = tmp->waittime - Show_lsaNow;
        notify(Show_Player, tprintf("[%d]%s:%s", ltd.ReturnSeconds(), bufp, tmp->comm));
    }
    else if (Good_obj(tmp->sem))
    {
        notify(Show_Player, tprintf("[#%d]%s:%s", tmp->sem, bufp, tmp->comm));
    }
    else
    {
        notify(Show_Player, tprintf("%s:%s", bufp, tmp->comm));
    }
    char *bp = bufp;
    if (Show_Key == PS_LONG)
    {
        for (int i = 0; i < tmp->nargs; i++)
        {
            if (tmp->env[i] != NULL)
            {
                safe_str((char *)"; Arg", bufp, &bp);
                safe_chr((char)(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(Show_Player, tmp->cause, 0);
        notify(Show_Player, tprintf("   Enactor: %s%s", bp, bufp));
        free_lbuf(bp);
    }
    free_lbuf(bufp);
}

int CallBack_ShowWait(PTASK_RECORD p)
{
    if (p->fpTask != Task_RunQueueEntry)
    {
        return IU_NEXT_TASK;
    }

    Total_RunQueueEntry++;
    BQUE *tmp = (BQUE *)(p->arg_voidptr);
    if (que_want(tmp, Show_Player_Target, Show_Object_Target))
    {
        Shown_RunQueueEntry++;
        if (Show_Key == PS_SUMM)
        {
            return IU_NEXT_TASK;
        }
        if (Show_bFirstLine)
        {
            notify(Show_Player, "----- Wait Queue -----");
            Show_bFirstLine = FALSE;
        }
        ShowPsLine(tmp);
    }
    return IU_NEXT_TASK;
}

int CallBack_ShowSemaphore(PTASK_RECORD p)
{
    if (p->fpTask != Task_SemaphoreTimeout)
    {
        return IU_NEXT_TASK;
    }

    Total_SemaphoreTimeout++;
    BQUE *tmp = (BQUE *)(p->arg_voidptr);
    if (que_want(tmp, Show_Player_Target, Show_Object_Target))
    {
        Shown_SemaphoreTimeout++;
        if (Show_Key == PS_SUMM)
        {
            return IU_NEXT_TASK;
        }
        if (Show_bFirstLine)
        {
            notify(Show_Player, "----- Semaphore Queue -----");
            Show_bFirstLine = FALSE;
        }
        ShowPsLine(tmp);
    }
    return IU_NEXT_TASK;
}

/*
 * ---------------------------------------------------------------------------
 * * do_ps: tell player what commands they have pending in the queue
 */
void do_ps(dbref player, dbref cause, int key, char *target)
{
    char *bufp;
    dbref player_targ, obj_targ;

    // Figure out what to list the queue for.
    //
    if ((key & PS_ALL) && !(See_Queue(player))) 
    {
        notify(player, NOPERM_MESSAGE);
        return;
    }
    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;
    }

    Show_lsaNow.GetUTC();
    Total_SystemTasks = 0;
    Total_RunQueueEntry = 0;
    Shown_RunQueueEntry = 0;
    Total_SemaphoreTimeout = 0;
    Shown_SemaphoreTimeout = 0;
    Show_Player_Target = player_targ;
    Show_Object_Target = obj_targ;
    Show_Key = key;
    Show_Player = player;
    Show_bFirstLine = TRUE;
    scheduler.TraverseOrdered(CallBack_ShowWait);
    Show_bFirstLine = TRUE;
    scheduler.TraverseOrdered(CallBack_ShowSemaphore);
    if (Wizard(player))
    {
        notify(player, "----- System Queue -----");
        scheduler.TraverseOrdered(CallBack_ShowDispatches);
    }

    // Display stats.
    //
    bufp = alloc_mbuf("do_ps");
    sprintf(bufp, "Totals: Wait Queue...%d/%d  Semaphores...%d/%d",
        Shown_RunQueueEntry, Total_RunQueueEntry,
        Shown_SemaphoreTimeout, Total_SemaphoreTimeout);
    notify(player, bufp);
    if (Wizard(player))
    {
        sprintf(bufp, "        System Tasks.....%d", Total_SystemTasks);
        notify(player, bufp);
    }
    free_mbuf(bufp);
}

CLinearTimeDelta ltdWarp;
int CallBack_Warp(PTASK_RECORD p)
{
    if (p->fpTask == Task_RunQueueEntry || p->fpTask == Task_SemaphoreTimeout)
    {
        BQUE *point = (BQUE *)(p->arg_voidptr);
        if (point->IsTimed)
        {
            point->waittime -= ltdWarp;
            return IU_UPDATE_TASK;
        }
    }
    return IU_NEXT_TASK;
}

/*
 * ---------------------------------------------------------------------------
 * * do_queue: Queue management
 */

void do_queue(dbref player, dbref cause, int key, char *arg)
{
    int was_disabled;

    was_disabled = 0;
    if (key == QUEUE_KICK)
    {
        int i = Tiny_atol(arg);
        int save_minPriority = scheduler.GetMinPriority();
        if (save_minPriority <= PRIORITY_CF_DEQUEUE_DISABLED)
        {
            notify(player, "Warning: automatic dequeueing is disabled.");
            scheduler.SetMinPriority(PRIORITY_CF_DEQUEUE_ENABLED);
        }
        CLinearTimeAbsolute lsaNow;
        lsaNow.GetUTC();
        scheduler.ReadyTasks(lsaNow);
        int ncmds = scheduler.RunTasks(i);
        scheduler.SetMinPriority(save_minPriority);

        if (!Quiet(player))
        {
            notify(player, tprintf("%d commands processed.", ncmds));
        }
    }
    else if (key == QUEUE_WARP)
    {
        int iWarp = Tiny_atol(arg);
        CLinearTimeDelta ltdWarp;
        ltdWarp.SetSeconds(iWarp);
        if (scheduler.GetMinPriority() <= PRIORITY_CF_DEQUEUE_DISABLED)
        {
            notify(player, "Warning: automatic dequeueing is disabled.");
        }

        scheduler.TraverseUnordered(CallBack_Warp);

        if (Quiet(player))
        {
            return;
        }
        if (iWarp > 0)
        {
            notify(player, tprintf("WaitQ timer advanced %d seconds.", iWarp));
        }
        else if (iWarp < 0)
        {
            notify(player, tprintf("WaitQ timer set back %d seconds.", iWarp));
        }
        else
        {
            notify(player, "Object queue appended to player queue.");
        }
    }
}