/* ************************************************************************
*  file: reception.c, Special module for Inn's.           Part of DIKUMUD *
*  Usage: Procedures handling saving/loading of player objects            *
*  Copyright (C) 1990, 1991 - see 'license.doc' for complete information. *
************************************************************************* */

#include "os.h"

#include "structs.h"
#include "comm.h"
#include "handler.h"
#include "db.h"
#include "interpreter.h"
#include "utils.h"
#include "spells.h"
#include "prototypes.h"

#define OBJ_SAVE_FILE "pcobjs.obj"
#define OBJ_FILE_FREE "\0\0\0"

extern struct room_data *world;
extern struct index_data *mob_index;
extern struct index_data *obj_index;

/* Extern functions */

void store_to_char (struct char_file_u *st, struct char_data *ch);
void do_tell (struct char_data *ch, char *argument, int cmd);
int str_cmp (char *arg1, char *arg2);
void clear_char (struct char_data *ch);


struct obj_cost {
  int total_cost;
  int no_carried;
  bool ok;
};


/* ************************************************************************
* Routines used for the "Offer"                                           *
************************************************************************* */

void add_obj_cost (struct char_data *ch, struct char_data *re,
  struct obj_data *obj, struct obj_cost *cost)
{
  char buf[MAX_STRING_LENGTH];

  /* Add cost for an item and it's conents, and next->contents */

  if (obj) {
    if ((obj->item_number > -1) && (cost->ok)) {
      cost->total_cost += MAX (0, obj->obj_flags.cost_per_day);
      cost->no_carried++;
      add_obj_cost (ch, re, obj->contains, cost);
      add_obj_cost (ch, re, obj->next_content, cost);
    } else if (cost->ok) {
      act ("$n tells you 'I refuse storing $p'", FALSE, re, obj, ch, TO_VICT);
      cost->ok = FALSE;
    }

  }
}


bool recep_offer (struct char_data *ch, struct char_data *receptionist,
  struct obj_cost *cost)
{
  int i;
  char buf[MAX_STRING_LENGTH];

  cost->total_cost = 100;       /* Minimum cost */
  cost->no_carried = 0;
  cost->ok = TRUE;              /* Use if any "-1" objects */

  add_obj_cost (ch, receptionist, ch->carrying, cost);

  for (i = 0; i < MAX_WEAR; i++)
    add_obj_cost (ch, receptionist, ch->equipment[i], cost);

  if (!cost->ok)
    return (FALSE);

  if (cost->no_carried == 0) {
    act ("$n tells you 'But you are not carrying anything?'", FALSE,
      receptionist, 0, ch, TO_VICT);
    return (FALSE);
  }

  if (cost->no_carried > MAX_OBJ_SAVE) {
    sprintf (buf,
      "$n tells you 'Sorry, but I can't store any more than %d items.",
      MAX_OBJ_SAVE);
    act (buf, FALSE, receptionist, 0, ch, TO_VICT);
    return (FALSE);
  }

  sprintf (buf, "$n tells you 'It will cost you %d coins per day'",
    cost->total_cost);
  act (buf, FALSE, receptionist, 0, ch, TO_VICT);

  if (cost->total_cost > GET_GOLD (ch)) {
    if (GET_LEVEL (ch) < 21)
      act ("$n tells you 'Which I can see you can't afford'",
        FALSE, receptionist, 0, ch, TO_VICT);
    else {
      act ("$n tells you 'Well, since you're a God, I guess it's okay'",
        FALSE, receptionist, 0, ch, TO_VICT);
      cost->total_cost = 0;
    }
  }

  if (cost->total_cost > GET_GOLD (ch))
    return (FALSE);
  else
    return (TRUE);
}


/* ************************************************************************
* General save/load routines                                              *
************************************************************************* */

/* pos == 0 is first position in file! */
void update_file (FILE * fl, int pos, struct obj_file_u *st)
{
  if (fseek (fl, sizeof (struct obj_file_u) * (pos), 0)) {
    perror ("Seeking in file update (update_file, reception.c)");
    WIN32CLEANUP
    exit (1);
  }
  if (fwrite (st, sizeof (struct obj_file_u), 1, fl) < 1) {
    perror ("Error updating file (update_file, reception.c).");
    WIN32CLEANUP
    exit (1);
  }

  /* WHY OH WHY?!??!? I can't tell, but I doesn't work without */
  /* Something with update mode and reads after writes         */
  if (fseek (fl, sizeof (struct obj_file_u) * (pos + 1), 0)) {
    perror ("Seeking in file update (update_file, reception.c)");
    WIN32CLEANUP
    exit (1);
  }

}


/* ************************************************************************
* Routines used to load a characters equipment from disk                  *
************************************************************************* */

void obj_store_to_char (struct char_data *ch, struct obj_file_u *st)
{
  struct obj_data *obj;
  int i, j;

  void obj_to_char (struct obj_data *object, struct char_data *ch);

  for (i = 0; i < MAX_OBJ_SAVE; i++) {
    if (st->objects[i].item_number > -1) {
      if (real_object (st->objects[i].item_number) > -1) {
        obj = read_object (st->objects[i].item_number, VIRTUAL);

        obj->obj_flags.value[0] = st->objects[i].value[0];
        obj->obj_flags.value[1] = st->objects[i].value[1];
        obj->obj_flags.value[2] = st->objects[i].value[2];
        obj->obj_flags.value[3] = st->objects[i].value[3];

        obj->obj_flags.extra_flags = st->objects[i].extra_flags;
        obj->obj_flags.weight = st->objects[i].weight;
        obj->obj_flags.timer = st->objects[i].timer;
        obj->obj_flags.bitvector = st->objects[i].bitvector;

        for (j = 0; j < MAX_OBJ_AFFECT; j++)
          obj->affected[j] = st->objects[i].affected[j];

        obj_to_char (obj, ch);
      }
    }
  }
}


void load_char_objs (struct char_data *ch)
{
  FILE *fl;
  int pos, i, j;
  bool found = FALSE;
  float timegold;
  struct obj_file_u st;


  /* r+b is for Binary Reading/Writing */
  if (!(fl = fopen (OBJ_SAVE_FILE, "r+b"))) {
    perror ("Opening object file for Loading PC's objects");
    WIN32CLEANUP
    exit (1);
  }

  pos = 0;

  while (!feof (fl) && !found) {
    pos += fread (&st, sizeof (struct obj_file_u), 1, fl);
    found = !str_cmp (st.owner, GET_NAME (ch));
  }

  if (found) {

    obj_store_to_char (ch, &st);

    /* To avoid overflow of int */

    timegold =
      (unsigned) (((float) st.total_cost * (float) (time (0) -
          st.last_update))
      / (float) (SECS_PER_REAL_DAY));

    GET_GOLD (ch) -= timegold;

    if (GET_GOLD (ch) < 0)
      GET_GOLD (ch) = 0;


    if (fseek (fl, sizeof (struct obj_file_u) * (pos - 1), 0)) {
      perror ("seeking in PC objects file.");
      WIN32CLEANUP
      exit (1);
    }
    strcpy (st.owner, OBJ_FILE_FREE);
    if (fwrite (&st, sizeof (struct obj_file_u), 1, fl) < 1) {
      log ("Error updating name to be set as unused.");
      WIN32CLEANUP
      exit (1);
    }

  } else {
    log ("Char has no data in file!");
  }

  fclose (fl);

  /* Save char, to avoid strange data if crashing */
  save_char (ch, NOWHERE);

}


/* ************************************************************************
* Routines used to save a characters equipment from disk                  *
************************************************************************* */

/* Puts object in store, at first item which has no -1 */
void put_obj_in_store (struct obj_data *obj, struct obj_file_u *st)
{
  int i, j;
  bool found = FALSE;

  for (i = 0; (i < MAX_OBJ_SAVE) && !found; i++)
    if (st->objects[i].item_number == -1) {
      st->objects[i].item_number = obj_index[obj->item_number].virtual;
      st->objects[i].value[0] = obj->obj_flags.value[0];
      st->objects[i].value[1] = obj->obj_flags.value[1];
      st->objects[i].value[2] = obj->obj_flags.value[2];
      st->objects[i].value[3] = obj->obj_flags.value[3];

      st->objects[i].extra_flags = obj->obj_flags.extra_flags;
      st->objects[i].weight = obj->obj_flags.weight;
      st->objects[i].timer = obj->obj_flags.timer;
      st->objects[i].bitvector = obj->obj_flags.bitvector;
      for (j = 0; j < MAX_OBJ_AFFECT; j++)
        st->objects[i].affected[j] = obj->affected[j];
      found = TRUE;
    }

  if (!found) {
    log ("No empty space to store object. (put_obj_in_store, reception.c)");
    exit (1);
  }
}



/* Destroy inventory after transferring it to "store inventory" */
void obj_to_store (struct obj_data *obj, struct obj_file_u *st,
  struct char_data *ch)
{
  static char buf[240];

  if (obj) {
    obj_to_store (obj->contains, st, ch);
    obj_to_store (obj->next_content, st, ch);

    if ((obj->obj_flags.timer < 0) && (obj->obj_flags.timer != OBJ_NOTIMER)) {
      sprintf (buf,
        "You're told: 'The %s is just old junk, I'll throw it away for you.'\n\r",
        fname (obj->name));
      send_to_char (buf, ch);
    } else {
      put_obj_in_store (obj, st);
      if (obj->in_obj)
        obj_from_obj (obj);
      extract_obj (obj);
    }
  }
}



/* write the vital data of a player to the player file */
void save_obj (struct char_data *ch, struct obj_cost *cost)
{
  struct obj_file_u st, dummy;
  FILE *fl;
  int pos, i, j;
  bool found = FALSE;


  for (j = 0; j < MAX_OBJ_SAVE; j++) {
    st.objects[j].item_number = -1;     /* Set as not used */
    st.objects[j].value[0] = 0;
    st.objects[j].value[1] = 0;
    st.objects[j].value[2] = 0;
    st.objects[j].value[3] = 0;
    st.objects[j].extra_flags = 0;
    st.objects[j].weight = 0;
    st.objects[j].timer = 0;

    st.objects[j].bitvector = 0;
    for (i = 0; i < MAX_OBJ_AFFECT; i++) {
      st.objects[j].affected[i].location = 0;
      st.objects[j].affected[i].modifier = 0;
    }

  }


  strcpy (st.owner, GET_NAME (ch));
  st.gold_left = GET_GOLD (ch);
  st.total_cost = cost->total_cost;
  st.last_update = time (0);

  obj_to_store (ch->carrying, &st, ch);
  ch->carrying = 0;

  for (i = 0; i < MAX_WEAR; i++)
    if (ch->equipment[i]) {
      obj_to_store (ch->equipment[i], &st, ch);
      unequip_char (ch, i);
    }

  if (!(fl = fopen (OBJ_SAVE_FILE, "a+b"))) {
    perror ("saving PC's objects");
    WIN32CLEANUP
    exit (1);
  }


  if (fseek (fl, 0, 0)) {       /* Move to beginning of file */
    perror ("seeking to start of PC objects file.");
    WIN32CLEANUP
    exit (1);
  }

  pos = 0;

  while (!feof (fl) && !found) {
    pos += fread (&dummy, sizeof (struct obj_file_u), 1, fl);
    found = (dummy.owner[0] == '\0');
  }

  if (feof (fl))
    update_file (fl, pos, &st);
  else {
    if (!found) {
      perror ("Really strange...\n\r");
      WIN32CLEANUP
      exit (1);
    }

    update_file (fl, pos - 1, &st);
  }

  fclose (fl);
}



/* ************************************************************************
* Routines used to update object file, upon boot time                     *
************************************************************************* */

void update_obj_file (void)
{
  FILE *fl, *char_file;
  struct obj_file_u st;
  struct char_file_u ch_st;
  struct char_data tmp_char;
  int pos, no_read, player_i;
  long days_passed, secs_lost;
  char buf[MAX_STRING_LENGTH];

  int find_name (char *name);
  extern struct player_index_element *player_table;

  if (!(char_file = fopen (PLAYER_FILE, "r+b"))) {
    perror
      ("   Opening player file for reading. (reception.c, update_obj_file)");
    WIN32CLEANUP
    exit (1);
  }

  /* r+b is for Binary Reading/Writing */
  if (!(fl = fopen (OBJ_SAVE_FILE, "r+b"))) {
    perror ("   Opening object file for updating");
    WIN32CLEANUP
    exit (1);
  }


  pos = 0;

  while (!feof (fl)) {
    no_read = fread (&st, sizeof (struct obj_file_u), 1, fl);
    pos += no_read;

    if ((!feof (fl)) && (no_read > 0) && st.owner[0]) {
      sprintf (buf, "   Processing %s[%d].", st.owner, pos);
      log (buf);
      days_passed = ((time (0) - st.last_update) / SECS_PER_REAL_DAY);
      secs_lost = ((time (0) - st.last_update) % SECS_PER_REAL_DAY);

      if (days_passed > 0) {
        if ((st.total_cost * days_passed) > st.gold_left) {

          if ((player_i = find_name (st.owner)) < 0) {
            perror ("   Character not in list. (update_obj_file)");
            exit (1);
          }

          fseek (char_file, (long) (player_table[player_i].nr *
              sizeof (struct char_file_u)), 0);

          fread (&ch_st, sizeof (struct char_file_u), 1, char_file);

          sprintf (buf, "   Dumping %s from object file.", ch_st.name);
          log (buf);

          ch_st.points.gold = 0;
          ch_st.load_room = NOWHERE;
          fseek (char_file, (long) (player_table[player_i].nr *
              sizeof (struct char_file_u)), 0);
          fwrite (&ch_st, sizeof (struct char_file_u), 1, char_file);

          strcpy (st.owner, OBJ_FILE_FREE);
          update_file (fl, pos - 1, &st);

        } else {

          sprintf (buf, "   Updating %s", st.owner);
          log (buf);
          st.gold_left -= (st.total_cost * days_passed);
          st.last_update = time (0) - secs_lost;
          update_file (fl, pos - 1, &st);
        }
      }
    }
  }

  fclose (fl);
  fclose (char_file);
}


/* ************************************************************************
* Routine Receptionist                                                    *
************************************************************************* */



int receptionist (struct char_data *ch, int cmd, char *arg)
{
  char buf[240];
  struct obj_cost cost;
  struct char_data *recep = 0;
  struct char_data *temp_char;
  sh_int save_room;
  sh_int action_tabel[9] = { 23, 24, 36, 105, 106, 109, 111, 142, 147 };

  void do_action (struct char_data *ch, char *argument, int cmd);
  int number (int from, int to);

  if (!ch->desc)
    return (FALSE);             /* You've forgot FALSE - NPC couldn't leave */

  for (temp_char = world[ch->in_room].people; (temp_char) && (!recep);
    temp_char = temp_char->next_in_room)
    if (IS_MOB (temp_char))
      if (mob_index[temp_char->nr].func == receptionist)
        recep = temp_char;

  if (!recep) {
    log ("Ingen receptionist.\n\r");
    WIN32CLEANUP
    exit (1);
  }

  if (IS_NPC (ch))
    return (FALSE);

  if ((cmd != 92) && (cmd != 93)) {
    if (!number (0, 30))
      do_action (recep, "", action_tabel[number (0, 8)]);
    return (FALSE);
  }

  if (!AWAKE (recep)) {
    act ("$e isn't able to talk to you...", FALSE, recep, 0, ch, TO_VICT);
    return (TRUE);
  }

  if (!CAN_SEE (recep, ch)) {
    act ("$n says, 'I don't deal with people I can't see!'", FALSE, recep, 0,
      0, TO_ROOM);
    return (TRUE);
  }

  if (cmd == 92) {              /* Rent  */
    if (recep_offer (ch, recep, &cost)) {

      act
        ("$n stores your stuff in the safe, and helps you into your chamber.",
        FALSE, recep, 0, ch, TO_VICT);
      act ("$n helps $N into $S private chamber.", FALSE, recep, 0, ch,
        TO_NOTVICT);

      save_obj (ch, &cost);
      save_room = ch->in_room;
      extract_char (ch);
      ch->in_room = world[save_room].number;
      save_char (ch, ch->in_room);
    }

  } else {                      /* Offer */
    recep_offer (ch, recep, &cost);
    act ("$N gives $n an offer.", FALSE, ch, 0, recep, TO_ROOM);
  }

  return (TRUE);
}