TinyMAZE/
TinyMAZE/config/
TinyMAZE/doc/
TinyMAZE/run/msgs/
TinyMAZE/src/
TinyMAZE/src/db/
TinyMAZE/src/ident/
TinyMAZE/src/io/
TinyMAZE/src/prog/
TinyMAZE/src/softcode/
TinyMAZE/src/util/
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <sys/stat.h>
#include "db.h"
#include "credits.h"
#include "externs.h"
#include "softcode.h"

extern char *swlm;
extern int quiet_reboot;

static int db_read_object P((FILE *));
static void putstring P((FILE *, char *));
static char *atr_fgets P((char *, int, FILE *));

static FILE *db_read_file = NULL;
static int current_db_version;

int loading_db = 0;

OBJ *player_list = NULL;
OBJ *thing_list = NULL;
OBJ *room_list = NULL;
OBJ *exit_list = NULL;

OBJ **db_list = NULL;

int db_top = 0;

OBJ *getloc(OBJ *thing)
{
  return(thing->location);
}

int Wizard(OBJ *thing)
{
  if(Typeof(thing) == TYPE_PLAYER && thing->class == CLASS_DIR)
    return(1);
  return(0);
}

int Guest(OBJ *thing)
{
  if(Typeof(thing) == TYPE_PLAYER && thing->class == CLASS_GUEST)
    return(1);
  return(0);
}

int Typeof(OBJ *thing)
{
  return(thing->flags & TYPE_MASK);
}

/* This function will rightfully crash if reference_db() hasn't been */
/* called yet. There isn't any need for this function until after that */
OBJ *find_object(int dbref)
{
  if(dbref >= db_top || dbref < 0)
    return(NULL);
  return(db_list[dbref]);
}

int get_free_dbref()
{
  int i;

  for(i = 0;i < db_top;++i)
    if(!db_list[i])
      break;

  return(i);
}

OBJ *new_object(int type, int need_dbref)
{
  OBJ *o;
  int i = -1;

  o = (OBJ *)stack_alloc(sizeof(OBJ), 1, 0);

  o->name = NULL;
  o->flags = (unsigned long)type;
  o->flags2 = 0;
  o->attrlist = NULL;
  o->location = NULL;
  o->contents = NULL;
  o->next_con = NULL;
  o->pows = NULL;
  o->exits = NULL;
  o->next_exit = NULL;
  o->mail = NULL;
  o->link = NULL;
  o->owner = NULL;
  o->mod_time = 0;
  o->create_time = now;
  o->comm_aliases = NULL;
  o->program = NULL;

/* Add the object to the proper list */
  switch(type)
  {
    case TYPE_PLAYER:
      o->next = player_list;
      player_list = o;
      break;
    case TYPE_THING:
      o->next = thing_list;
      thing_list = o;
      break;
    case TYPE_ROOM:
      o->next = room_list;
      room_list = o;
      break;
    case TYPE_EXIT:
      o->next = exit_list;
      exit_list = o;
      break;
    default:
      log_error("new_object() was passed an unrecognized type!");
      stack_free(o);
      return(NULL);
  }

  if(need_dbref)
  {
    i = get_free_dbref();
    o->dbref = i;

/* We only want to update db_list if they request a dbref */
/* Otherwise, the db is probably loading and it'll slow stuff down. */
    if(i >= db_top)
    {
      db_top++;
      db_list = (OBJ **)stack_realloc(db_list, sizeof(OBJ *)*db_top);
    }

    db_list[i] = o;

    plugin_create(o);
  }

  return(o);
}

static void savelists(FILE *f, OBJ *o)
{
  int num;
  OBJ *p;

  num = 0;
  if(Typeof(o) != TYPE_EXIT)
  {
    for(p = o->contents;p;p = p->next_con)
      num++;
    putref(f, num);
    for(p = o->contents;p;p = p->next_con)
      putref(f, p->dbref);
    num = 0;
  }
  if(Typeof(o) == TYPE_ROOM)
  {
    for(p = o->exits;p;p = p->next_exit)
      num++;
    putref(f, num);
    for(p = o->exits;p;p = p->next_exit)
      putref(f, p->dbref);
    num = 0;
  }
}

static void put_pows(FILE *f, OBJ *o)
{
  int i;
  char buf[4096];

  if(Typeof(o) != TYPE_PLAYER)
    return;

  for(i = 0;i < NUM_POWS;++i)
  {
    if(!i)
      sprintf(buf, "%d:%d", powers[i].num, o->pows[powers[i].num]);
    else
      sprintf(buf+strlen(buf), " %d:%d",
        powers[i].num, o->pows[powers[i].num]);
  }

  putstring(f, buf);
}

static void save_programs(FILE *f, OBJ *o)
{
  PROG *p;
  PROGLINE *pline;
  int num_programs = 0, num_lines;

  for(p = o->program;p;p = p->next)
    num_programs++;
  fprintf(f, "%d\n", num_programs);

  for(p = o->program;p;p = p->next)
  {
    fprintf(f, "%s\n", p->name);
    fprintf(f, "%s\n", p->trigger);

    num_lines = 0;

    for(pline = p->program;pline;pline = pline->next)
      num_lines++;

    fprintf(f, "%d\n", num_lines);

    for(pline = p->program;pline;pline = pline->next)
      fprintf(f, "%d %s\n", pline->line, pline->code);
  }
}

static void save_atrs(FILE *f, OBJ *o)
{
  ALIST *ptr;

  for(ptr = o->attrlist;ptr;ptr = ptr->next)
  {
    fputc('>', f);
    putref(f, ptr->attr->atrnum);
    putstring(f, ptr->value);
  }
}

static void save_comm_aliases(FILE *f, OBJ *o)
{
  CALIAS *c;
  int ctr = 0;

  for(c = o->comm_aliases;c;c = c->next)
    ctr++;

  putref(f, ctr);

  for(c = o->comm_aliases;c;c = c->next)
  {
    fprintf(f, "%s\n", c->command->name);
    fprintf(f, "%s\n", c->alias);
  }
}

static int db_write_object(FILE *f, OBJ *o)
{
  fprintf(f, "&%d\n", o->dbref);
  fprintf(f, "%ld\n", o->flags);
  fprintf(f, "%ld\n", o->flags2);
  putstring(f, o->name);
  if(Typeof(o) != TYPE_ROOM)
  {
    putref(f, o->location->dbref);
    if(!o->link)
      putref(f, -1);
    else
      putref(f, o->link->dbref);
  }
  if(Typeof(o) == TYPE_PLAYER)
  {
    put_pows(f, o);
    putref(f, o->class);
  }
  putref(f, o->owner->dbref);
  fprintf(f, "%ld\n", o->mod_time);
  fprintf(f, "%ld\n", o->create_time);

  savelists(f, o);
  save_programs(f, o);
  save_comm_aliases(f, o);

  putref(f, o->creator->dbref);

  save_atrs(f, o);
  fprintf(f, "<\n");

  return(0);
}

void db_write(FILE *f)
{
  OBJ *o;
  int num = 0;

  for(o = player_list;o;o = o->next)
    num++;
  for(o = thing_list;o;o = o->next)
    num++;
  for(o = room_list;o;o = o->next)
    num++;
  for(o = exit_list;o;o = o->next)
    num++;

  fprintf(f, "@MAZEv2-%d\n", DB_VERSION);
  fprintf(f, "~%d\n", num);
  fprintf(f, "%s\n", swlm);

  for(o = player_list;o;o = o->next)
    db_write_object(f, o);
  for(o = thing_list;o;o = o->next)
    db_write_object(f, o);
  for(o = room_list;o;o = o->next)
    db_write_object(f, o);
  for(o = exit_list;o;o = o->next)
    db_write_object(f, o);

  write_mail();
  save_com_db();
  save_maillists();
  fflush(f);
}

static char *getstring_noalloc(FILE *f)
{
  char buf[4096];

  atr_fgets(buf, sizeof(buf), f);
  *last_char(buf) = '\0';

  return(stack_string_alloc(buf, 0));
}

void getstring(FILE *f, char **ptr)
{
  char buf[4096];

  strcpy(buf, getstring_noalloc(f));

  *ptr = stack_string_alloc(buf, 1);
}

static void get_pows(FILE *f, OBJ *o)
{
  char buf[4096];
  char *b, *p, *s;

  if(Typeof(o) != TYPE_PLAYER)
  {
    o->pows = NULL;
    return;
  }

/* Use calloc() so uninitialized powers are set to 0 */
  o->pows = (int *)stack_alloc(sizeof(int)*NUM_POWS, 1, 1);

  strcpy(buf, getstring_noalloc(f));
  b = buf;

/* update_powers() will be called later to fix this */
  if(current_db_version < 1)
    return;

  while((s = parse_up(&b, ' ')))
  {
    p = strchr(s, ':'); /* Don't bother checking failure. We're screwed anyway */
    *p++ = '\0';
    o->pows[atoi(s)] = atoi(p);
  }
}

static void getlists(FILE *f, OBJ *o)
{
  int num, i;

  if(Typeof(o) != TYPE_EXIT)
  {
    num = getref(f);

    o->loadinfo->contents = (int *)stack_alloc(sizeof(int)*(num+1), 1, 0);
    for(i = 0;i < num;++i)
      o->loadinfo->contents[i] = getref(f);
    o->loadinfo->contents[num] = -1;
  }

  if(Typeof(o) == TYPE_ROOM)
  {
    num = getref(f);

    o->loadinfo->exits = (int *)stack_alloc(sizeof(int)*(num+1), 1, 0);
    for(i = 0;i < num;++i)
      o->loadinfo->exits[i] = getref(f);
    o->loadinfo->exits[num] = -1;
  }
}

void free_database()
{
  struct object *o = NULL, *onext;
  
/* Make all lists into one */
  if(player_list)
    for(o = player_list;o->next;o = o->next);
  if(thing_list)
  {
    if(o)
      o->next = thing_list;
    for(o = thing_list;o->next;o = o->next);
  }
  if(room_list)
  {
    if(o)
      o->next = room_list;
    for(o = room_list;o->next;o = o->next);
  }
  if(exit_list)
  {
    if(o)
      o->next = exit_list;
  }

/* Now player_list should point to the start of EVERYTHING */
  for(o = player_list;o;o = onext)
  {
    onext = o->next;

    stack_free(o->name);
    free_progs(o);
    atr_free(o);
    free_mail(o);
    free_caliases(o);
    if(o->pows)
      stack_free(o->pows);
    stack_free(o);
  }

  stack_free(db_list);
}

/* Read attribute list */
static int get_list(FILE *f, OBJ *obj)
{
  ATTR *atr;
  ALIST *newattr;
  char buf[4096];
  int c;
  int atrnum;

  obj->attrlist = NULL;

  for(;;)
  {
    c = fgetc(f);
    switch(c)
    {
      case '>':             /* Read # then string */
        atrnum = getref(f);
        if((atr = find_attr_by_num(atrnum)))
        {
          strcpy(buf, getstring_noalloc(f));
          atr_add_a(obj, atr, buf);

/* This will happen if buf has no length, which really shouldn't happen */
          if(!(newattr = find_attr_on_obj(obj, atr)))
            continue;
        }
        else          /* Ignore bad attributes */
          getstring_noalloc(f);
        break;
      case '<':                 /* End of list */
        fgets(buf, 1024, f);
        return(1);
      default:
        log_error(tprintf("Bad character %c on object %d",
          c, obj->dbref));
        return(0);
    }
  }
}

void db_set_read(FILE *f)
{
  db_read_file = f;
}

static void run_startups()
{
}

static void welcome_descriptors()
{
  DDATA *d;
  char buf[128];

  strcpy(buf, "MAZE reload complete.  Welcome back!");

  for(d = descriptor_list;d;d = d->next)
    if(check_state(d, STATE_RELOADCONNECT))
    {
      set_state(d, STATE_CONNECTED);
      queue_string(d, buf, 1);
    }
  flush_all_output();
}

static bool check_db_support(char *str)
{
  char *p;

  if(strncmp(str, "MAZEv2", strlen("MAZEv2")))
    return(0);

  if(!(p = strchr(str, '-')))
    current_db_version = 0;
  else
    current_db_version = atoi(p+1);

  if(current_db_version > DB_VERSION)
    return(0);
  return(1);
}

void load_db()
{
  int ctr;
  int total_db;
  char buf[4096], buf2[4096], db_version[4096];
  int c;

  c = getc(db_read_file);
/* Read DB version number */
  if(c != '@')
  {
    log_error("Missing DB version!");
    exit_nicely(1);
  }

  fgets(db_version, sizeof(db_version), db_read_file);
  if(*db_version)
    *(db_version+strlen(db_version)-1) = '\0';

  if(!check_db_support(db_version))
  {
    log_error("This version of TinyMAZE does not support this database version");
    exit_nicely(1);
  }

  if(current_db_version != DB_VERSION)
  {
    log_important("Old database version. Attempting to convert it...");
    log_important("*** START DB CONVERT MESSAGES ***");
  }

  c = getc(db_read_file);

  if(c != '~')
  {
    log_error("Corrupt DB! Looking for '~'");
    exit_nicely(1);
  }

  total_db = getref(db_read_file);

  fgets(buf, sizeof(buf), db_read_file);
  *(buf+strlen(buf)-1) = '\0';
  swlm = stack_string_alloc(buf, 1);

  loading_db = 1;

  if(!quiet_reboot)
  {
    notify_all2("Loading database...", NULL);
    sprintf(buf2, "%d", total_db);
    strcpy(buf, comma(buf2));
    sprintf(buf2, "%ld", file_size(config.db_name));
    notify_all2(tprintf("%s total objects (%s bytes).",
      buf, comma(buf2)), NULL);
    flush_all_output();
  }

  if(current_db_version < 1)
    log_important("All players' powers will be reset to the initial level for their class");

  for(ctr = 0;ctr < total_db;++ctr)
  {
    if(!quiet_reboot && !(ctr%1000))
    {
      sprintf(buf, "Loading object #%d\t(%3.0f%% Done)",
        ctr, 100*(float)ctr/(float)total_db);
      notify_all2(buf, NULL);
      flush_all_output();
    }

    if(db_read_object(db_read_file) == -1)
    {
      log_error("Couldn't load database; shutting down the MAZE.");
      exit_nicely(136);
      break;
    }
  }

  if(current_db_version != DB_VERSION)
    log_important("*** END DB CONVERT MESSAGES ***");

  if(!quiet_reboot)
  {
    notify_all2("Database object load complete.", NULL);
    flush_all_output();
  }

  if(!quiet_reboot)
  {
    notify_all2("Referencing database...", NULL);
    flush_all_output();
  }
  reference_db();

  if(!quiet_reboot)
  {
    notify_all2("Loading mail database...", NULL);
    flush_all_output();
  }
  read_mail();

  load_com_db();
  load_maillists();
  load_todomotd(0);
  load_todomotd(1);
  load_sc_funcs();

  if(!quiet_reboot)
  {
    notify_all2("Running startups.", NULL);
    flush_all_output();
  }
  run_startups();
  welcome_descriptors();

  loading_db = 0;

  log_important("MAZE online.");
}

static void read_programs(FILE *f, OBJ *o)
{
  PROG *p;
  int num_programs, num_lines;
  int line_num;
  char buf[4096];
  char *b;

  o->program = NULL;

  num_programs = getref(f);

  while(num_programs-- > 0)
  {
    p = new_program(o);

    fgets(buf, sizeof(buf), f);
    *(buf+strlen(buf)-1) = '\0';
    p->name = stack_string_alloc(buf, 1);
    fgets(buf, sizeof(buf), f);
    *(buf+strlen(buf)-1) = '\0';
    p->trigger = stack_string_realloc(p->trigger, buf);

    num_lines = getref(f);

    while(num_lines--)
    {
      fgets(buf, sizeof(buf), f);
      *(buf+strlen(buf)-1) = '\0';
      line_num = atoi(buf);
      if((b = strchr(buf, ' '))) 
        add_prog_line(p, line_num, b+1);
    }
  }
}

static void read_comm_aliases(FILE *f, OBJ *o)
{
  int num;
  char buf[4096], buf2[4096];

  num = getref(f);

  while(num--)
  {
    strcpy(buf, getstring_noalloc(f));
    strcpy(buf2, getstring_noalloc(f));
    add_comm_alias(o, buf, buf2);
  }
}

static void update_powers(OBJ *o)
{
  int i;

  for(i = 0;i < NUM_POWS;i++)
    set_pow(o, i, powers[i].init[class_to_list_pos(o->class)]);
}

static int db_read_object(FILE *f)
{
  int c;
  int objnum;
  struct object *o;
  unsigned long flags;

  c = getc(f);

  switch(c)
  {
    case '&':
      objnum = getref(f);
      flags = atol(getstring_noalloc(f));
      o = new_object(flags & TYPE_MASK, 0);
      if(!o)
      {
        log_error("Error creating new object!");
        exit_nicely(1);
      }
      o->loadinfo = (struct db_load_info *)stack_alloc(sizeof(struct db_load_info), 1, 0);
      o->dbref = objnum;
      if(objnum >= db_top)
        db_top = objnum+1;
      o->flags = flags;
      o->flags2 = atol(getstring_noalloc(f));
      getstring(f, &o->name);
      if(Typeof(o) != TYPE_ROOM)
      {
        o->loadinfo->location = getref(f);
        o->loadinfo->link = getref(f);
      }
      if(Typeof(o) == TYPE_PLAYER)
      {
        get_pows(f, o);
        o->class = getref(f);
        if(current_db_version < 1)
          update_powers(o);
      }
      o->loadinfo->owner = getref(f);
      o->mod_time = atol(getstring_noalloc(f));
      o->create_time = atol(getstring_noalloc(f));

      getlists(f, o);
      read_programs(f, o);
      read_comm_aliases(f, o);

      o->loadinfo->creator = getref(f);

      get_list(f, o);
      break;

    default:
      return(-1);
  }

  return(0);
}

/*****************************************************XXX BRG DB CODE XXX*/
#define DB_LOGICAL 0x15

/* atr_fputs - needed to support %r substitution                *
 * does: outputs string <what> on stream <fp>, quoting newlines *
 * with a DB_LOGICAL (currently ctrl-U).                        *
 * added on 4/26/94 by Brian Gaeke (Roodler)                    */

void atr_fputs(char *what, FILE * fp)
{
  while(*what)
  {
    if(*what == '\n')
      fputc(DB_LOGICAL, fp);
    fputc(*what++, fp);
  }
}

/* atr_fgets - needed to support %r substitution              *
 * does: inputs a string, max <size> chars, from stream <fp>, *
 * into buffer <buffer>. if a DB_LOGICAL is encountered,      *
 * the next character (possibly a \n) won't terminate the     *
 * string, as it would in fgets.                              *
 * added on 4/26/94 by Brian Gaeke (Roodler)                  */

char *atr_fgets(char *buffer, int size, FILE *fp)
{
  int num_read = 0;
  char ch, *mybuf = buffer;

  for(;;)
  {
    ch = getc(fp);
    if(ch == EOF)
      break;
    if(ch == DB_LOGICAL)
    {
      ch = getc(fp);
      *mybuf++ = ch;
      num_read++;
      continue;
    }
    if(ch == '\n')
    {
      *mybuf++ = ch;
      num_read++;
      break;
    }
    *mybuf++ = ch;
    num_read++;
    if(num_read > size)
      break;
  }
  *mybuf = '\0';
  return(buffer);
}

/*****************************************************/

void putstring(FILE *f, char *s)
{
  if(s)
    atr_fputs(s, f);
  fputc('\n', f);
}

void do_dump(OBJ *player)
{
  if(!power(player, POW_DB))
  {
    notify(player, perm_denied());
    return;
  }
  notify_except_flag("|+W|** Saving **", NULL, QUIET);
  flush_all_output();
  fork_and_dump();
  notify(player, "Database @dumped.");
}

void info_db(OBJ *player)
{
  notify(player, tprintf("|+B|DB Version|+W|: |+C|MAZEv2-%d", DB_VERSION));
  notify(player, tprintf("|+B|db_top|+W|    : |+C|%d", db_top));
  notify(player, tprintf("|+B|First Free|+W|: |+C|#%d", get_free_dbref()));
  notify(player, tprintf("|+B|DB Size|+W|   : |+C|%s bytes",
    comma(tprintf("%ld", file_size(config.db_name)))));
  notify(player, "");
  do_stats(player, "");
}