/***************************************************************************
 *  file: save.c, Database module.                        Part of DIKUMUD  *
 *  Usage: Saving and loading of characters                                *
 *  Copyright (C) 1990, 1991 - see 'license.doc' for complete information. *
 *                                                                         *
 *  Copyright (C) 1992, 1993 Michael Chastain, Michael Quan, Mitchell Tse  *
 *  Rewritten by MERC Industries, based on crash.c by prometheus           *
 *  (Taquin Ho) and abaddon (Jeff Stile).                                  *
 *  You can use our stuff in any way you like whatsoever so long as this   *
 *  copyright notice remains intact.  If you like it please drop a line    *
 *  to mec@garnet.berkeley.edu.                                            *
 *                                                                         *
 *  This is free software and you are benefitting.  We hope that you       *
 *  share your changes too.  What goes around, comes around.               *
 ***************************************************************************/

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

#include "structs.h"
#include "mob.h"
#include "obj.h"
#include "utils.h"
#include "db.h"
#include "handler.h"
#include "spells.h"

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

void obj_store_to_char(struct char_data *ch, struct obj_file_elem *object);
bool put_obj_in_store(struct obj_data *obj, struct char_data *ch,
    FILE *fpsave);
void restore_weight(struct obj_data *obj);
void store_to_char(struct char_file_u *st, struct char_data *ch);
void char_to_store(struct char_data *ch, struct char_file_u *st);



/*
 * Save a character and inventory.
 * Would be cool to save NPC's too for quest purposes.
 */
void save_char_obj( struct char_data *ch )
{
    struct  char_file_u uchar;
    FILE *  fpsave  = NULL;
    char    strsave[MAX_INPUT_LENGTH];
    int     iWear;

    if ( IS_NPC(ch) || GET_LEVEL(ch) < 2 )
	goto LSuccess;

    sprintf( strsave, "%s/%s", SAVE_DIR, ch->player.name );
    if ( ( fpsave = fopen( strsave, "wb" ) ) == NULL )
	goto LError;

    char_to_store( ch, &uchar );
    if ( ch->in_room < 2 )
	uchar.load_room = world[ch->specials.was_in_room].number;
    else
	uchar.load_room = world[ch->in_room].number;

    if ( fwrite( &uchar, sizeof(uchar), 1, fpsave ) == 0 )
	goto LError;

    if ( !obj_to_store( ch->carrying, ch, fpsave ) )
	goto LError;

    restore_weight( ch->carrying );
    for ( iWear = 0; iWear < MAX_WEAR; iWear++ )
    {
	if ( ch->equipment[iWear] )
	{
	    if ( !obj_to_store( ch->equipment[iWear], ch, fpsave ) )
		goto LError;
	    restore_weight( ch->equipment[iWear] );
	}
    }
    goto LSuccess;

 LError:
    sprintf( log_buf, "Save_char_obj: %s", strsave );
    log( log_buf );

 LSuccess:
    if ( fpsave != NULL )
	fclose( fpsave );
    return;
}



/*
 * Load a char and inventory into a new ch structure.
 */
bool load_char_obj( struct descriptor_data *d, char *name )
{
    FILE *  fpsave  = NULL;
    char    strsave[MAX_INPUT_LENGTH];
    struct  char_file_u uchar;
    struct  char_data *ch;

    CREATE( ch, struct char_data, 1 );
    d->character    = ch;
    clear_char( ch );
    ch->desc        = d;

    sprintf( strsave, "%s/%s", SAVE_DIR, name );
    if ( ( fpsave = fopen( strsave, "rb" ) ) == NULL )
	return FALSE;

    if ( fread( &uchar, sizeof(uchar), 1, fpsave ) == 0 )
	goto LError;
    reset_char( ch );
    GET_NAME(ch) = str_dup(name);
    store_to_char( &uchar, ch );
    if ( world[ch->in_room].zone != world[real_room(3001)].zone )
	gain_exp(ch, 0 - MIN(GET_EXP(ch)/2, 10*GET_LEVEL(ch)*GET_LEVEL(ch) ));

    while ( !feof( fpsave ) )
    {
	struct  obj_file_elem   object;

	fread( &object, sizeof(object), 1, fpsave );
	if ( ferror( fpsave ) )
	    goto LError;
	if ( feof( fpsave ) )
	    break;

	obj_store_to_char( ch, &object );
    }
    goto LSuccess;

 LError:
    sprintf( log_buf, "Load_char_obj: %s", strsave );
    log( log_buf );
    if ( fpsave != NULL )
	fclose( fpsave );
    return FALSE;

 LSuccess:
    if ( fpsave != NULL )
	fclose( fpsave );
    return TRUE;
}



void obj_store_to_char(struct char_data *ch, struct obj_file_elem *object)
{
    struct obj_data *obj;
    int j;
    int nr;

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

    if ( ( nr = real_object(object->item_number) ) > -1 ) 
    {
	obj = read_object( nr, 0 );
	obj->obj_flags.value[0] = object->value[0];
	obj->obj_flags.value[1] = object->value[1];
	obj->obj_flags.value[2] = object->value[2];
	obj->obj_flags.value[3] = object->value[3];

	obj->obj_flags.extra_flags = object->extra_flags;
	obj->obj_flags.weight      = object->weight;
	obj->obj_flags.timer       = object->timer;
	obj->obj_flags.eq_level    = object->eq_level;
	obj->obj_flags.bitvector   = object->bitvector;

	for(j=0; j<MAX_OBJ_AFFECT; j++)
	    obj->affected[j] = object->affected[j];

	obj_to_char(obj, ch);
    }
}



bool obj_to_store( struct obj_data *obj, struct char_data *ch, FILE *fpsave )
{
    struct obj_data *tmp;

    if ( obj == NULL )
	return TRUE;

    /* Write depth first (so weights come out right) */
    if ( !obj_to_store( obj->contains, ch, fpsave ) )
	return FALSE;
    if ( !obj_to_store( obj->next_content, ch, fpsave) )
	return FALSE;
    if ( !put_obj_in_store( obj, ch, fpsave ) )
	return FALSE;

    /* Adjust container weights of up-linked items */
    for ( tmp = obj->in_obj; tmp; tmp = tmp->in_obj )
	GET_OBJ_WEIGHT(tmp) -= GET_OBJ_WEIGHT(obj);

    return TRUE;
}





/*
 * Write one object to the file.
 */
bool put_obj_in_store(struct obj_data *obj, struct char_data *ch, FILE *fpsave)
{
    int iAffect;
    struct  obj_file_elem   object;

    if ( GET_ITEM_TYPE(obj) == ITEM_KEY )
	return TRUE;

    object.version	= 0;
    object.item_number  = obj_index[obj->item_number].virtual;
    object.value[0]     = obj->obj_flags.value[0];
    object.value[1]     = obj->obj_flags.value[1];
    object.value[2]     = obj->obj_flags.value[2];
    object.value[3]     = obj->obj_flags.value[3];
    object.extra_flags  = obj->obj_flags.extra_flags;
    object.weight       = obj->obj_flags.weight;
    object.timer        = obj->obj_flags.timer;
    object.eq_level	= obj->obj_flags.eq_level;
    object.bitvector    = obj->obj_flags.bitvector;
    for ( iAffect = 0; iAffect < MAX_OBJ_AFFECT; iAffect++ )
	object.affected[iAffect]    = obj->affected[iAffect];
    
    if ( fwrite( &object, sizeof(object), 1, fpsave ) == 0 )
	return FALSE;

    return TRUE;
}



/*
 * Restore container weights after a save.
 */
void restore_weight(struct obj_data *obj)
{
    struct obj_data *tmp;

    if ( obj == NULL )
	return;

    restore_weight( obj->contains );
    restore_weight( obj->next_content );
    for ( tmp = obj->in_obj; tmp; tmp = tmp->in_obj )
	GET_OBJ_WEIGHT( tmp ) += GET_OBJ_WEIGHT( obj );
}



void store_to_char(struct char_file_u *st, struct char_data *ch)
{
    int i;

    strncpy( ch->pwd, st->pwd, 11 );

    GET_SEX(ch) = st->sex;
    GET_CLASS(ch) = st->class;
    GET_LEVEL(ch) = st->level;

    ch->player.short_descr = 0;
    ch->player.long_descr = 0;

    if (*st->title)
    {
	CREATE(ch->player.title, char, strlen(st->title) + 1);
	strcpy(ch->player.title, st->title);
    }
    else
	GET_TITLE(ch) = 0;

    if (*st->description)
    {
	CREATE(ch->player.description, char, 
	    strlen(st->description) + 1);
	strcpy(ch->player.description, st->description);
    }
    else
	ch->player.description = 0;

    ch->player.hometown = st->hometown;

    ch->player.time.birth = st->birth;
    ch->player.time.played = st->played;
    ch->player.time.logon  = time(0);

    for (i = 0; i < MAX_TONGUE; i++)
	ch->player.talks[i] = st->talks[i];

    ch->player.weight = st->weight;
    ch->player.height = st->height;

    ch->abilities = st->abilities;
    ch->tmpabilities = st->abilities;
    ch->points = st->points;
	if (ch->points.max_mana < 100) {
	   ch->points.max_mana = 100;
     } /* if */

    for (i = 0; i < MAX_SKILLS; i++)
	ch->skills[i] = st->skills[i];

    ch->specials.practices    = st->practices;
    ch->specials.alignment    = st->alignment;

    ch->specials.act          = st->act;
    ch->specials.carry_weight = 0;
    ch->specials.carry_items  = 0;
    ch->points.armor          = 100;
    ch->points.hitroll        = 0;
    ch->points.damroll        = 0;

    /* Not used as far as I can see (Michael) */
    for(i = 0; i <= 4; i++)
      ch->specials.apply_saving_throw[i] = st->apply_saving_throw[i];

    for(i = 0; i <= 2; i++)
      GET_COND(ch, i) = st->conditions[i];

    /* Add all spell effects */
    for(i=0; i < MAX_AFFECT; i++) {
	if (st->affected[i].type)
	    affect_to_char(ch, &st->affected[i]);
    }
    ch->in_room = real_room(st->load_room);
    affect_total(ch);
}



/* copy vital data from a players char-structure to the file structure */
void char_to_store(struct char_data *ch, struct char_file_u *st)
{
    int i;
    struct affected_type *af;
    struct obj_data *char_eq[MAX_WEAR];

    strncpy( st->pwd, ch->pwd, 11 );

    /* Unaffect everything a character can be affected by */

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

    for(af = ch->affected, i = 0; i<MAX_AFFECT; i++) {
	if (af &&
		    (!(af->type == SPELL_INVISIBLE &&
		       af->bitvector == AFF_INVISIBLE &&
		       ch->specials.wizInvis))) {
	    st->affected[i] = *af;
	    st->affected[i].next = 0;
	    /* subtract effect of the spell or the effect will be doubled */
	    affect_modify( ch, st->affected[i].location,
			       st->affected[i].modifier,
			       st->affected[i].bitvector, FALSE);                         
	    af = af->next;
	} else {
	    st->affected[i].type = 0;  /* Zero signifies not used */
	    st->affected[i].duration = 0;
	    st->affected[i].modifier = 0;
	    st->affected[i].location = 0;
	    st->affected[i].bitvector = 0;
	    st->affected[i].next = 0;
	}
    }

    if ((i >= MAX_AFFECT) && af && af->next)
	log( "Char_to_store: too many affects." );

    ch->tmpabilities = ch->abilities;

    st->version    = 0;
    st->birth      = ch->player.time.birth;
    st->played     = ch->player.time.played;
    st->played    += (long) (time(0) - ch->player.time.logon);
    st->last_logon = time(0);

    ch->player.time.played = st->played;
    ch->player.time.logon = time(0);

    st->hometown = ch->player.hometown;
    st->weight   = GET_WEIGHT(ch);
    st->height   = GET_HEIGHT(ch);
    st->sex      = GET_SEX(ch);
    st->class    = GET_CLASS(ch);
    st->level    = GET_LEVEL(ch);
    st->abilities = ch->abilities;
    st->points    = ch->points;
    st->alignment = ch->specials.alignment;
    st->practices = ch->specials.practices;
    st->act       = ch->specials.act;

    st->points.armor   = 100;
    st->points.hitroll =  0;
    st->points.damroll =  0;

    if (GET_TITLE(ch))
	strcpy(st->title, GET_TITLE(ch));
    else
	*st->title = '\0';

    if (ch->player.description)
	strcpy(st->description, ch->player.description);
    else
	*st->description = '\0';


    for (i = 0; i < MAX_TONGUE; i++)
	st->talks[i] = ch->player.talks[i];

    for (i = 0; i < MAX_SKILLS; i++)
	st->skills[i] = ch->skills[i];

    strcpy(st->name, GET_NAME(ch) );

    for(i = 0; i <= 4; i++)
      st->apply_saving_throw[i] = ch->specials.apply_saving_throw[i];

    for(i = 0; i <= 2; i++)
      st->conditions[i] = GET_COND(ch, i);

    for(af = ch->affected, i = 0; i<MAX_AFFECT; i++) {
	if (af &&
		    (!(af->type == SPELL_INVISIBLE &&
		       af->bitvector == AFF_INVISIBLE &&
		       ch->specials.wizInvis))) {
	    /* Add effect of the spell or it will be lost */
	    /* When saving without quitting               */
	    affect_modify( ch, st->affected[i].location,
			       st->affected[i].modifier,
			       st->affected[i].bitvector, TRUE);
	    af = af->next;
	}
    }

    for(i=0; i<MAX_WEAR; i++) {
	if (char_eq[i])
	    equip_char(ch, char_eq[i], i);
    }

    affect_total(ch);
}