/
roa/
roa/lib/boards/
roa/lib/config/
roa/lib/edits/
roa/lib/help/
roa/lib/misc/
roa/lib/plrobjs/
roa/lib/quests/
roa/lib/socials/
roa/lib/www/
roa/lib/www/LEDSign/
roa/lib/www/LEDSign/fonts/
roa/lib/www/LEDSign/scripts/
roa/src/s_inc/
roa/src/sclient/
roa/src/sclient/binary/
roa/src/sclient/text/
roa/src/util/
/************************************************************************
	Realms of Aurealis 		James Rhone aka Vall of RoA

house.c					Player house code and immortal
					interfaces with it.  Based on 
					Circle3.0's beta 8 house code
					and heavily modified since.

		******** Heavily modified and expanded ********
		*** BE AWARE OF ALL RIGHTS AND RESERVATIONS ***
		******** Heavily modified and expanded ********
		        All rights reserved henceforth. 

    Please note that no guarantees are associated with any code from
Realms of Aurealis.  All code which has been released to the general
public has been done so with an 'as is' pretense.  RoA is based on both
Diku and CircleMUD and ALL licenses from both *MUST* be adhered to as well
as the RoA license.   *** Read, Learn, Understand, Improve ***
*************************************************************************/
//
// Modifications:
//	1/6/97 - streamline, change, rewrite (jtrhone)
//		 House code is unacceptably unstable and its behavior
//		 has prompted this rewrite.

#include "conf.h"
#include "sysdep.h"

#include "structures.h"
#include "comm.h"
#include "handler.h"
#include "db.h"
#include "interpreter.h"
#include "acmd.h"
#include "utils.h"
#include "house.h"
#include "objsave.h"
#include "lists.h"
#include "global.h"

// external functions
extern long get_id_by_name(char *name);
extern char *get_name_by_id(long id);
extern int  inv_count(obdata *ob);
extern BOOL load_house_objects(int hvnum);
extern BOOL save_object(obdata *obj, FILE *fp);

// internal globals
int num_houses = 0;
houserec house_control[MAX_HOUSES];

// No longer save object files in BINARY format
// save them in text format (re: objsave.c)  -roa
void crashsave_house(int vnum)
{
  int rnum;
  char fname[128];
  FILE *fp;

  if ((rnum = real_room(vnum)) < 0)
    return;

  sprintf(fname, "house/%d.house",vnum);
  if (!(fp = fopen(fname, "w"))) {
    sprintf(buf, "SYSERR: Error opening house file %d.house",vnum);
    mudlog(buf, BRF, LEV_IMM, TRUE);
    return;
  }

  if (inv_count(world[rnum].contents) > MAX_HOUSE_OBJS)
  {
    sprintf(buf, "%%1WARNING%%0: Max house contents (%d) exceeded.\n\r"
                 "House contents not saved.\n\r", MAX_HOUSE_OBJS);
    send_to_room(buf, rnum);
    fclose(fp);
    return;
  }

  // if nothing in room, remove file and return
  if (!world[rnum].contents)
  {
    fclose(fp);
    remove(fname);
    return;
  }

  // get rid of the non rentables in the house (keys/mails etc) -roa
  extract_norents(world[rnum].contents);

  // check one more time just so we're not calling a function for no reason
  // if the room only had norents, now it has nothing, remove file and return 
  if (!world[rnum].contents)
  {
    fclose(fp);
    remove(fname);
    return;
  }

  // save_object is recursive, just send the first object in the list
  // re: objsave.c
  if (!save_object(world[rnum].contents, fp)) 
  {
    fclose(fp);
    send_to_room("Error saving house contents, notify an immortal...\n\r",rnum);
    return;
  }

  fclose(fp);
  send_to_room("Saving house...\n\r", rnum);
  REMOVE_BIT(ROOM_FLAGS2(rnum), HOUSE_CRASH);

  sprintf(buf, "SYSUPD: House #%d saved.", vnum);
  mudlog(buf, BUG, LEV_IMM, FALSE);
}

// wax a house file from disk, mudlog any errors -roa
void delete_house_file(int vnum)
{
  char fname[128];
  FILE *fp;

  sprintf(fname, "house/%d.house",vnum);
  if (!(fp = fopen(fname, "r"))) 
  {
    if (errno != ENOENT) {
      sprintf(buf, "SYSERR: Error deleting house file #%d. (1)", vnum);
      mudlog(buf, BRF, LEV_IMM, TRUE);
    }
    return;
  }

  fclose(fp);
  if (unlink(fname) < 0) {
    sprintf(buf, "SYSERR: Error deleting house file #%d. (2)", vnum);
    mudlog(buf, BRF, LEV_IMM, TRUE);
  }
}

/******************************************************************
 *  Functions for house administration (creation, deletion, etc.  *
 *****************************************************************/
int find_house(sh_int vnum)
{
  int i;

  for (i = 0; i < num_houses; i++)
    if (house_control[i].vnum == vnum)
      return i;
  return -1;
}

/* Save the house control information */
void save_house_control(void)
{
  FILE *fp;

  if (!(fp = fopen(HCONTROL_FILE, "wb"))) {
    perror("SYSERR: Unable to open house control file");
    return;
  }

  // al la JE, write the block in on fell swoop
  fwrite(house_control, sizeof(houserec), num_houses, fp);
  fclose(fp);
}

/* call from boot_db - will load control recs, load objs */
/* should do sanity checks on vnums & remove invalid records */
void boot_houses(void)
{
  houserec temp_house;
  int rroom;
  FILE *fp;

  // clear everything out before we fill it
  memset((char *)house_control, 0, sizeof(houserec) * MAX_HOUSES);

  if (!(fp = fopen(HCONTROL_FILE, "rb"))) {
    mudlog("SYSERR: Unable to locate house control file.", 
           BRF, LEV_IMM, TRUE);
    return;
  }

  while (!feof(fp) && num_houses < MAX_HOUSES) 
  {
    fread(&temp_house, sizeof(houserec), 1, fp);

    if (feof(fp))
      break;

    // if owner no longer in player_index, skip it
    if (!get_name_by_id(temp_house.owner))
      continue;

    // if the vnum doesnt correspond to an actual room, skip it
    if ((rroom = real_room(temp_house.vnum)) < 0)
      continue;

    // if we have already loaded this house, skip it
    if ((find_house(temp_house.vnum)) >= 0)
      continue;

    // copy data over, bump up house_counter
    house_control[num_houses++] = temp_house;

    // set appropriate flags on the entry room
    SET_BIT(ROOM_FLAGS(rroom), PRIVATE);
    SET_BIT(ROOM_FLAGS2(rroom), HOUSE);
    
    // load the objects into the house
    load_house_objects(temp_house.vnum);
  }
  fclose(fp);

  // finished reading in, now save it again
  save_house_control();
}

/* "House Control" functions */

char *HCONTROL_FORMAT =
"Usage: hcontrol build     <house vnum> <player name>\r\n"
"       hcontrol destroy   <house vnum>\r\n"
"       hcontrol pay       <house vnum>\r\n"
"       hcontrol guestlist <house vnum>\n\r"
"       hcontrol squish    <house vnum>\n\r"
"       hcontrol show\r\n";

#define NAME(x) ((temp = get_name_by_id(x)) == NULL ? "<UNDEF>" : temp)

void list_houses(chdata * ch)
{
  int i;
  char *timestr, *temp;
  char built_on[50], last_pay[50], own_name[50];
  char buf[20000];

  if (!num_houses) {
    send_to_char("No houses have been defined.\r\n", ch);
    return;
  }

  strcpy(buf, "%B%6 Vnum    Owner    #Guests  Build Date       Last Paymt%0\r\n");
  strcat(buf, "%B%4------ ---------  -------  ----------       ----------%0\r\n");

  // go thru each house in house_control array and output info
  for (i = 0; i < num_houses; i++) 
  {
    // determine build date
    if (house_control[i].built_on) {
      timestr = asctime(localtime(&(house_control[i].built_on)));
      *(timestr + 10) = 0;
      str_cpy(built_on, timestr, 50, "list_houses");
    } else
      strcpy(built_on, "%4Unknown%0");

    // determine last payment date
    if (house_control[i].last_payment) {
      timestr = asctime(localtime(&(house_control[i].last_payment)));
      *(timestr + 10) = 0;
      strcpy(last_pay, timestr);
    } else
      strcpy(last_pay, "%4None%0");

    // owner's name in here
    strcpy(own_name, NAME(house_control[i].owner));

    // now throw that info into the buf
    sprintf(buf+strlen(buf), 
	    "%-6d %-9.9s    %-7d%-10.10s       %-10.10s\n\r",
	    house_control[i].vnum, CAP(own_name), house_control[i].num_guests,
	    built_on, last_pay);
  }
  page_string(ch->desc, buf, 1); 
}

void list_guests_to_char(chdata *ch, char *arg)
{
  char owner[128];
  int i, j;
  int vnum;
  char *temp;

  if (!is_number(arg))
  {
    send_to_char("Argument must be a vnum.\n\r",ch);
    return;
  }
  
  vnum = atoi(arg);

  if ((i = find_house(vnum)) < 0)
  {
    sprintf(buf, "Vnum %d not defined in house control record.\n\r",vnum);
    S2C();
    return;
  }

  strcpy(owner, NAME(house_control[i].owner));

  sprintf(buf, "%%B%%6%s House Guest List%%0: House #%%6%d%%0, Owner: %%6%s%%0\n\r",
          shortmudname, house_control[i].vnum, CAP(owner));
  S2C();

  for (j = 0; j < house_control[i].num_guests; j++) 
  {
    strcpy(buf, NAME(house_control[i].guests[j]));
    send_to_char(strcat(CAP(buf), "\r\n"), ch);
  }
}

// do the actual flagging of the house here
// record build date and other data, no need for atriums anymore -roa
void hcontrol_build_house(chdata *ch, char *arg)
{
  char arg1[MAX_INPUT_LENGTH];
  houserec temp_house;
  sh_int virt_house, real_house;
  long owner;

  if (num_houses >= MAX_HOUSES) {
    send_to_char("Max number of houses already defined.  See Vall.\n\r",ch);
    return;
  }

  /* first arg: house's vnum */
  arg = one_argument(arg, arg1);
  if (!*arg1) {
    send_to_char(HCONTROL_FORMAT, ch);
    return;
  }

  virt_house = atoi(arg1);
  if ((real_house = real_room(virt_house)) < 0) {
    send_to_char("No such room exists.\r\n", ch);
    return;
  }

  if ((find_house(virt_house)) >= 0) {
    send_to_char("House already exists.\r\n", ch);
    return;
  }

  /* second arg: player's name */
  arg = one_argument(arg, arg1);
  if (!*arg1) {
    send_to_char(HCONTROL_FORMAT, ch);
    return;
  }

  if ((owner = get_id_by_name(arg1)) < 0) {
    sprintf(buf, "Unknown player '%s'.\r\n", arg1);
    S2C();
    return;
  }

  temp_house.vnum         = virt_house;
  temp_house.built_on     = time(0);
  temp_house.owner        = owner;
  temp_house.num_guests   = 0;
  temp_house.last_payment = 0;
  temp_house.bitvector    = 0;
  world[real_house].owner = owner;

  house_control[num_houses++] = temp_house;

  SET_BIT(ROOM_FLAGS(real_house), PRIVATE);
  SET_BIT(ROOM_FLAGS2(real_house), HOUSE);
  crashsave_house(virt_house);
  save_house_control();

  send_to_char("House successfully created.\r\n", ch);
  send_to_char("Use %6rconnect%0 and %6wldsave%0 to connect and save.\n\r",ch);
  send_to_char("Remember: after rconnecting, wldsave BOTH affected zones.\n\r",ch);
}

void hcontrol_destroy_house(chdata *ch, char *arg)
{
  int i, j;
  int real_house;

  if (!*arg) {
    send_to_char(HCONTROL_FORMAT, ch);
    return;
  }

  if ((i = find_house(atoi(arg))) < 0) {
    send_to_char("Unknown house.\r\n", ch);
    return;
  }

  if ((real_house = real_room(house_control[i].vnum)) < 0)
  {
    sprintf(buf, "SYSERR: House #%d, invalid vnum.",house_control[i].vnum);
    mudlog(buf, BRF, LEV_IMM, TRUE);
  }

  delete_house_file(house_control[i].vnum);

  for (j = i; j < num_houses - 1; j++)
    house_control[j] = house_control[j + 1];

  num_houses--;

  send_to_char("House deleted.\r\n", ch);
  save_house_control();

  // make sure the world jives, update it
  if (real_house >= 0)
  {
    world[real_house].owner = -1;
    FREENULL(world[real_house].name);
    FREENULL(world[real_house].description);
    world[real_house].name = str_dup("(default)");
    world[real_house].description = str_dup("(default)\n\r");
    REMOVE_BIT(ROOM_FLAGS(real_house),  PRIVATE);
    REMOVE_BIT(ROOM_FLAGS2(real_house), HOUSE | HOUSE_CRASH);
  }
}

void hcontrol_pay_house(chdata *ch, char *arg)
{
  int i;
  char owner[128];
  char *temp;

  if (!*arg)
  {
    send_to_char(HCONTROL_FORMAT, ch);
    return;
  }

  if ((i = find_house(atoi(arg))) < 0)
  {
    send_to_char("Unknown house.\r\n", ch);
    return;
  }

  strcpy(owner, NAME(house_control[i].owner));
  sprintf(buf, "PLRUPD: %s's house payment collected by %s.",
          CAP(owner), GET_NAME(ch));
  mudlog(buf, NRM, LEV_IMM, TRUE);

  house_control[i].last_payment = time(0);

  save_house_control();
  send_to_char("Payment recorded.\r\n", ch);
}

// eliminate guests from a house who are no longer in the pfile
void squish_house(chdata *ch, char *arg)
{
  char owner[128];
  int i, j, k, count;
  int vnum;
  char *temp;

  if (!is_number(arg))
  {
    send_to_char("Argument must be a vnum.\n\r",ch);
    return;
  }
  
  vnum = atoi(arg);

  if ((i = find_house(vnum)) < 0)
  {
    sprintf(buf, "Vnum %d not defined in house control record.\n\r",vnum);
    S2C();
    return;
  }

  strcpy(owner, NAME(house_control[i].owner));
  sprintf(buf, "Squishing %s's house.\n\r",CAP(owner));
  S2C();

  for (count = 0, j = 0; j < house_control[i].num_guests; j++)
    if (!get_name_by_id(house_control[i].guests[j])) 
    {
      for (k = j; k < house_control[i].num_guests - 1; k++)
	house_control[i].guests[k] = house_control[i].guests[k + 1];

      house_control[i].num_guests--;
      count++;
    }

  sprintf(buf, "%d invalid entries removed.\n\r",count);
  S2C();
}


// The hcontrol command itself, used by imms to create/destroy houses
// revamped, 1/6/97 jtrhone aka vall (see header above) 
ACMD(do_hcontrol)
{
  char arg1[MAX_INPUT_LENGTH], arg2[MAX_INPUT_LENGTH];

  half_chop(argument, arg1, arg2);

  if (is_abbrev(arg1, "build"))
    hcontrol_build_house(ch, arg2);
  else if (is_abbrev(arg1, "destroy"))
    hcontrol_destroy_house(ch, arg2);
  else if (is_abbrev(arg1, "guestlist"))
    list_guests_to_char(ch, arg2);
  else if (is_abbrev(arg1, "pay"))
    hcontrol_pay_house(ch, arg2);
  else if (is_abbrev(arg1, "squish"))
    squish_house(ch, arg2);
  else if (is_abbrev(arg1, "show"))
    list_houses(ch);
  else
    send_to_char(HCONTROL_FORMAT, ch);
}

/* The house command, used by mortal house owners to assign guests */
ACMD(do_house)
{
  int i, j, id;
  char tmparg[MAX_INPUT_LENGTH];

  one_argument(argument, arg);

  if (!ROOM_FLAGGED2(ch->in_room, HOUSE))
    send_to_char("You must be in your house to set guests.\r\n", ch);
  else if ((i = find_house(world[ch->in_room].number)) < 0)
    send_to_char("Your house was not found...seek immortal help.\r\n", ch);
  else if (GET_IDNUM(ch) != house_control[i].owner)
    send_to_char("Only the primary owner can set guests.\r\n", ch);
  else if (!*arg) 
  {
    sprintf(tmparg, "%d", world[ch->in_room].number);
    list_guests_to_char(ch, tmparg);
  } 
  else if (!str_cmp(arg, "public"))
  {
    SET_BIT(house_control[i].bitvector, HOUSE_PUBLIC);
    send_to_char("House now open to any player.\n\r",ch);
  }
  else if (!str_cmp(arg, "private"))
  {
    REMOVE_BIT(house_control[i].bitvector, HOUSE_PUBLIC);
    send_to_char("House now restricted to guest list.\n\r",ch);
  }
  else if ((id = get_id_by_name(arg)) < 0)
    send_to_char("No such player.\r\n", ch);
  else 
  {
    for (j = 0; j < house_control[i].num_guests; j++)
      if (house_control[i].guests[j] == id) 
      {
	for (; j < house_control[i].num_guests - 1; j++)
	  house_control[i].guests[j] = house_control[i].guests[j + 1];

	house_control[i].num_guests--;
	save_house_control();
	send_to_char("Guest deleted.\r\n", ch);
	return;
      }

    // put in max guest check
    if (house_control[i].num_guests >= MAX_GUESTS)
    {
      send_to_char("You have the maximum number of guests already.\n\r",ch);
      return;
    }
    
    j = house_control[i].num_guests++;
    house_control[i].guests[j] = id;
    save_house_control();
    send_to_char("Guest added.\r\n", ch);
  }
}

/* Misc. administrative functions */
/* crash-save all the houses */
void House_save_all(void)
{
  int i;
  sh_int real_house;

  for (i = 0; i < num_houses; i++)
    if ((real_house = real_room(house_control[i].vnum)) >= 0)
      if (ROOM_FLAGGED2(real_house, HOUSE_CRASH))
	crashsave_house(house_control[i].vnum);
}

// can a certain character enter a house
int House_can_enter(chdata *ch, int house)
{
  int i, j;

  if (GET_LEVEL(ch) >= LEV_GOD || (i = find_house(house)) < 0)
    return TRUE;

  if (GET_IDNUM(ch) == house_control[i].owner)
    return TRUE;

  for (j = 0; j < house_control[i].num_guests; j++)
    if (GET_IDNUM(ch) == house_control[i].guests[j])
      return TRUE;

  if (HOUSE_FLAGGED(i, HOUSE_PUBLIC))
    return TRUE;

  return FALSE;
}