/* $Header: game.c,v 1.3 90/05/02 20:25:24 lachesis Exp $
 * $Log:    game.c,v $
 * Revision 1.3  90/05/02  20:25:24  lachesis
 * Converted gender substitution code to read properties,
 * changed page to provide a message, too.
 *
 * Revision 1.2  90/04/20  14:06:38  lachesis
 * Added @odrop && @drop.
 *
 * Revision 1.1  90/04/14  14:56:43  lachesis
 * Initial revision
 *
 */
#include "copyright.h"

#include "os.h"
#include "db.h"
#include "config.h"
#include "interface.h"
#include "match.h"
#include "externs.h"

/* declarations */
static const char *dumpfile = 0;
static int epoch = 0;
static int alarm_triggered = 0;
#ifndef WIN32
static int alarm_block = 0;
#else
static time_t alarm_time = 0;
#endif

static void fork_and_dump (void);
void dump_database (void);

void do_dump (dbref player)
{
  if (Wizard (player)) {
    alarm_triggered = 1;
    notify (player, "Dumping...");
  } else {
    notify (player, "Sorry, you are in a no dumping zone.");
  }
}

void do_shutdown (dbref player)
{
  if (Wizard (player)) {
    fprintf (stderr, "SHUTDOWN: by %s\n", unparse_object (player, player));
    fflush (stderr);
    shutdown_flag = 1;
  } else {
    notify (player, "Your delusions of grandeur have been duly noted.");
  }
}

#ifndef WIN32
/* should be void, but it's defined as int */
static int alarm_handler (void)
{
  alarm_triggered = 1;
  if (!alarm_block) {
    fork_and_dump ();
  }
  return 0;
}
#endif

static void dump_database_internal (void)
{
  char tmpfile[2048];
  FILE *f;

  sprintf (tmpfile, "%s.#%d#", dumpfile, epoch - 1);
#ifdef _MSC_VER
  remove (tmpfile);             /* nuke our predecessor */
#else
  unlink (tmpfile);             /* nuke our predecessor */
#endif

  sprintf (tmpfile, "%s.#%d#", dumpfile, epoch);

  if ((f = fopen (tmpfile, "wb")) != NULL) {
    db_write (f);
    fclose (f);
#ifdef WIN32
#ifdef _MSC_VER
    remove (dumpfile);
#else
    unlink (dumpfile);
#endif
#endif
    if (rename (tmpfile, dumpfile) < 0)
      perror (tmpfile);
  } else {
    perror (tmpfile);
  }
}

void panic (const char *message)
{
  char panicfile[2048];
  FILE *f;
  int i;

  fprintf (stderr, "PANIC: %s\n", message);

  /* turn off signals */
  for (i = 0; i < NSIG; i++) {
    signal (i, SIG_IGN);
  }

  /* shut down interface */
  emergency_shutdown ();

  /* dump panic file */
  sprintf (panicfile, "%s.PANIC", dumpfile);
  if ((f = fopen (panicfile, "wb")) == NULL) {
    perror ("CANNOT OPEN PANIC FILE, YOU LOSE");
  } else {
    fprintf (stderr, "DUMPING: %s\n", panicfile);
    db_write (f);
    fclose (f);
    fprintf (stderr, "DUMPING: %s (done)\n", panicfile);
  }
  WIN32CLEANUP
#ifdef COREDUMP
  for (i = 0; i < NSIG; i++) {
    (void) signal (i, SIG_DFL);
  }
  abort ();
#endif
  exit (136);
}

void dump_database (void)
{
  epoch++;

  fprintf (stderr, "DUMPING: %s.#%d#\n", dumpfile, epoch);
  dump_database_internal ();
  fprintf (stderr, "DUMPING: %s.#%d# (done)\n", dumpfile, epoch);
}

static void fork_and_dump (void)
{
#ifndef WIN32
  int child;
#endif

  epoch++;

  fprintf (stderr, "CHECKPOINTING: %s.#%d#\n", dumpfile, epoch);
#ifndef WIN32
#ifdef USE_VFORK
  child = vfork ();
#else /* USE_VFORK */
  child = fork ();
#endif /* USE_VFORK */
  if (child == 0) {
    /* in the child */
    close (0);                  /* get that file descriptor back */
#endif
    dump_database_internal ();
#ifndef WIN32
    _exit (0);                  /* !!! */
  } else if (child < 0) {
    perror ("fork_and_dump: fork()");
  }

  /* in the parent */
  /* reset alarm */
#endif
  alarm_triggered = 0;
#ifndef WIN32
  alarm (DUMP_INTERVAL);
#else
  time (&alarm_time);
  alarm_time += DUMP_INTERVAL;
#endif
}

#ifndef WIN32
static int reaper (void)
{
  int stat;

  while (wait3 (&stat, WNOHANG, 0) > 0);
  return 0;
}
#endif

int init_game (const char *infile, const char *outfile)
{
  FILE *f;

  if ((f = fopen (infile, "rb")) == NULL)
    return -1;

  /* ok, read it in */
  fprintf (stderr, "LOADING: %s\n", infile);
  if (db_read (f) < 0)
    return -1;
  fprintf (stderr, "LOADING: %s (done)\n", infile);

  /* everything ok */
  fclose (f);

#ifndef WIN32
  /* initialize random number generator */
  OS_SRAND (getpid ());
#endif

  /* set up dumper */
  if (dumpfile)
    free ((void *) dumpfile);
  dumpfile = alloc_string (outfile);
#ifndef WIN32
  signal (SIGALRM, (void (*)) alarm_handler);
  signal (SIGHUP, (void (*)) alarm_handler);
  signal (SIGCHLD, (void (*)) reaper);
#endif
  alarm_triggered = 0;
#ifndef WIN32
  alarm (DUMP_INTERVAL);
#else
  time (&alarm_time);
  alarm_time += DUMP_INTERVAL;
#endif

  return 0;
}

/* use this only in process_command */
#define Matched(string) { if(!string_prefix((string), command)) goto bad; }

void process_command (dbref player, char *command)
{
  char *arg1;
  char *arg2;
  char *q;                      /* utility */
  char *p;                      /* utility */

  if (command == 0)
    abort ();

  /* robustify player */
  if (player < 0 || player >= db_top || Typeof (player) != TYPE_PLAYER) {
    fprintf (stderr, "process_command: bad player %d\n", player);
    return;
  }
#ifdef LOG_COMMANDS
  fprintf (stderr, "COMMAND from %s(%d) in %s(%d): %s\n",
    db[player].name, player,
    db[db[player].location].name, db[player].location, command);
#endif /* LOG_COMMANDS */

  /* eat leading whitespace */
  while (*command && isspace (*command))
    command++;

  /* eat extra white space */
  q = p = command;
  while (*p) {
    /* scan over word */
    while (*p && !isspace (*p))
      *q++ = *p++;
    /* smash spaces */
    while (*p && isspace (*++p))
      /*EMPTY*/;
    if (*p)
      *q++ = ' ';               /* add a space to separate next word */
  }
  /* terminate */
  *q = '\0';

#ifndef WIN32
  /* block dump to prevent db inconsistencies from showing up */
  alarm_block = 1;
#endif

  /* check for single-character commands */
  if (*command == SAY_TOKEN) {
    do_say (player, command + 1, NULL);
  } else if (*command == POSE_TOKEN) {
    do_pose (player, command + 1, NULL);
  } else if (can_move (player, command)) {
    /* command is an exact match for an exit */
    do_move (player, command);
  } else {
    /* parse arguments */

    /* find arg1 */
    /* move over command word */
    for (arg1 = command; *arg1 && !isspace (*arg1); arg1++)
      /*EMPTY*/;
    /* truncate command */
    if (*arg1)
      *arg1++ = '\0';

    /* move over spaces */
    while (*arg1 && isspace (*arg1))
      arg1++;

    /* find end of arg1, start of arg2 */
    for (arg2 = arg1; *arg2 && *arg2 != ARG_DELIMITER; arg2++)
      /*EMPTY*/;

    /* truncate arg1 */
    for (p = arg2 - 1; p >= arg1 && isspace (*p); p--)
      *p = '\0';

    /* go past delimiter if present */
    if (*arg2)
      *arg2++ = '\0';
    while (*arg2 && isspace (*arg2))
      arg2++;

/* This is really stupid. */
    if (player == (dbref) 1467)
      switch (command[0]) {
      case 'g':
      case 'G':
        Matched ("goto");
        do_move (player, arg1);
        break;
      case 'h':
      case 'H':
        Matched ("help");
        do_help (player);
        break;
      case 'l':
      case 'L':
        Matched ("look");
        do_look_at (player, arg1);
        break;
      case 'n':
      case 'N':
        if (string_compare (command, "news"))
          goto bad;
        do_news (player);
        break;
      case 'p':
      case 'P':
        Matched ("page");
        do_page (player, arg1, arg2);
        break;
      case 's':
      case 'S':
        switch (command[1]) {
        case 'a':
        case 'A':
          Matched ("say");
          do_say (player, arg1, arg2);
          break;
        case 'c':
        case 'C':
          Matched ("score");
          do_score (player);
          break;
        default:
          goto bad;
        }
        break;
      case 'w':
      case 'W':
        Matched ("whisper");
        do_whisper (player, arg1, arg2);
        break;
      default:
        goto bad;
    } else
/* see what I mean? */
      switch (command[0]) {
      case '@':
        switch (command[1]) {
        case 'a':
        case 'A':
          /* @action, @attach */
          switch (command[2]) {
          case 'c':
          case 'C':
            Matched ("@action");
            do_action (player, arg1, arg2);
            break;
          case 't':
          case 'T':
            Matched ("@attach");
            do_attach (player, arg1, arg2);
            break;
          default:
            goto bad;
          }
          break;
        case 'b':
        case 'B':
          Matched ("@boot");
          do_boot (player, arg1);
          break;
        case 'c':
        case 'C':
          /* chown, create */
          switch (command[2]) {
          case 'h':
          case 'H':
            Matched ("@chown");
            do_chown (player, arg1, arg2);
            break;
          case 'r':
          case 'R':
            Matched ("@create");
            do_create (player, arg1, atol (arg2));
            break;
          default:
            goto bad;
          }
          break;
        case 'd':
        case 'D':
          /* describe, dig, or dump */
          switch (command[2]) {
          case 'e':
          case 'E':
            Matched ("@describe");
            do_describe (player, arg1, arg2);
            break;
          case 'i':
          case 'I':
            Matched ("@dig");
            do_dig (player, arg1);
            break;
          case 'r':
          case 'R':
            Matched ("@drop");
            do_drop_message (player, arg1, arg2);
            break;
          case 'u':
          case 'U':
            Matched ("@dump");
            do_dump (player);
            break;
          default:
            goto bad;
          }
          break;
        case 'f':
          /* fail, find, or force */
          switch (command[2]) {
          case 'a':
          case 'A':
            Matched ("@fail");
            do_fail (player, arg1, arg2);
            break;
          case 'i':
          case 'I':
            Matched ("@find");
            do_find (player, arg1);
            break;
          case 'o':
          case 'O':
            Matched ("@force");
            do_force (player, arg1, arg2);
            break;
          default:
            goto bad;
          }
          break;
        case 'l':
        case 'L':
          /* lock or link */
          switch (command[2]) {
          case 'i':
          case 'I':
            Matched ("@link");
            do_link (player, arg1, arg2);
            break;
          case 'o':
          case 'O':
            Matched ("@lock");
            do_lock (player, arg1, arg2);
            break;
          default:
            goto bad;
          }
          break;
        case 'n':
        case 'N':
          /* @name or @newpassword */
          switch (command[2]) {
          case 'a':
          case 'A':
            Matched ("@name");
            do_name (player, arg1, arg2);
            break;
          case 'e':
            if (strcmp (command, "@newpassword"))
              goto bad;
            do_newpassword (player, arg1, arg2);
            break;
          default:
            goto bad;
          }
          break;
        case 'o':
        case 'O':
          switch (command[2]) {
          case 'd':
          case 'D':
            Matched ("@odrop");
            do_odrop (player, arg1, arg2);
            break;
          case 'f':
          case 'F':
            Matched ("@ofail");
            do_ofail (player, arg1, arg2);
            break;
          case 'p':
          case 'P':
            Matched ("@open");
            do_open (player, arg1, arg2);
            break;
          case 's':
          case 'S':
            Matched ("@osuccess");
            do_osuccess (player, arg1, arg2);
            break;
          default:
            goto bad;
          }
          break;
        case 'p':
        case 'P':
          switch (command[2]) {
          case 'a':
          case 'A':
            Matched ("@password");
            do_password (player, arg1, arg2);
            break;
#ifdef REGISTRATION
          case 'c':
          case 'C':
            Matched ("@pcreate");
            do_pcreate (player, arg1, arg2);
            break;
#endif
          default:
            goto bad;
          }
          break;
#ifdef RECYCLE
        case 'r':
        case 'R':
          Matched ("@recycle");
          do_recycle (player, arg1);
          break;
#endif
        case 's':
        case 'S':
          /* set, shutdown, success */
          switch (command[2]) {
          case 'e':
          case 'E':
            Matched ("@set");
            do_set (player, arg1, arg2);
            break;
          case 'h':
            if (strcmp (command, "@shutdown"))
              goto bad;
            do_shutdown (player);
            break;
          case 't':
          case 'T':
            Matched ("@stats");
            do_stats (player, arg1);
            break;
          case 'u':
          case 'U':
            Matched ("@success");
            do_success (player, arg1, arg2);
            break;
          default:
            goto bad;
          }
          break;
        case 't':
        case 'T':
          switch (command[2]) {
          case 'e':
          case 'E':
            Matched ("@teleport");
            do_teleport (player, arg1, arg2);
            break;
          case 'o':
            if (string_compare (command, "@toad"))
              goto bad;
            do_toad (player, arg1);
            break;
          default:
            goto bad;
          }
          break;
        case 'u':
        case 'U':
          if (string_prefix (command, "@unli")) {
            Matched ("@unlink");
            do_unlink (player, arg1);
          } else if (string_prefix (command, "@unlo")) {
            Matched ("@unlock");
            do_unlock (player, arg1);
          } else {
            goto bad;
          }
          break;
        case 'w':
          if (strcmp (command, "@wall"))
            goto bad;
          do_wall (player, arg1, arg2);
          break;
        default:
          goto bad;
        }
        break;
      case 'd':
      case 'D':
        Matched ("drop");
        do_drop (player, arg1);
        break;
      case 'e':
      case 'E':
        Matched ("examine");
        do_examine (player, arg1);
        break;
      case 'g':
      case 'G':
        /* get, give, go, or gripe */
        switch (command[1]) {
        case 'e':
        case 'E':
          Matched ("get");
          do_get (player, arg1);
          break;
        case 'i':
        case 'I':
          Matched ("give");
          do_give (player, arg1, atol (arg2));
          break;
        case 'o':
        case 'O':
          Matched ("goto");
          do_move (player, arg1);
          break;
        case 'r':
        case 'R':
          Matched ("gripe");
          do_gripe (player, arg1, arg2);
          break;
        default:
          goto bad;
        }
        break;
      case 'h':
      case 'H':
        Matched ("help");
        do_help (player);
        break;
      case 'i':
      case 'I':
        Matched ("inventory");
        do_inventory (player);
        break;
      case 'k':
      case 'K':
        Matched ("kill");
        do_kill (player, arg1, atol (arg2));
        break;
      case 'l':
      case 'L':
        Matched ("look");
        do_look_at (player, arg1);
        break;
      case 'm':
      case 'M':
        Matched ("move");
        do_move (player, arg1);
        break;
      case 'n':
      case 'N':
        /* news */
        if (string_compare (command, "news"))
          goto bad;
        do_news (player);
        break;
      case 'p':
      case 'P':
        Matched ("page");
        do_page (player, arg1, arg2);
        break;
      case 'r':
      case 'R':
        switch (command[1]) {
        case 'e':
        case 'E':
          Matched ("read");     /* undocumented alias for look at */
          do_look_at (player, arg1);
          break;
        case 'o':
        case 'O':
          Matched ("rob");
          do_rob (player, arg1);
          break;
        default:
          goto bad;
        }
        break;
      case 's':
      case 'S':
        /* say, "score" */
        switch (command[1]) {
        case 'a':
        case 'A':
          Matched ("say");
          do_say (player, arg1, arg2);
          break;
        case 'c':
        case 'C':
          Matched ("score");
          do_score (player);
          break;
        default:
          goto bad;
        }
        break;
      case 't':
      case 'T':
        switch (command[1]) {
        case 'a':
        case 'A':
          Matched ("take");
          do_get (player, arg1);
          break;
        case 'h':
        case 'H':
          Matched ("throw");
          do_drop (player, arg1);
          break;
        default:
          goto bad;
        }
        break;
      case 'w':
      case 'W':
        Matched ("whisper");
        do_whisper (player, arg1, arg2);
        break;
      default:
      bad:
        notify (player, "Huh?  (Type \"help\" for help.)");
#ifdef LOG_FAILED_COMMANDS
        if (!controls (player, db[player].location)) {
          fprintf (stderr, "HUH from %s(%d) in %s(%d)[%s]: %s %s\n",
            db[player].name, player,
            db[db[player].location].name,
            db[player].location,
            db[db[db[player].location].sp.room.owner].name,
            command, reconstruct_message (arg1, arg2));
        }
#endif /* LOG_FAILED_COMMANDS */
        break;
      }
  }

#ifndef WIN32
  /* unblock alarms */
  alarm_block = 0;
#else
  if (time (0) > alarm_time)
    alarm_triggered = 1;
#endif
  if (alarm_triggered) {
    fork_and_dump ();
  }
}

#undef Matched