/* ************************************************************************
*   File: wld2html.c					                  *
*  Usage: Convert a DikuMUD .wld file into a series of .html files        *
*                                                                         *
*  This program is in the public domain.                                  *
*  Written (QUICKLY AND DIRTILY) by Jeremy Elson (jelson@cs.jhu.edu)      *
*  Based on the Circle 3.0 syntax checker program (scheck.c)              *
************************************************************************ */

#define log(msg) puts(msg)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>


#define NOWHERE    -1		/* nil reference for room-database	 */

/* The cardinal directions: used as index to room_data.dir_option[] */
#define NORTH          0
#define EAST           1
#define SOUTH          2
#define WEST           3
#define UP             4
#define DOWN           5

#define NUM_OF_DIRS	6

#define CREATE(result, type, number)  do {\
	if (!((result) = (type *) calloc ((number), sizeof(type))))\
		{ perror("malloc failure"); abort(); } } while(0)


/* Exit info: used in room_data.dir_option.exit_info */
#define EX_ISDOOR		(1 << 0)	/* Exit is a door		 */
#define EX_CLOSED		(1 << 1)	/* The door is closed	 */
#define EX_LOCKED		(1 << 2)	/* The door is locked	 */
#define EX_PICKPROOF		(1 << 3)	/* Lock can't be picked	 */

#define MAX_STRING_LENGTH	8192

typedef signed char sbyte;
typedef unsigned char ubyte;
typedef signed short int sh_int;
typedef unsigned short int ush_int;
typedef char bool;
typedef char byte;

typedef sh_int room_num;
typedef sh_int obj_num;


char buf[MAX_STRING_LENGTH];
char buf1[MAX_STRING_LENGTH];
char buf2[MAX_STRING_LENGTH];
char arg[MAX_STRING_LENGTH];

/* room-related structures ************************************************/


struct room_direction_data {
  char *general_description;	/* When look DIR.			 */

  char *keyword;		/* for open/close			 */

  sh_int exit_info;		/* Exit info				 */
  obj_num key;			/* Key's number (-1 for no key)		 */
  room_num to_room;		/* Where direction leads (NOWHERE)	 */
};

struct extra_descr_data {
  char *keyword;		/* Keyword in look/examine          */
  char *description;		/* What to see                      */
  struct extra_descr_data *next;/* Next in list                     */
};

struct reset_com {
  char command;			/* current command                      */

  bool if_flag;			/* if TRUE: exe only if preceding exe'd */
  int arg1;			/* */
  int arg2;			/* Arguments to the command             */
  int arg3;			/* */

  /*
   * Commands:              * 'M': Read a mobile     * 'O': Read an object    *
   * 'G': Give obj to mob   * 'P': Put obj in obj    * 'G': Obj to char       *
   * 'E': Obj to char equip * 'D': Set state of door *
   */
};



struct zone_data {
  char *name;			/* name of this zone                  */
  int lifespan;			/* how long between resets (minutes)  */
  int age;			/* current age of this zone (minutes) */
  int top;			/* upper limit for rooms in this zone */

  int reset_mode;		/* conditions for reset (see below)   */
  int number;			/* virtual number of this zone	  */
  struct reset_com *cmd;	/* command table for reset	          */

  /*
   * Reset mode:                              * 0: Don't reset, and don't
   * update age.    * 1: Reset if no PC's are located in zone. * 2: Just
   * reset.                           *
   */
};

/* ================== Memory Structure for room ======================= */
struct room_data {
  room_num number;		/* Rooms number	(vnum)		      */
  sh_int zone;			/* Room zone (for resetting)          */
  int sector_type;		/* sector type (move/hide)            */
  char *name;			/* Rooms name 'You are ...'           */
  char *description;		/* Shown when entered                 */
  struct extra_descr_data *ex_description;	/* for examine/look       */
  struct room_direction_data *dir_option[NUM_OF_DIRS];	/* Directions */
  int room_flags;		/* DEATH,DARK ... etc                 */

  byte light;			/* Number of lightsources in room     */
};
/* ====================================================================== */


/**************************************************************************
*  declarations of most of the 'global' variables                         *
************************************************************************ */

struct room_data *world = NULL;	/* array of rooms		 */
int top_of_world = 0;		/* ref to top element of world	 */

struct zone_data *zone_table = NULL;
int top_of_zone_table = 0;


/* local functions */
char *fread_string(FILE * fl, char *error);
void setup_dir(FILE * fl, int room, int dir);
void index_boot(char *name);
void discrete_load(FILE * fl);
void parse_room(FILE * fl, int virtual_nr);
void parse_mobile(FILE * mob_f, int nr);
char *parse_object(FILE * obj_f, int nr);
void assign_rooms(void);
int file_to_string(char *name, char *buf);
int file_to_string_alloc(char *name, char **buf);
void renum_world(void);
void write_output(void);


char *dir_names[] = {"North", "East", "South", "West", "Up", "Down"};


/*************************************************************************
*  routines for booting the system                                       *
*********************************************************************** */

/* body of the booting system */
int main(int argc, char **argv)
{
  int i;

  if (argc != 2) {
    fprintf(stderr, "Usage: %s <world-file-name>\n", argv[0]);
    exit(1);
  }
  index_boot(argv[1]);

  log("Renumbering rooms.");
  renum_world();

  log("Writing output.");
  write_output();
  log("Boot db -- DONE.");
}


void write_output(void)
{
  int i;
  FILE *fl;
  char buf[128];
  register int door, found;

  for (i = 0; i <= top_of_world; i++) {
    sprintf(buf, "%d", world[i].number);
    log(buf);
    sprintf(buf, "%d.html", world[i].number);

    if (!(fl = fopen(buf, "w"))) {
      perror("opening output file");
      exit(1);
    }
    fprintf(fl, "<title> %s </title>\n", world[i].name);
    fprintf(fl, "<h1> %s </h1>\n", world[i].name);
    fprintf(fl, "<pre>\n");
    fputs(world[i].description, fl);
    fprintf(fl, "</pre>\n");
    fprintf(fl, "<P> Exits: <P> \n");

    found = 0;
    for (door = 0; door < NUM_OF_DIRS; door++)
      if (world[i].dir_option[door] &&
	  world[i].dir_option[door]->to_room != NOWHERE) {
	found = 1;
	fprintf(fl, "<a href = \"%d.html\"> %s to %s</a> <p>\n",
		world[world[i].dir_option[door]->to_room].number,
		dir_names[door],
		world[world[i].dir_option[door]->to_room].name);
      }
    if (!found)
      fprintf(fl, "None!");
    fclose(fl);
  }
}

/* function to count how many hash-mark delimited records exist in a file */
int count_hash_records(FILE * fl)
{
  char buf[128];
  int count = 0;

  while (fgets(buf, 128, fl))
    if (*buf == '#')
      count++;

  return count;
}



void index_boot(char *name)
{
  char *index_filename, *prefix;
  FILE *index, *db_file;
  int rec_count = 0;

  if (!(db_file = fopen(name, "r"))) {
    perror("error opening world file");
    exit(1);
  }
  rec_count = count_hash_records(db_file);
  CREATE(world, struct room_data, rec_count);
  rewind(db_file);
  discrete_load(db_file);
}


void discrete_load(FILE * fl)
{
  int nr = -1, last = 0;
  char line[256];

  for (;;) {
    if (!get_line(fl, line)) {
      fprintf(stderr, "Format error after room #%d\n", nr);
      exit(1);
    }
    if (*line == '$')
      return;

    if (*line == '#') {
      last = nr;
      if (sscanf(line, "#%d", &nr) != 1) {
	fprintf(stderr, "Format error after room #%d\n", nr);
	exit(1);
      }
      if (nr >= 99999)
	return;
      else
	parse_room(fl, nr);
    } else {
      fprintf(stderr, "Format error near room #%d\n", nr);
      fprintf(stderr, "Offending line: '%s'\n", line);
      exit(1);
    }
  }
}



/* load the rooms */
void parse_room(FILE * fl, int virtual_nr)
{
  static int room_nr = 0, zone = 0;
  int t[10], i;
  char line[256];
  struct extra_descr_data *new_descr;

  sprintf(buf2, "room #%d", virtual_nr);

  world[room_nr].zone = zone;
  world[room_nr].number = virtual_nr;
  world[room_nr].name = fread_string(fl, buf2);
  world[room_nr].description = fread_string(fl, buf2);

  if (!get_line(fl, line) || sscanf(line, " %d %d %d ", t, t + 1, t + 2) != 3) {
    fprintf(stderr, "Format error in room #%d\n", virtual_nr);
    exit(1);
  }
  /* t[0] is the zone number; ignored with the zone-file system */
  world[room_nr].room_flags = t[1];
  world[room_nr].sector_type = t[2];

  world[room_nr].light = 0;	/* Zero light sources */

  for (i = 0; i < NUM_OF_DIRS; i++)
    world[room_nr].dir_option[i] = NULL;

  world[room_nr].ex_description = NULL;

  sprintf(buf, "Format error in room #%d (expecting D/E/S)", virtual_nr);

  for (;;) {
    if (!get_line(fl, line)) {
      fprintf(stderr, "%s\n", buf);
      exit(1);
    }
    switch (*line) {
    case 'D':
      setup_dir(fl, room_nr, atoi(line + 1));
      break;
    case 'E':
      CREATE(new_descr, struct extra_descr_data, 1);
      new_descr->keyword = fread_string(fl, buf2);
      new_descr->description = fread_string(fl, buf2);
      new_descr->next = world[room_nr].ex_description;
      world[room_nr].ex_description = new_descr;
      break;
    case 'S':			/* end of room */
      top_of_world = room_nr++;
      return;
      break;
    default:
      fprintf(stderr, "%s\n", buf);
      exit(1);
      break;
    }
  }
}



/* read direction data */
void setup_dir(FILE * fl, int room, int dir)
{
  int t[5];
  char line[256];

  sprintf(buf2, "room #%d, direction D%d", world[room].number, dir);

  CREATE(world[room].dir_option[dir], struct room_direction_data, 1);
  world[room].dir_option[dir]->general_description = fread_string(fl, buf2);
  world[room].dir_option[dir]->keyword = fread_string(fl, buf2);

  if (!get_line(fl, line)) {
    fprintf(stderr, "Format error, %s\n", buf2);
    exit(1);
  }
  if (sscanf(line, " %d %d %d ", t, t + 1, t + 2) != 3) {
    fprintf(stderr, "Format error, %s\n", buf2);
    exit(1);
  }
  if (t[0] == 1)
    world[room].dir_option[dir]->exit_info = EX_ISDOOR;
  else if (t[0] == 2)
    world[room].dir_option[dir]->exit_info = EX_ISDOOR | EX_PICKPROOF;
  else
    world[room].dir_option[dir]->exit_info = 0;

  world[room].dir_option[dir]->key = t[1];
  world[room].dir_option[dir]->to_room = t[2];
}


/* resolve all vnums into rnums in the world */
void renum_world(void)
{
  register int room, door;

  for (room = 0; room <= top_of_world; room++)
    for (door = 0; door < NUM_OF_DIRS; door++)
      if (world[room].dir_option[door])
	if (world[room].dir_option[door]->to_room != NOWHERE)
	  world[room].dir_option[door]->to_room =
	    real_room(world[room].dir_option[door]->to_room);
}



/*************************************************************************
*  procedures for resetting, both play-time and boot-time	 	 *
*********************************************************************** */


/* read and allocate space for a '~'-terminated string from a given file */
char *fread_string(FILE * fl, char *error)
{
  char buf[MAX_STRING_LENGTH], tmp[512], *rslt;
  register char *point;
  int done = 0, length = 0, templength = 0;

  bzero(buf, MAX_STRING_LENGTH);
  do {
    if (!fgets(tmp, 512, fl)) {
      fprintf(stderr, "SYSERR: fread_string: format error at or near %s\n",
	      error);
      exit(1);
    }
    /* If there is a '~', end the string; else an "\r\n" over the '\n'. */
    if ((point = strchr(tmp, '~')) != NULL) {
      *point = '\0';
      done = 1;
    } else {
      point = tmp + strlen(tmp) - 1;
      *(point++) = '\n';
      *point = '\0';
    }

    templength = strlen(tmp);

    if (length + templength >= MAX_STRING_LENGTH) {
      log("SYSERR: fread_string: string too large (db.c)");
      exit(1);
    } else {
      strcat(buf + length, tmp);
      length += templength;
    }
  } while (!done);

  if (strlen(buf) > 0) {
    CREATE(rslt, char, strlen(buf) + 1);
    strcpy(rslt, buf);
  } else
    rslt = NULL;

  return rslt;
}


/* read contents of a text file, and place in buf */
int file_to_string(char *name, char *buf)
{
  FILE *fl;
  char tmp[100];

  *buf = '\0';

  if (!(fl = fopen(name, "r"))) {
    sprintf(tmp, "Error reading %s", name);
    perror(tmp);
    *buf = '\0';
    return (-1);
  }
  do {
    fgets(tmp, 99, fl);

    if (!feof(fl)) {
      if (strlen(buf) + strlen(tmp) + 2 > MAX_STRING_LENGTH) {
	log("SYSERR: fl->strng: string too big (db.c, file_to_string)");
	*buf = '\0';
	return (-1);
      }
      strcat(buf, tmp);
      *(buf + strlen(buf) + 1) = '\0';
      *(buf + strlen(buf)) = '\r';
    }
  } while (!feof(fl));

  fclose(fl);

  return (0);
}



/* returns the real number of the room with given virtual number */
int real_room(int virtual)
{
  int bot, top, mid;

  bot = 0;
  top = top_of_world;

  /* perform binary search on world-table */
  for (;;) {
    mid = (bot + top) / 2;

    if ((world + mid)->number == virtual)
      return (mid);
    if (bot >= top) {
      fprintf(stderr, "Room %d does not exist in database\n", virtual);
      return (-1);
    }
    if ((world + mid)->number > virtual)
      top = mid - 1;
    else
      bot = mid + 1;
  }
}


/* get_line reads the next non-blank line off of the input stream.
 * The newline character is removed from the input.  Lines which begin
 * with '*' are considered to be comments.
 */
int get_line(FILE * fl, char *buf)
{
  char temp[256];

  do {
    fgets(temp, 256, fl);
    if (*temp)
      temp[strlen(temp) - 1] = '\0';
  } while (!feof(fl) && (*temp == '*' || !*temp));

  if (feof(fl))
    return 0;
  else {
    strcpy(buf, temp);
    return 1;
  }
}
