/* $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 <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <sys/wait.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;
static int alarm_block = 0;

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

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

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

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

    if((f = fopen(tmpfile, "w")) != NULL) {
	db_write(f);
	fclose(f);
	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, "w")) == 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);
    }
#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)
{
    int child;

    epoch++;

    fprintf(stderr, "CHECKPOINTING: %s.#%d#\n", dumpfile, epoch);
#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 */
	dump_database_internal();
	_exit(0);		/* !!! */
    } else if(child < 0) {
	perror("fork_and_dump: fork()");
    }
	
    /* in the parent */
    /* reset alarm */
    alarm_triggered = 0;
    alarm(DUMP_INTERVAL);
}

static int reaper(void)
{
    union wait stat;

    while(wait3(&stat, WNOHANG, 0) > 0)
      /*EMPTY*/
      ;
    return 0;
}

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

   if((f = fopen(infile, "r")) == 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);

   /* initialize random number generator */
   srandom(getpid());

   /* set up dumper */
   if(dumpfile) free((void *) dumpfile);
   dumpfile = alloc_string(outfile);
   signal(SIGALRM, alarm_handler);
   signal(SIGHUP, alarm_handler);
   signal(SIGCHLD, reaper);
   alarm_triggered = 0;
   alarm(DUMP_INTERVAL);
   
   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 */

    char *index(char *, char);

    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';

    /* block dump to prevent db inconsistencies from showing up */
    alarm_block = 1;

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

    /* unblock alarms */
    alarm_block = 0;
    if(alarm_triggered) {
	fork_and_dump();
    }
}

#undef Matched