/*
 * $Id: olc_save.c,v 1.66 1999/04/16 15:52:25 fjoe Exp $
 */

/**************************************************************************
 *  File: olc_save.c                                                       *
 *                                                                         *
 *  Much time and thought has gone into this software and you are          *
 *  benefitting.  We hope that you share your changes too.  What goes      *
 *  around, comes around.                                                  *
 *                                                                         *
 *  This code was freely distributed with the The Isles 1.1 source code,   *
 *  and has been used here for OLC - OLC would not be what it is without   *
 *  all the previous coders who released their source code.                *
 *                                                                         *
 ***************************************************************************/

/*
 * olc_save.c
 * This takes care of saving all the .are information.
 */

#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include "merc.h"
#include "obj_prog.h"
#include "olc.h"
#include "db/db.h"
#include "db/lang.h"
#include "db/socials.h"

#define DIF(a,b) (~((~a)|(b)))

/*
 *  Verbose writes reset data in plain english into the comments
 *  section of the resets.  It makes areas considerably larger but
 *  may aid in debugging.
 */
#define VERBOSE

static void save_print(CHAR_DATA *ch, const char *format, ...);

/*****************************************************************************
 Name:		save_area_list
 Purpose:	Saves the listing of files to be loaded at startup.
 Called by:	do_asave(olc_save.c).
 ****************************************************************************/
void save_area_list()
{
	FILE *fp;
	AREA_DATA *pArea;

	if ((fp = dfopen(AREA_PATH, AREA_LIST, "w")) == NULL)
		return;

	for (pArea = area_first; pArea; pArea = pArea->next)
		fprintf(fp, "%s\n", pArea->file_name);

	fprintf(fp, "$\n");
	fclose(fp);
}

void save_mobprogs(FILE *fp, AREA_DATA *pArea)
{
	MPCODE *mpcode;
        int i;
	bool found = FALSE;

	for (i = pArea->min_vnum; i <= pArea->max_vnum; i++) {
        	if ((mpcode = mpcode_lookup(i)) != NULL) {
			if (!found) {
        			fprintf(fp, "#MOBPROGS\n");
				found = TRUE;
			}
			fprintf(fp, "#%d\n", i);
			fwrite_string(fp, NULL, mpcode->code);
		}
        }

	if (found)
        	fprintf(fp,"#0\n\n");
}

/*****************************************************************************
 Name:		save_mobile
 Purpose:	Save one mobile to file, new format -- Hugin
 Called by:	save_mobiles (below).
 ****************************************************************************/
void save_mobile(FILE *fp, MOB_INDEX_DATA *pMobIndex)
{
    race_t *r = race_lookup(pMobIndex->race);
    MPTRIG *mptrig;
    flag64_t temp;

    if (r == NULL) {
	log_printf("save_mobile: %d: unknown race", pMobIndex->race);
	return;
    }

    fprintf(fp, "#%d\n",	pMobIndex->vnum);
    fwrite_string(fp, NULL,	pMobIndex->name);
    mlstr_fwrite(fp, NULL,	pMobIndex->short_descr);
    mlstr_fwrite(fp, NULL,	pMobIndex->long_descr);
    mlstr_fwrite(fp, NULL,	pMobIndex->description);
    fwrite_string(fp, NULL,	r->name);
    fprintf(fp, "%s ",		format_flags(pMobIndex->act & ~r->act));
    fprintf(fp, "%s ",		format_flags(pMobIndex->affected_by & ~r->aff));
    fprintf(fp, "%d %d\n",	pMobIndex->alignment , pMobIndex->group);
    fprintf(fp, "%d ",		pMobIndex->level);
    fprintf(fp, "%d ",		pMobIndex->hitroll);
    fprintf(fp, "%dd%d+%d ",	pMobIndex->hit[DICE_NUMBER], 
				pMobIndex->hit[DICE_TYPE], 
				pMobIndex->hit[DICE_BONUS]);
    fprintf(fp, "%dd%d+%d ",	pMobIndex->mana[DICE_NUMBER], 
				pMobIndex->mana[DICE_TYPE], 
				pMobIndex->mana[DICE_BONUS]);
    fprintf(fp, "%dd%d+%d ",	pMobIndex->damage[DICE_NUMBER], 
				pMobIndex->damage[DICE_TYPE], 
				pMobIndex->damage[DICE_BONUS]);
    fprintf(fp, "%s\n",		attack_table[pMobIndex->dam_type].name);
    fprintf(fp, "%d %d %d %d\n",
				pMobIndex->ac[AC_PIERCE] / 10, 
				pMobIndex->ac[AC_BASH]   / 10, 
				pMobIndex->ac[AC_SLASH]  / 10, 
				pMobIndex->ac[AC_EXOTIC] / 10);
    fprintf(fp, "%s ",		format_flags(pMobIndex->off_flags & ~r->off));
    fprintf(fp, "%s ",		format_flags(pMobIndex->imm_flags & ~r->imm));
    fprintf(fp, "%s ",		format_flags(pMobIndex->res_flags & ~r->res));
    fprintf(fp, "%s\n",		format_flags(pMobIndex->vuln_flags & ~r->vuln));
    fprintf(fp, "%s %s %s %d\n",
			flag_string(position_table, pMobIndex->start_pos),
			flag_string(position_table, pMobIndex->default_pos),
			flag_string(sex_table, pMobIndex->sex),
			pMobIndex->wealth);
    fprintf(fp, "%s ",		format_flags(pMobIndex->form & ~r->form));
    fprintf(fp, "%s ",		format_flags(pMobIndex->parts & ~r->parts));

    fprintf(fp, "%s ",		flag_string(size_table, pMobIndex->size));
    fprintf(fp, "%s\n",	IS_NULLSTR(pMobIndex->material) ? pMobIndex->material : "unknown");

/* save diffs */
    if ((temp = DIF(r->act, pMobIndex->act)))
     	fprintf(fp, "F act %s\n", format_flags(temp));

    if ((temp = DIF(r->aff, pMobIndex->affected_by)))
     	fprintf(fp, "F aff %s\n", format_flags(temp));

    if ((temp = DIF(r->off, pMobIndex->off_flags)))
     	fprintf(fp, "F off %s\n", format_flags(temp));

    if ((temp = DIF(r->imm, pMobIndex->imm_flags)))
     	fprintf(fp, "F imm %s\n", format_flags(temp));

    if ((temp = DIF(r->res, pMobIndex->res_flags)))
     	fprintf(fp, "F res %s\n", format_flags(temp));

    if ((temp = DIF(r->vuln, pMobIndex->vuln_flags)))
     	fprintf(fp, "F vul %s\n", format_flags(temp));

    if ((temp = DIF(r->form, pMobIndex->form)))
     	fprintf(fp, "F for %s\n", format_flags(temp));

    if ((temp = DIF(r->parts, pMobIndex->parts)))
    	fprintf(fp, "F par %s\n", format_flags(temp));

    for (mptrig = pMobIndex->mptrig_list; mptrig; mptrig = mptrig->next)
    {
        fprintf(fp, "M %s %d %s~\n",
        flag_string(mptrig_types, mptrig->type), mptrig->vnum,
                fix_string(mptrig->phrase));
    }

    if (pMobIndex->clan)
		fwrite_string(fp, "C", clan_name(pMobIndex->clan));
    if (pMobIndex->invis_level)
		fprintf(fp, "I %d\n", pMobIndex->invis_level);
    if (pMobIndex->fvnum)
		fprintf(fp, "V %d\n", pMobIndex->fvnum);
}

/*****************************************************************************
 Name:		save_mobiles
 Purpose:	Save #MOBILES secion of an area file.
 Called by:	save_area(olc_save.c).
 Notes:         Changed for ROM OLC.
 ****************************************************************************/
void save_mobiles(FILE *fp, AREA_DATA *pArea)
{
	int i;
	MOB_INDEX_DATA *pMob;
	bool found = FALSE;

	for (i = pArea->min_vnum; i <= pArea->max_vnum; i++)
		if ((pMob = get_mob_index(i))) {
			if (!found) {
				fprintf(fp, "#MOBILES\n");
				found = TRUE;
			}
			save_mobile(fp, pMob);
		}

	if (found)
		fprintf(fp, "#0\n\n");
}

/*****************************************************************************
 Name:		save_object
 Purpose:	Save one object to file.
                new ROM format saving -- Hugin
 Called by:	save_objects (below).
 ****************************************************************************/
void save_object(FILE *fp, OBJ_INDEX_DATA *pObjIndex)
{
    char letter;
    AFFECT_DATA *pAf;
    ED_DATA *pEd;

    fprintf(fp, "#%d\n",	pObjIndex->vnum);
    fwrite_string(fp, NULL,	pObjIndex->name);
    mlstr_fwrite(fp, NULL,	pObjIndex->short_descr);
    mlstr_fwrite(fp, NULL,	pObjIndex->description);
    fwrite_string(fp, NULL,	pObjIndex->material);
    fprintf(fp, "%s ",		flag_string(item_types, pObjIndex->item_type));
    fprintf(fp, "%s ",		format_flags(pObjIndex->extra_flags &
					     ~(ITEM_ENCHANTED |
					       ITEM_OLDSTYLE)));
    fprintf(fp, "%s\n",		format_flags(pObjIndex->wear_flags));

/*
 *  Using format_flags to write most values gives a strange
 *  looking area file, consider making a case for each
 *  item type later.
 */

    switch (pObjIndex->item_type)
    {
        default:
	    fprintf(fp, "%s %s %s %s %s\n",
			format_flags(pObjIndex->value[0]),
	    		format_flags(pObjIndex->value[1]),
	    		format_flags(pObjIndex->value[2]),
	    		format_flags(pObjIndex->value[3]),
	    		format_flags(pObjIndex->value[4]));
	    break;

	case ITEM_MONEY:
	case ITEM_ARMOR:
	    fprintf(fp, "%d %d %d %d %d\n",
			pObjIndex->value[0],
	    		pObjIndex->value[1],
	    		pObjIndex->value[2],
	    		pObjIndex->value[3],
	    		pObjIndex->value[4]);
		break;

        case ITEM_DRINK_CON:
        case ITEM_FOUNTAIN:
            fprintf(fp, "%d %d '%s' %d %d\n",
                     pObjIndex->value[0],
                     pObjIndex->value[1],
                     liq_table[pObjIndex->value[2]].liq_name,
		     pObjIndex->value[3],
		     pObjIndex->value[4]);
            break;

        case ITEM_CONTAINER:
            fprintf(fp, "%d %s %d %d %d\n",
                     pObjIndex->value[0],
                     format_flags(pObjIndex->value[1]),
                     pObjIndex->value[2],
                     pObjIndex->value[3],
                     pObjIndex->value[4]);
            break;

        case ITEM_WEAPON:
            fprintf(fp, "%s %d %d %s %s\n",
                     flag_string(weapon_class, pObjIndex->value[0]),
                     pObjIndex->value[1],
                     pObjIndex->value[2],
                     attack_table[pObjIndex->value[3]].name,
                     format_flags(pObjIndex->value[4]));
            break;
            
        case ITEM_PILL:
        case ITEM_POTION:
        case ITEM_SCROLL:
	    fprintf(fp, "%d '%s' '%s' '%s' '%s'\n",
/* no negative numbers */
		     pObjIndex->value[0] > 0 ? pObjIndex->value[0] : 0,
		     pObjIndex->value[1] != -1 ?
			skill_name(pObjIndex->value[1]) : str_empty,
		     pObjIndex->value[2] != -1 ?
		     	skill_name(pObjIndex->value[2]) : str_empty,
		     pObjIndex->value[3] != -1 ?
		     	skill_name(pObjIndex->value[3]) : str_empty,
		     pObjIndex->value[4] != -1 ?
		     	skill_name(pObjIndex->value[4]) : str_empty);
	    break;

        case ITEM_STAFF:
        case ITEM_WAND:
	    fprintf(fp, "%d %d %d '%s' %d\n",
	    			pObjIndex->value[0],
	    			pObjIndex->value[1],
	    			pObjIndex->value[2],
	    			pObjIndex->value[3] != -1 ?
	    				skill_name(pObjIndex->value[3]) : str_empty,
	    			pObjIndex->value[4]);
	    break;
	case ITEM_PORTAL:
	    fprintf(fp, "%s %s %s %d %s\n",
			format_flags(pObjIndex->value[0]),
	    		format_flags(pObjIndex->value[1]),
	    		format_flags(pObjIndex->value[2]),
	    		pObjIndex->value[3],
	    		format_flags(pObjIndex->value[4]));
	    break;
	case ITEM_LIGHT:
	case ITEM_TATTOO:
	case ITEM_TREASURE:
	    fprintf(fp, "%s %s %d %s %s\n",
			format_flags(pObjIndex->value[0]),
	    		format_flags(pObjIndex->value[1]),
	    		pObjIndex->value[2],
	    		format_flags(pObjIndex->value[3]),
	    		format_flags(pObjIndex->value[4]));
	    break;
    }

    fprintf(fp, "%d ", pObjIndex->level);
    fprintf(fp, "%d ", pObjIndex->weight);
    fprintf(fp, "%d ", pObjIndex->cost);

         if (pObjIndex->condition >  90) letter = 'P';
    else if (pObjIndex->condition >  75) letter = 'G';
    else if (pObjIndex->condition >  50) letter = 'A';
    else if (pObjIndex->condition >  25) letter = 'W';
    else if (pObjIndex->condition >  10) letter = 'D';
    else if (pObjIndex->condition >   0) letter = 'B';
    else                                   letter = 'R';

    fprintf(fp, "%c\n", letter);

    for (pAf = pObjIndex->affected; pAf; pAf = pAf->next)
    {
	if (pAf->where == TO_OBJECT || pAf->bitvector == 0)
	        fprintf(fp, "A\n%d %d\n",  pAf->location, pAf->modifier);
	else
	{
		fprintf(fp, "F\n");

		switch(pAf->where)
		{
			case TO_AFFECTS:
				fprintf(fp, "A ");
				break;
			case TO_IMMUNE:
				fprintf(fp, "I ");
				break;
			case TO_RESIST:
				fprintf(fp, "R ");
				break;
			case TO_VULN:
				fprintf(fp, "V ");
				break;
			default:
				log_printf("olc_save: vnum %d: "
					   "invalid affect->where: %d",
					   pObjIndex->vnum, pAf->where);
				break;
		}
		
		fprintf(fp, "%d %d %s\n", pAf->location, pAf->modifier,
				format_flags(pAf->bitvector));
	}
    }

    for (pEd = pObjIndex->ed; pEd; pEd = pEd->next)
		ed_fwrite(fp, pEd);

    if (pObjIndex->clan)
		fwrite_string(fp, "C", clan_name(pObjIndex->clan));
    fprintf(fp, "G %s\n", flag_string(gender_table, pObjIndex->gender));
}

/*****************************************************************************
 Name:		save_objects
 Purpose:	Save #OBJECTS section of an area file.
 Called by:	save_area(olc_save.c).
 Notes:         Changed for ROM OLC.
 ****************************************************************************/
void save_objects(FILE *fp, AREA_DATA *pArea)
{
	int i;
	OBJ_INDEX_DATA *pObj;
	bool found = FALSE;

	for (i = pArea->min_vnum; i <= pArea->max_vnum; i++)
		if ((pObj = get_obj_index(i))) {
			if (!found) {
    				fprintf(fp, "#OBJECTS\n");
				found = TRUE;
			}
			save_object(fp, pObj);
    		}

	if (found)
		fprintf(fp, "#0\n\n");
}

static int exitcmp(const void *p1, const void *p2)
{
	return (*(EXIT_DATA**)p1)->orig_door - (*(EXIT_DATA**)p2)->orig_door;
}

void save_room(FILE *fp, ROOM_INDEX_DATA *pRoomIndex)
{
	int door;
	ED_DATA *pEd;
	EXIT_DATA *pExit;
	EXIT_DATA *exit[MAX_DIR];
	int max_door;

        fprintf(fp, "#%d\n",	pRoomIndex->vnum);
	mlstr_fwrite(fp, NULL,	pRoomIndex->name);
	mlstr_fwrite(fp, NULL,	pRoomIndex->description);
	fprintf(fp, "0 ");
        fprintf(fp, "%s ",	format_flags(pRoomIndex->room_flags));
        fprintf(fp, "%s\n",	flag_string(sector_types,
					    pRoomIndex->sector_type));

        for (pEd = pRoomIndex->ed; pEd; pEd = pEd->next)
		ed_fwrite(fp, pEd);

	/* sort exits (to minimize diffs) */
	for (max_door = 0, door = 0; door < MAX_DIR; door++)
		if ((pExit = pRoomIndex->exit[door]))
			exit[max_door++] = pExit;
	qsort(exit, max_door, sizeof(*exit), exitcmp);

	for (door = 0; door < max_door; door++) {
		pExit = exit[door];
		if (pExit->to_room.r) {
 
	 		/* HACK : TO PREVENT EX_LOCKED etc without EX_ISDOOR
 			   to stop booting the mud */
 			if (IS_SET(pExit->rs_flags, EX_CLOSED)
 			||  IS_SET(pExit->rs_flags, EX_LOCKED)
 			||  IS_SET(pExit->rs_flags, EX_PICKPROOF)
 			||  IS_SET(pExit->rs_flags, EX_NOPASS)
 			||  IS_SET(pExit->rs_flags, EX_EASY)
 			||  IS_SET(pExit->rs_flags, EX_HARD)
 			||  IS_SET(pExit->rs_flags, EX_INFURIATING)
 			||  IS_SET(pExit->rs_flags, EX_NOCLOSE)
 			||  IS_SET(pExit->rs_flags, EX_NOLOCK) )
 				SET_BIT(pExit->rs_flags, EX_ISDOOR);
 			else
 				REMOVE_BIT(pExit->rs_flags, EX_ISDOOR);
 
			fprintf(fp, "D%d\n",      pExit->orig_door);
			mlstr_fwrite(fp, NULL,	  pExit->description);
			fprintf(fp, "%s~\n",      pExit->keyword);
			fprintf(fp, "%s %d %d\n",
				format_flags(pExit->rs_flags | EX_BITVAL),
				pExit->key,
				pExit->to_room.r->vnum);
		}
	}

	if (pRoomIndex->mana_rate != 100 || pRoomIndex->heal_rate != 100)
		fprintf (fp, "M %d H %d\n", pRoomIndex->mana_rate,
					    pRoomIndex->heal_rate);
		 			     
	if (!IS_NULLSTR(pRoomIndex->owner))
		fprintf (fp, "O %s~\n" , pRoomIndex->owner);

	if (pRoomIndex->clan)
		fwrite_string(fp, "C", clan_name(pRoomIndex->clan));

	fprintf(fp, "S\n");
}

/*****************************************************************************
 Name:		save_rooms
 Purpose:	Save #ROOMS section of an area file.
 Called by:	save_area(olc_save.c).
 ****************************************************************************/
void save_rooms(FILE *fp, AREA_DATA *pArea)
{
	ROOM_INDEX_DATA *pRoomIndex;
	bool found = FALSE;
	int i;

	for (i = pArea->min_vnum; i <= pArea->max_vnum; i++)
		if ((pRoomIndex = get_room_index(i))) {
			if (!found) {
				fprintf(fp, "#ROOMS\n");
				found = TRUE;
			}
			save_room(fp, pRoomIndex);
		}

	if (found)
		fprintf(fp, "#0\n\n");
}

void save_special(FILE *fp, MOB_INDEX_DATA *pMobIndex)
{
#if defined(VERBOSE)
	fprintf(fp, "M %d %s\t* %s\n",
		pMobIndex->vnum,
		spec_name(pMobIndex->spec_fun),
		mlstr_mval(pMobIndex->short_descr));
#else
	fprintf(fp, "M %d %s\n",
		pMobIndex->vnum, spec_name(pMobIndex->spec_fun));
#endif
}

/*****************************************************************************
 Name:		save_specials
 Purpose:	Save #SPECIALS section of area file.
 Called by:	save_area(olc_save.c).
 ****************************************************************************/
void save_specials(FILE *fp, AREA_DATA *pArea)
{
	int i;
	MOB_INDEX_DATA *pMobIndex;
	bool found = FALSE;
    
	for (i = pArea->min_vnum; i <= pArea->max_vnum; i++)
		if ((pMobIndex = get_mob_index(i))
		&&  pMobIndex->spec_fun) {
			if (!found) {
				fprintf(fp, "#SPECIALS\n");
				found = TRUE;
			}
			save_special(fp, pMobIndex);
		}

	if (found)
		fprintf(fp, "S\n\n");
}

/*
 * This function is obsolete.  It it not needed but has been left here
 * for historical reasons.  It is used currently for the same reason.
 *
 * I don't think it's obsolete in ROM -- Hugin.
 */
void save_door_reset(FILE *fp, ROOM_INDEX_DATA *pRoomIndex, EXIT_DATA *pExit)
{
#if defined(VERBOSE)
	fprintf(fp,
		"D 0 %d %d %d\t* %s: door to the %s: %s\n", 
		pRoomIndex->vnum,
		pExit->orig_door,
		IS_SET(pExit->rs_flags, EX_LOCKED) ? 2 : 1,
		mlstr_mval(pRoomIndex->name),
		dir_name[pExit->orig_door],
		IS_SET(pExit->rs_flags, EX_LOCKED) ?
			"closed and locked" : "closed");
#else
	fprintf(fp, "D 0 %d %d %d\n", 
		pRoomIndex->vnum,
		pExit->orig_door,
		IS_SET(pExit->rs_flags, EX_LOCKED) ? 2 : 1);
#endif
}

void save_reset(FILE *fp, AREA_DATA *pArea,
		ROOM_INDEX_DATA *pRoomIndex, RESET_DATA *pReset)
{
	switch (pReset->command) {
	default:
		bug("Save_resets: bad command %c.", pReset->command);
		break;

#if defined(VERBOSE)
	case 'M':
		fprintf(fp, "M 0 %d %d %d %d\t* %s (%s)\n", 
			pReset->arg1,
			pReset->arg2,
			pReset->arg3,
			pReset->arg4,
			mlstr_mval(get_mob_index(pReset->arg1)->short_descr),
			mlstr_mval(get_room_index(pReset->arg3)->name));
		break;

	case 'O':
		fprintf(fp, "O 0 %d 0 %d\t* %s (%s)\n", 
			pReset->arg1,
			pReset->arg3,
			mlstr_mval(get_obj_index(pReset->arg1)->short_descr),
			mlstr_mval(get_room_index(pReset->arg3)->name));
		break;

	case 'P':
		fprintf(fp, "P 0 %d %d %d %d\t* %s: %s\n", 
			pReset->arg1,
			pReset->arg2,
			pReset->arg3,
			pReset->arg4,
			mlstr_mval(get_obj_index(pReset->arg3)->short_descr),
			mlstr_mval(get_obj_index(pReset->arg1)->short_descr));
		break;

	case 'G':
		fprintf(fp, "G 0 %d 0\t\t*\t%s\n",
			pReset->arg1,
			mlstr_mval(get_obj_index(pReset->arg1)->short_descr));
		break;

	case 'E':
		fprintf(fp, "E 0 %d 0 %d\t\t*\t%s: %s\n",
			pReset->arg1,
			pReset->arg3,
			mlstr_mval(get_obj_index(pReset->arg1)->short_descr),
			flag_string(wear_loc_strings, pReset->arg3));
		break;

	case 'D':
		break;

	case 'R':
		pRoomIndex = get_room_index(pReset->arg1);
		fprintf(fp, "R 0 %d %d\t* %s: randomize\n", 
			pReset->arg1,
			pReset->arg2,
			mlstr_mval(pRoomIndex->name));
		break;
#else
	case 'M':
		fprintf(fp, "M 0 %d %d %d %d\n", 
			pReset->arg1,
			pReset->arg2,
			pReset->arg3,
			pReset->arg4);
		break;

	case 'O':
		fprintf(fp, "O 0 %d 0 %d\n", 
			pReset->arg1,
			pReset->arg3);
		break;

	case 'P':
		fprintf(fp, "P 0 %d %d %d %d\n", 
			pReset->arg1,
			pReset->arg2,
			pReset->arg3,
			pReset->arg4);
		break;

	case 'G':
		fprintf(fp, "G 0 %d 0\n", pReset->arg1);
		break;

	case 'E':
		fprintf(fp, "E 0 %d 0 %d\n",
			pReset->arg1,
			pReset->arg3);
		break;

	case 'D':
		break;

	case 'R':
		fprintf(fp, "R 0 %d %d\n", 
			pReset->arg1,
			pReset->arg2);
		break;
#endif
	}
}

/*****************************************************************************
 Name:		save_resets
 Purpose:	Saves the #RESETS section of an area file.
 Called by:	save_area(olc_save.c)
 ****************************************************************************/
void save_resets(FILE *fp, AREA_DATA *pArea)
{
	ROOM_INDEX_DATA *pRoomIndex;
	RESET_DATA *pReset;
	EXIT_DATA *pExit;
	int door;
	bool found = FALSE;
	int i;

	for (i = pArea->min_vnum; i <= pArea->max_vnum; i++)
		if ((pRoomIndex = get_room_index(i)))
			for (door = 0; door < MAX_DIR; door++)
				if ((pExit = pRoomIndex->exit[door])
				&&  pExit->to_room.r 
				&&  (IS_SET(pExit->rs_flags, EX_CLOSED) ||
				     IS_SET(pExit->rs_flags, EX_LOCKED))) {
					if (!found) {
						fprintf(fp, "#RESETS\n");
						found = TRUE;
					}
    					save_door_reset(fp, pRoomIndex, pExit);
				}

	for (i = pArea->min_vnum; i <= pArea->max_vnum; i++)
		if ((pRoomIndex = get_room_index(i)))
    			for (pReset = pRoomIndex->reset_first; pReset; pReset = pReset->next) {
				if (!found) {
					fprintf(fp, "#RESETS\n");
					found = TRUE;
				}
				save_reset(fp, pArea, pRoomIndex, pReset);
			}

	if (found)
		fprintf(fp, "S\n\n");
}

void save_shop(FILE *fp, MOB_INDEX_DATA *pMobIndex)
{
	SHOP_DATA *pShopIndex;
	int iTrade;

	pShopIndex = pMobIndex->pShop;

	fprintf(fp, "%d ", pShopIndex->keeper);
	for (iTrade = 0; iTrade < MAX_TRADE; iTrade++) {
		if (pShopIndex->buy_type[iTrade] != 0)
			fprintf(fp, "%d ", pShopIndex->buy_type[iTrade]);
		else
			fprintf(fp, "0 ");
	}
	fprintf(fp, "%d %d ", pShopIndex->profit_buy, pShopIndex->profit_sell);
	fprintf(fp, "%d %d\n", pShopIndex->open_hour, pShopIndex->close_hour);
}

/*****************************************************************************
 Name:		save_shops
 Purpose:	Saves the #SHOPS section of an area file.
 Called by:	save_area(olc_save.c)
 ****************************************************************************/
void save_shops(FILE *fp, AREA_DATA *pArea)
{
	MOB_INDEX_DATA *pMobIndex;
	int i;
	bool found = FALSE;
    
	for (i = pArea->min_vnum; i <= pArea->max_vnum; i++)
		if ((pMobIndex = get_mob_index(i))
		&&  pMobIndex->pShop) {
			if (!found) {
				fprintf(fp, "#SHOPS\n");
				found = TRUE;
			}
			save_shop(fp, pMobIndex);
		}

	if (found)
		fprintf(fp, "0\n\n");
}

void save_olimits(FILE *fp, AREA_DATA *pArea)
{
	int i;
	OBJ_INDEX_DATA *pObj;
	bool found = FALSE;

	for (i = pArea->min_vnum; i <= pArea->max_vnum; i++)
		if ((pObj = get_obj_index(i)) != NULL
		&&  pObj->limit != -1) {
			if (!found) {
				fprintf(fp, "#OLIMITS\n");
				found = TRUE;
			}
			fprintf(fp, "O %d %d\t* %s\n",
				i, pObj->limit, mlstr_mval(pObj->short_descr));
		}

	if (found)
		fprintf(fp, "S\n\n");
}

void save_omprog(FILE *fp, OBJ_INDEX_DATA *pObjIndex)
{
	int i;

	for (i = 0; i < OPROG_MAX; i++)
		if (pObjIndex->oprogs[i] != NULL)
			fprintf(fp, "O %d %s %s\t* `%s'\n",
				pObjIndex->vnum,
				optype_table[i],
				oprog_name_lookup(pObjIndex->oprogs[i]),
				mlstr_mval(pObjIndex->short_descr));
}

void save_omprogs(FILE *fp, AREA_DATA *pArea)
{
	int i;
	OBJ_INDEX_DATA *pObjIndex;
	bool found = FALSE;

	for (i = pArea->min_vnum; i <= pArea->max_vnum; i++)
		if ((pObjIndex = get_obj_index(i)) != NULL
		&&  pObjIndex->oprogs) {
			if (!found) {
				fprintf(fp, "#OMPROGS\n");
				found = TRUE;
			}
			save_omprog(fp, pObjIndex);
		}

	if (found)
		fprintf(fp, "S\n\n");
}

void save_practicers(FILE *fp, AREA_DATA *pArea)
{
	int i;
	MOB_INDEX_DATA *pMobIndex;
	bool found = FALSE;

	for (i = pArea->min_vnum; i <= pArea->max_vnum; i++)
		if ((pMobIndex = get_mob_index(i)) != NULL
		&&  pMobIndex->practicer != 0) {
			if (!found) {
				fprintf(fp, "#PRACTICERS\n");
				found = TRUE;
			}
    			fprintf(fp, "M %d %s~\t* %s\n",
				pMobIndex->vnum,
				flag_string(skill_groups, pMobIndex->practicer),
				mlstr_mval(pMobIndex->short_descr));
		}

	if (found)
		fprintf(fp, "S\n\n");
}

void save_helps(FILE *fp, AREA_DATA *pArea)
{
	HELP_DATA *pHelp = pArea->help_first;

	if (pHelp == NULL)
		return;
		
	fprintf(fp, "#HELPS\n");

	for (; pHelp; pHelp = pHelp->next_in_area) {
		fprintf(fp, "%d %s~\n",
			pHelp->level, fix_string(pHelp->keyword));
		mlstr_fwrite(fp, NULL, pHelp->text);
	}

	fprintf(fp, "-1 $~\n\n");
}

/*****************************************************************************
 Name:		save_area
 Purpose:	Save an area, note that this format is new.
 Called by:	do_asave(olc_save.c).
 ****************************************************************************/
void save_area(AREA_DATA *pArea)
{
	FILE *fp;
	int flags;

	if ((fp = dfopen(AREA_PATH, pArea->file_name, "w")) == NULL)
		return;

	fprintf(fp, "#AREADATA\n");
	fprintf(fp, "Name %s~\n",	pArea->name);
	fwrite_string(fp, "Builders", pArea->builders);
	fprintf(fp, "VNUMs %d %d\n",	pArea->min_vnum, pArea->max_vnum);
	fwrite_string(fp, "Credits", pArea->credits);
	fprintf(fp, "Security %d\n",	pArea->security);
	fprintf(fp, "LevelRange %d %d\n",
		pArea->min_level, pArea->max_level);
	if (!mlstr_null(pArea->resetmsg))
		mlstr_fwrite(fp, "ResetMessage", pArea->resetmsg);
	flags = pArea->flags & ~AREA_CHANGED;
	if (flags)
		fwrite_string(fp, "Flags", flag_string(area_flags, flags));
	if (pArea->clan)
		fwrite_string(fp, "Clan", clan_name(pArea->clan));
	fprintf(fp, "End\n\n");

	if (pArea->min_vnum && pArea->max_vnum) {
		save_mobiles(fp, pArea);
		save_objects(fp, pArea);
		save_rooms(fp, pArea);
		save_specials(fp, pArea);
		save_resets(fp, pArea);
		save_shops(fp, pArea);
		save_olimits(fp, pArea);
		save_mobprogs(fp, pArea);
		save_practicers(fp, pArea);
		save_omprogs(fp, pArea);
	}
	save_helps(fp, pArea);

	fprintf(fp, "#$\n");

	fclose(fp);
}

void save_skills()
{
}

void save_clan(CHAR_DATA *ch, clan_t *clan)
{
	int i;
	FILE *fp;

/* save clan data */
	if ((fp = dfopen(CLANS_PATH, clan->file_name, "w")) == NULL) {
		save_print(ch, "%s%c%s: %s",
			   CLANS_PATH, PATH_SEPARATOR, clan->file_name,
			   strerror(errno));
		return;
	}
		
	fprintf(fp, "#CLAN\n");

	fwrite_string(fp, "Name", clan->name);
	if (clan->recall_vnum)
		fprintf(fp, "Recall %d\n", clan->recall_vnum);
	if (clan->obj_vnum)
		fprintf(fp, "Item %d\n", clan->obj_vnum);
	if (clan->mark_vnum)
		fprintf(fp, "Mark %d\n", clan->mark_vnum);
	if (clan->altar_vnum)
		fprintf(fp, "Altar %d\n", clan->altar_vnum);

	REMOVE_BIT(clan->flags, CLAN_CHANGED);
	if (clan->flags)
		fprintf(fp, "Flags %s~\n",
			flag_string(clan_flags, clan->flags));

	for (i = 0; i < clan->skills.nused; i++) {
		clskill_t *cs = VARR_GET(&clan->skills, i);

		if (cs->sn > 0) 
			fprintf(fp, "Skill '%s' %d %d\n",
				skill_name(cs->sn), cs->level, cs->percent);
	}

	fprintf(fp, "End\n\n"
		    "#$\n");
	fclose(fp);

/* save plists */
	if ((fp = dfopen(PLISTS_PATH, clan->file_name, "w")) == NULL) {
		save_print(ch, "%s%c%s: %s", PLISTS_PATH,
			   PATH_SEPARATOR, clan->file_name,
			   strerror(errno));
		return;
	}

	fprintf(fp, "#PLISTS\n");

	fwrite_string(fp, "Leaders", clan->leader_list);
	fwrite_string(fp, "Seconds", clan->second_list);
	fwrite_string(fp, "Members", clan->member_list);

	fprintf(fp, "End\n\n"
		    "#$\n");
	fclose(fp);

	save_print(ch, "    %s (%s)", clan->name, clan->file_name);
}

void save_clans(CHAR_DATA *ch)
{
	int i;
	FILE *fp;
	bool found = FALSE;
	int sec = ch ? (IS_NPC(ch) ? 0 : ch->pcdata->security) : 9;

	if (sec < SECURITY_CLAN) {
		save_print(ch, "Insufficient security to save clans.");
		return;
	}

	if ((fp = dfopen(CLANS_PATH, CLAN_LIST, "w")) == NULL) {
		save_print(ch, "%s%c%s: %s",
			   CLANS_PATH, PATH_SEPARATOR, CLAN_LIST,
			   strerror(errno));
		return;
	}

	save_print(ch, "Saved clans:");

	for (i = 0; i < clans.nused; i++) {
		fprintf(fp, "%s\n", CLAN(i)->file_name);
		if (IS_SET(CLAN(i)->flags, CLAN_CHANGED)) {
			save_clan(ch, CLAN(i));
			found = TRUE;
		}
	}

	fprintf(fp, "$\n");
	fclose(fp);

	if (!found)
		save_print(ch, "    None.");
}

void save_msgdb(CHAR_DATA *ch)
{
	int i;
	FILE *fp;
	int sec = ch ? (IS_NPC(ch) ? 0 : ch->pcdata->security) : 9;

	if (sec < SECURITY_MSGDB) {
		save_print(ch, "Insufficient security to save msgdb.");
		return;
	}

	if ((fp = dfopen(ETC_PATH, MSG_FILE, "w")) == NULL) {
		save_print(ch, "%s%c%s: %s",
			   ETC_PATH, PATH_SEPARATOR, MSG_FILE,
			   strerror(errno));
		return;
	}

	for (i = 0; i < MAX_MSG_HASH; i++) {
		varr *v = msg_hash_table+i;
		int j;

		for (j = 0; j < v->nused; j++) {
			mlstring **mlp = VARR_GET(v, j);
			if (!mlstr_nlang(*mlp))
				continue;
			mlstr_fwrite(fp, NULL, *mlp);
		}
	}

	fprintf(fp, "$~\n");
	fclose(fp);
	save_print(ch, "Msgdb saved.");
}

bool save_lang(CHAR_DATA *ch, lang_t *l)
{
	int i;
	FILE *fp;
	lang_t *sl;
	int flags;

	if ((fp = dfopen(LANG_PATH, l->file_name, "w")) == NULL) {
		save_print(ch, "%s%c%s: %s",
			   LANG_PATH, PATH_SEPARATOR, l->file_name,
			   strerror(errno));
		return FALSE;
	}

	fprintf(fp, "#LANG\n"
		    "Name %s\n", l->name);
	if ((sl = varr_get(&langs, l->slang_of)))
		fprintf(fp, "SlangOf %s\n", sl->name);
	flags = l->flags & ~LANG_CHANGED;
	if (flags)
		fprintf(fp, "Flags %s~\n", flag_string(lang_flags, flags));
	fprintf(fp, "End\n\n");

	for (i = 0; i < MAX_RULECL; i++) {
		rulecl_t *rcl = l->rules + i;

		if (!IS_NULLSTR(rcl->file_impl)
		||  !IS_NULLSTR(rcl->file_expl)) {
			fprintf(fp, "#RULECLASS\n"
				    "Class %s\n",
				flag_string(rulecl_names, i));
			fwrite_string(fp, "Impl", rcl->file_impl);
			fwrite_string(fp, "Expl", rcl->file_expl);
			fprintf(fp, "End\n\n");
		}
	}

	fprintf(fp, "#$\n");
	fclose(fp);
	return TRUE;
}

void save_langs(CHAR_DATA *ch)
{
	int lang;
	bool list = FALSE;
	int sec = ch ? (IS_NPC(ch) ? 0 : ch->pcdata->security) : 9;

	if (sec < SECURITY_MSGDB) {
		save_print(ch, "Insufficient security to save langs.");
		return;
	}

	for (lang = 0; lang < langs.nused; lang++) {
		lang_t *l = VARR_GET(&langs, lang);

		if (IS_SET(l->flags, LANG_CHANGED)
		&&  save_lang(ch, l)) {
			save_print(ch, "Language '%s' saved (%s%c%s).",
				   l->name, LANG_PATH, PATH_SEPARATOR,
				   l->file_name);
			l->flags &= ~LANG_CHANGED;
			list = TRUE;
		}
	}

	if (list) {
		FILE *fp;

		if ((fp = dfopen(LANG_PATH, LANG_LIST, "w")) == NULL) {
			save_print(ch, "%s%c%s: %s",
				   LANG_PATH, PATH_SEPARATOR, LANG_LIST,
				   strerror(errno));
			return;
		}

		for (lang = 0; lang < langs.nused; lang++) {
			lang_t *l = VARR_GET(&langs, lang);
			fprintf(fp, "%s\n", l->file_name);
		}
		fprintf(fp, "$\n");
		fclose(fp);
	}
}

void save_rule(FILE *fp, rule_t *r)
{
	int i;

	fprintf(fp, "#RULE\n"
		    "Name %s~\n", r->name);
	if (r->arg)
		fprintf(fp, "BaseLen %d\n", r->arg);
	for (i = 0; i < r->f->v.nused; i++) {
		char **p = VARR_GET(&r->f->v, i);
		if (IS_NULLSTR(*p))
			continue;
		fprintf(fp, "Form %d %s~\n", i, *p);
	}
	fprintf(fp, "End\n\n");
}

void save_expl(CHAR_DATA *ch, lang_t *l, rulecl_t *rcl)
{
	int i;
	FILE *fp;

	if (!IS_SET(rcl->flags, RULES_EXPL_CHANGED))
		return;

	if ((fp = dfopen(LANG_PATH, rcl->file_expl, "w")) == NULL) {
		save_print(ch, "%s%c%s: %s",
			   LANG_PATH, PATH_SEPARATOR, rcl->file_expl,
			   strerror(errno));
		return;
	}

	for (i = 0; i < MAX_RULE_HASH; i++) {
		int j;

		for (j = 0; j < rcl->expl[i].nused; j++) {
			rule_t *r = VARR_GET(rcl->expl+i, j);
			save_rule(fp, r);
		}
	}

	fprintf(fp, "#$\n");
	fclose(fp);

	save_print(ch, "Explicit rules (%s%c%s) saved "
		       "(lang '%s', rules type '%s').",
		   LANG_PATH, PATH_SEPARATOR, rcl->file_expl,
		   l->name, flag_string(rulecl_names, rcl->rulecl));
	rcl->flags &= ~RULES_EXPL_CHANGED;
}

void save_impl(CHAR_DATA *ch, lang_t *l, rulecl_t *rcl)
{
	int i;
	FILE *fp;

	if (!IS_SET(rcl->flags, RULES_IMPL_CHANGED))
		return;

	if ((fp = dfopen(LANG_PATH, rcl->file_impl, "w")) == NULL) {
		save_print(ch, "%s%c%s: %s",
			   LANG_PATH, PATH_SEPARATOR, rcl->file_impl,
			   strerror(errno));
		return;
	}

	for (i = 0; i < rcl->impl.nused; i++) {
		rule_t *r = VARR_GET(&rcl->impl, i);
		save_rule(fp, r);
	}

	fprintf(fp, "#$\n");
	fclose(fp);

	save_print(ch, "Implicit rules (%s%c%s) saved "
		       "(lang '%s', rules type '%s').",
		   LANG_PATH, PATH_SEPARATOR, rcl->file_impl,
		   l->name, flag_string(rulecl_names, rcl->rulecl));
	rcl->flags &= ~RULES_IMPL_CHANGED;
}

void save_rules(CHAR_DATA *ch)
{
	int lang;
	int sec = ch ? (IS_NPC(ch) ? 0 : ch->pcdata->security) : 9;

	if (sec < SECURITY_MSGDB) {
		save_print(ch, "Insufficient security to save rules.");
		return;
	}

	for (lang = 0; lang < langs.nused; lang++) {
		int i;
		lang_t *l = VARR_GET(&langs, lang);

		for (i = 0; i < MAX_RULECL; i++) {
			save_expl(ch, l, l->rules+i);
			save_impl(ch, l, l->rules+i);
		}
	}
}

void save_social(FILE *fp, social_t *soc)
{
	fprintf(fp, "#SOCIAL\n");
	fprintf(fp, "name %s\n", soc->name);
	fprintf(fp, "min_pos %s\n",
		flag_string(position_table, soc->min_pos));
	fwrite_string(fp, "found_char", soc->found_char);
	fwrite_string(fp, "found_vict", soc->found_vict);
	fwrite_string(fp, "found_notvict", soc->found_notvict);
	fwrite_string(fp, "noarg_char", soc->noarg_char);
	fwrite_string(fp, "noarg_room", soc->noarg_room);
	fwrite_string(fp, "self_char", soc->self_char);
	fwrite_string(fp, "self_room", soc->self_room);
	fwrite_string(fp, "notfound_char", soc->notfound_char);
	fprintf(fp, "end\n\n");
}

void save_socials(CHAR_DATA *ch)
{
	int i;
	FILE *fp;
	int sec = ch ? (IS_NPC(ch) ? 0 : ch->pcdata->security) : 9;

	if (sec < SECURITY_SOCIALS) {
		save_print(ch, "Insufficient security to save socials.");
		return;
	}

	if ((fp = dfopen(ETC_PATH, SOCIALS_CONF, "w")) == NULL) {
		save_print(ch, "%s%c%s: %s",
			   ETC_PATH, PATH_SEPARATOR, SOCIALS_CONF,
			   strerror(errno));
		return;
	}

	for (i = 0; i < socials.nused; i++) {
		social_t *soc = VARR_GET(&socials, i);
		save_social(fp, soc);
	}

	fprintf(fp, "#$\n");
	fclose(fp);
	save_print(ch, "Socials saved.");
}

void save_areas(CHAR_DATA *ch, int flags)
{
	AREA_DATA *pArea;
	bool found = FALSE;
	int sec;

	if (!ch)       /* Do an autosave */
		sec = 9;
	else if (!IS_NPC(ch))
    		sec = ch->pcdata->security;
	else
    		sec = 0;

	if (ch)
		char_puts("Saved zones:\n", ch);
	else
		log("Saved zones:");

	for (pArea = area_first; pArea; pArea = pArea->next) {
		/* Builder must be assigned this area. */
		if (ch && !IS_BUILDER(ch, pArea))
			continue;

		if (flags && !IS_SET(pArea->flags, flags))
			continue;

		found = TRUE;
		save_area(pArea);

		if (ch)
			char_printf(ch, "    %s (%s)\n",
				    pArea->name, pArea->file_name);
		else
			log_printf("    %s (%s)",
				   pArea->name, pArea->file_name);
		REMOVE_BIT(pArea->flags, flags);
	}

	if (!found) {
		if (ch)
			char_puts("    None.\n", ch);
		else
			log("    None.");
	}
	else
		save_area_list();
}

/*****************************************************************************
 Name:		do_asave
 Purpose:	Entry point for saving area data.
 Called by:	interpreter(interp.c)
 ****************************************************************************/
void do_asave(CHAR_DATA *ch, const char *argument)
{
	if (argument[0] == '\0') {
		if (ch)
			do_help(ch, "'OLC ASAVE'");
		return;
	}

	/* Save the world, only authorized areas. */
	/* -------------------------------------- */
	if (!str_cmp("world", argument)) {
		save_areas(ch, 0);
		if (ch)
			char_puts("You saved the world.\n", ch);
		else
			log("Saved the world");
		return;
	}

	/* Save changed areas, only authorized areas. */
	/* ------------------------------------------ */
	if (!str_cmp("changed", argument)) {
		save_areas(ch, AREA_CHANGED);
		if (ch)
			char_puts("You saved changed areas.\n", ch);
		else
			log("Saved changed areas");
		return;
	}

	if (!str_cmp("skills", argument)) {
		save_skills(ch);
		if (ch)
			char_puts("You saved skills table.\n", ch);
		else
			log("Saved skills table");
		return;
	}

	if (!str_cmp("rules", argument)) {
		save_rules(ch);
		return;
	}

	if (!str_cmp("clans", argument)) {
		save_clans(ch);
		return;
	}

	if (!str_cmp("msgdb", argument)) {
		save_msgdb(ch);
		return;
	}

	if (!str_cmp("langs", argument)) {
		save_langs(ch);
		return;
	}

	if (!str_cmp("socials", argument)) {
		save_socials(ch);
		return;
	}

	/* Show correct syntax. */
	/* -------------------- */
	if (ch)
		do_asave(ch, str_empty);
}

static void save_print(CHAR_DATA *ch, const char *format, ...)
{
	char buf[MAX_STRING_LENGTH];
	va_list ap;

	va_start(ap, format);
	vsnprintf(buf, sizeof(buf), format, ap);
	va_end(ap);

	if (ch)
		char_printf(ch, "%s\n", buf);
	else
		log(buf);
	wiznet("$t", ch, buf, WIZ_OLC, 0, 0);
}
