/*
 *  File:       files.cc
 *  Summary:    Functions used to save and load levels/games.
 *  Written by: Linley Henzell and Alexey Guzeev
 *
 *  Change History (most recent first):
 *
 *   <7>   19 June 2000  GDL    Change handle to FILE *
 *   <6>   11/14/99      cdl    Don't let player ghosts follow you up/down
 *   <5>    7/13/99      BWR    Monsters now regenerate hps off level &
 ghosts teleport
 *   <4>    6/13/99      BWR    Added tmp file pairs to save file.
 *   <3>    6/11/99      DML    Replaced temp file deletion code.
 *
 *   <2>    5/12/99      BWR    Multiuser system support,
 *                                        including appending UID to
 *                                        name, and compressed saves
 *                                        in the SAVE_DIR_PATH directory
 *
 *   <1>   --/--/--      LRH    Created
 */

#include "AppHdr.h"
#include "files.h"

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

#ifdef DOS
#include <conio.h>
#include <file.h>
#endif

#ifdef LINUX
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#endif

#ifdef USE_EMX
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#endif

#ifdef OS9
#include <stat.h>
#else
#include <sys/stat.h>
#endif

#ifdef __MINGW32__
#include <io.h>
#endif

#include "externs.h"

#include "cloud.h"
#include "debug.h"
#include "dungeon.h"
#include "itemname.h"
#include "items.h"
#include "message.h"
#include "misc.h"
#include "monstuff.h"
#include "mon-util.h"
#include "mstuff2.h"
#include "player.h"
#include "randart.h"
#include "skills2.h"
#include "stash.h"
#include "stuff.h"
#include "tags.h"
#include "travel.h"
#include "wpn-misc.h"

void save_level(int level_saved, bool was_a_labyrinth, char where_were_you);

// temp file pairs used for file level cleanup
extern FixedArray < bool, MAX_LEVELS, MAX_BRANCHES > tmp_file_pairs;

/*
   Order for looking for conjurations for the 1st & 2nd spell slots,
   when finding spells to be remembered by a player's ghost:
 */
unsigned char search_order_conj[] = {
/* 0 */
    SPELL_LEHUDIBS_CRYSTAL_SPEAR,
    SPELL_BOLT_OF_DRAINING,
    SPELL_AGONY,
    SPELL_DISINTEGRATE,
    SPELL_LIGHTNING_BOLT,
    SPELL_STICKY_FLAME,
    SPELL_ISKENDERUNS_MYSTIC_BLAST,
    SPELL_BOLT_OF_FIRE,
    SPELL_BOLT_OF_COLD,
    SPELL_FIREBALL,
    SPELL_DELAYED_FIREBALL,
/* 10 */
    SPELL_VENOM_BOLT,
    SPELL_BOLT_OF_IRON,
    SPELL_STONE_ARROW,
    SPELL_THROW_FLAME,
    SPELL_THROW_FROST,
    SPELL_PAIN,
    SPELL_STING,
    SPELL_MAGIC_DART,
    SPELL_NO_SPELL,                        // end search
};

/*
   Order for looking for summonings and self-enchants for the 3rd spell slot:
 */
unsigned char search_order_third[] = {
/* 0 */
    SPELL_SYMBOL_OF_TORMENT,
    SPELL_SUMMON_GREATER_DEMON,
    SPELL_SUMMON_WRAITHS,
    SPELL_SUMMON_HORRIBLE_THINGS,
    SPELL_SUMMON_DEMON,
    SPELL_DEMONIC_HORDE,
    SPELL_HASTE,
    SPELL_ANIMATE_DEAD,
    SPELL_INVISIBILITY,
    SPELL_CALL_IMP,
    SPELL_SUMMON_SMALL_MAMMAL,
/* 10 */
    SPELL_CONTROLLED_BLINK,
    SPELL_BLINK,
    SPELL_NO_SPELL,                        // end search
};

/*
   Order for looking for enchants for the 4th + 5th spell slot. If fails, will
   go through conjs.
   Note: Dig must be in misc2 (5th) position to work.
 */
unsigned char search_order_misc[] = {
/* 0 */
    SPELL_AGONY,
    SPELL_BANISHMENT,
    SPELL_PARALYZE,
    SPELL_CONFUSE,
    SPELL_SLOW,
    SPELL_POLYMORPH_OTHER,
    SPELL_TELEPORT_OTHER,
    SPELL_DIG,
    SPELL_NO_SPELL,                        // end search
};

/* Last slot (emergency) can only be teleport self or blink. */

static void redraw_all(void)
{
    you.redraw_hit_points = 1;
    you.redraw_magic_points = 1;
    you.redraw_strength = 1;
    you.redraw_intelligence = 1;
    you.redraw_dexterity = 1;
    you.redraw_armour_class = 1;
    you.redraw_evasion = 1;
    you.redraw_experience = 1;
    you.redraw_gold = 1;

    you.redraw_status_flags = REDRAW_LINE_1_MASK | REDRAW_LINE_2_MASK | REDRAW_LINE_3_MASK;
}

struct ghost_struct ghost;

unsigned char translate_spell(unsigned char spel);
unsigned char search_third_list(unsigned char ignore_spell);
unsigned char search_second_list(unsigned char ignore_spell);
unsigned char search_first_list(unsigned char ignore_spell);

void add_spells( struct ghost_struct &gs );
void generate_random_demon();

static bool determine_version( FILE *restoreFile, 
                               char &majorVersion, char &minorVersion );

static void restore_version( FILE *restoreFile, 
                             char majorVersion, char minorVersion );

static bool determine_level_version( FILE *levelFile, 
                                     char &majorVersion, char &minorVersion );

static void restore_level_version( FILE *levelFile, 
                                   char majorVersion, char minorVersion );

static bool determine_ghost_version( FILE *ghostFile, 
                                     char &majorVersion, char &minorVersion );

static void restore_ghost_version( FILE *ghostFile, 
                                   char majorVersion, char minorVersion );

static void restore_tagged_file( FILE *restoreFile, int fileType, 
                                 char minorVersion );

static void load_ghost();

void make_filename( char *buf, const char *prefix, int level, int where,
                    bool isLabyrinth, bool isGhost )
{
    UNUSED( isGhost );

    char suffix[4], lvl[5];
    char finalprefix[kFileNameLen];

    //if (level < 0) level = 0; //
    strcpy(suffix, (level < 10) ? "0" : "");
    itoa(level, lvl, 10);
    strcat(suffix, lvl);
    suffix[2] = where + 97;
    suffix[3] = '\0';

    // init buf
    buf[0] = '\0';

#ifdef SAVE_DIR_PATH
    strcpy(buf, SAVE_DIR_PATH);
#endif

    strncpy(finalprefix, prefix, kFileNameLen);
    finalprefix[kFileNameLen] = '\0';

    strcat(buf, finalprefix);

#ifdef SAVE_DIR_PATH
    // everyone sees everyone else's ghosts. :)
    char uid[10];
    if (!isGhost)
    {
        itoa( (int) getuid(), uid, 10 );
        strcat(buf, uid);
    }
#endif

#ifdef JP /* 訳不用？ */
    strcat(buf, ".");
#else
    strcat(buf, ".");
#endif
    if (isLabyrinth)
#ifdef JP /* 訳不用？ */
        strcat(buf, "lab");     // temporary level
#else
        strcat(buf, "lab");     // temporary level
#endif
    else
        strcat(buf, suffix);
}

static void write_tagged_file( FILE *dataFile, char majorVersion,
                               char minorVersion, int fileType )
{
    struct tagHeader th;

    // find all relevant tags
    char tags[NUM_TAGS];
    tag_set_expected(tags, fileType);

    // write version
    struct tagHeader versionTag;
    versionTag.offset = 0;
    versionTag.tagID = TAG_VERSION;
    marshallByte(versionTag, majorVersion);
    marshallByte(versionTag, minorVersion);
    tag_write(versionTag, dataFile);

    // all other tags
    for(int i=1; i<NUM_TAGS; i++)
    {
        if (tags[i] == 1)
        {
            tag_construct(th, i);
            tag_write(th, dataFile);
        }
    }
}

void load( unsigned char stair_taken, int load_mode, bool was_a_labyrinth,
           char old_level, char where_were_you2 )
{
    int j = 0;
    int i = 0, count_x = 0, count_y = 0;
    char cha_fil[kFileNameSize];

    int foll_class[8];
    int foll_hp[8];
    int foll_hp_max[8];
    unsigned char foll_HD[8];
    int foll_AC[8];
    char foll_ev[8];
    unsigned char foll_speed[8];
    unsigned char foll_speed_inc[8];

    unsigned char foll_targ_1_x[8];
    unsigned char foll_targ_1_y[8];
    unsigned char foll_beh[8];
    unsigned char foll_att[8];
    int foll_sec[8];
    unsigned char foll_hit[8];

    unsigned char foll_ench[8][NUM_MON_ENCHANTS];
    unsigned char foll_flags[8];

    item_def foll_item[8][8];

    int itmf = 0;
    int ic = 0;
    int imn = 0;
    int val;

    bool just_created_level = false;

#ifdef DOS_TERM
    window(1, 1, 80, 25);
#endif

    make_filename( cha_fil, you.your_name, you.your_level, you.where_are_you,
                   you.level_type != LEVEL_DUNGEON, false );

    if (you.level_type == LEVEL_DUNGEON)
    {
        if (tmp_file_pairs[you.your_level][you.where_are_you] == false)
        {
            // make sure old file is gone
            unlink(cha_fil);

            // save the information for later deletion -- DML 6/11/99
            tmp_file_pairs[you.your_level][you.where_are_you] = true;
        }
    }

    you.prev_targ = MHITNOT;

    int following = -1;
    int fmenv = 0;     // not used again until after found_stair label {dlb}
    int minvc = 0;

    // Don't delete clouds just because the player saved and restarted.
    if (load_mode != LOAD_RESTART_GAME)
    {
        for (int clouty = 0; clouty < MAX_CLOUDS; ++clouty)
            delete_cloud( clouty );

        ASSERT( env.cloud_no == 0 );
    }

    // This block is to grab followers and save the old level to disk.
    if (load_mode == LOAD_ENTER_LEVEL)
    {
        // grab followers
        for (count_x = you.x_pos - 1; count_x < you.x_pos + 2; count_x++)
        {
            for (count_y = you.y_pos - 1; count_y < you.y_pos + 2; count_y++)
            {
                if (count_x == you.x_pos && count_y == you.y_pos)
                    continue;

                following++;
                foll_class[following] = -1;

                if (mgrd[count_x][count_y] == NON_MONSTER)
                    continue;

                struct monsters *fmenv = &menv[mgrd[count_x][count_y]];

                if (fmenv->type == MONS_PLAYER_GHOST
                    && fmenv->hit_points < fmenv->max_hit_points / 2)
                {
#ifdef JP /* 訳不用？ */
                    mpr("亡霊は影の中に消え去った。");
#else
                    mpr("The ghost fades into the shadows.");
#endif
                    monster_teleport(fmenv, true);
                    continue;
                }

                // monster has to be already tagged in order to follow:
                if (!testbits( fmenv->flags, MF_TAKING_STAIRS ))
                    continue;

#if DEBUG_DIAGNOSTICS
#ifdef JP /* 訳不用？ */
                snprintf( info, INFO_SIZE, "%s is following.", 
#else
                snprintf( info, INFO_SIZE, "%s is following.", 
#endif
                          ptr_monam( fmenv, DESC_CAP_THE ) );
                mpr( info, MSGCH_DIAGNOSTICS );
#endif

                foll_class[following] = fmenv->type;
                foll_hp[following] = fmenv->hit_points;
                foll_hp_max[following] = fmenv->max_hit_points;
                foll_HD[following] = fmenv->hit_dice;
                foll_AC[following] = fmenv->armour_class;
                foll_ev[following] = fmenv->evasion;
                foll_speed[following] = fmenv->speed;
                foll_speed_inc[following] = fmenv->speed_increment;
                foll_targ_1_x[following] = fmenv->target_x;
                foll_targ_1_y[following] = fmenv->target_y;

                for (minvc = 0; minvc < NUM_MONSTER_SLOTS; ++minvc)
                {
                    const int item = fmenv->inv[minvc];
                    if (item == NON_ITEM)
                    {
                        foll_item[following][minvc].quantity = 0;
                        continue;
                    }

                    foll_item[following][minvc] = mitm[item];
                    destroy_item( item );
                }

                foll_beh[following] = fmenv->behaviour;
                foll_att[following] = fmenv->attitude;
                foll_sec[following] = fmenv->number;
                foll_hit[following] = fmenv->foe;

                for (j = 0; j < NUM_MON_ENCHANTS; j++)
                {
                    foll_ench[following][j] = fmenv->enchantment[j];
                    fmenv->enchantment[j] = ENCH_NONE;
                }

                foll_flags[following] = fmenv->flags;

                fmenv->flags = 0;
                fmenv->type = -1;
                fmenv->hit_points = 0;
                fmenv->max_hit_points = 0;
                fmenv->hit_dice = 0;
                fmenv->armour_class = 0;
                fmenv->evasion = 0;

                mgrd[count_x][count_y] = NON_MONSTER;
            }
        }                        // end of grabbing followers

        if (!was_a_labyrinth)
            save_level( old_level, false, where_were_you2 );

        was_a_labyrinth = false;
    }

    // clear out ghost/demon lord information:
    strcpy( ghost.name, "" );
    for (ic = 0; ic < NUM_GHOST_VALUES; ++ic)
        ghost.values[ic] = 0;

#ifdef DOS
    strupr(cha_fil);
#endif

    // Try to open level savefile.
    FILE *levelFile = fopen(cha_fil, "rb");

    // GENERATE new level when the file can't be opened:
    if (levelFile == NULL)
    {                           
        strcpy(ghost.name, "");

        for (imn = 0; imn < NUM_GHOST_VALUES; ++imn)
            ghost.values[imn] = 0;

        builder( you.your_level, you.level_type );
        just_created_level = true;

        if (you.level_type == LEVEL_PANDEMONIUM)
            generate_random_demon();

        if (you.your_level > 1 && one_chance_in(3))
            load_ghost();
    }
    else
    {
        // BEGIN -- must load the old level : pre-load tasks

        // LOAD various tags
        char majorVersion;
        char minorVersion;

        if (!determine_level_version( levelFile, majorVersion, minorVersion ))
        {
#ifdef JP /* 訳不用？ */
            perror("\nLevel file appears to be invalid.\n");
#else
            perror("\nLevel file appears to be invalid.\n");
#endif
            end(-1);
        }

        restore_level_version( levelFile, majorVersion, minorVersion );

        // sanity check - EOF
        if (!feof( levelFile ))
        {
#ifdef JP /* 訳不用？ */
            snprintf( info, INFO_SIZE, "\nIncomplete read of \"%s\" - aborting.\n", cha_fil);
#else
            snprintf( info, INFO_SIZE, "\nIncomplete read of \"%s\" - aborting.\n", cha_fil);
#endif
            perror(info);
            end(-1);
        }

        fclose( levelFile );

        // POST-LOAD tasks :
        link_items();
        redraw_all();
    }

#ifdef USE_TILE
    if (Options.use_tile)    
    {
        TileGhostInit(ghost);
        if (you.level_type == LEVEL_PANDEMONIUM)
            TilePandemInit(ghost);
    }
#endif

    // closes all the gates if you're on the way out
    for (i = 0; i < GXM; i++)
    {
        for (j = 0; j < GYM; j++)
        {
            if (just_created_level)
                env.map[i][j] = 0;

            if (you.char_direction == DIR_ASCENDING
                && you.level_type != LEVEL_PANDEMONIUM)
            {
                if (grd[i][j] == DNGN_ENTER_HELL
                    || grd[i][j] == DNGN_ENTER_ABYSS
                    || grd[i][j] == DNGN_ENTER_PANDEMONIUM)
                {
                    grd[i][j] = DNGN_STONE_ARCH;
                }
            }

            if (load_mode != LOAD_RESTART_GAME)
                env.cgrid[i][j] = EMPTY_CLOUD;
        }
    }

    // This next block is for cases where we want to look for a stairs 
    // to place the player.
    if (load_mode != LOAD_RESTART_GAME && you.level_type != LEVEL_ABYSS)
    {
        bool find_first = true;

        // Order is important here:
        if (you.level_type == LEVEL_DUNGEON 
            && where_were_you2 == BRANCH_VESTIBULE_OF_HELL
            && stair_taken == DNGN_STONE_STAIRS_UP_I)
        {
            // leaving hell - look for entry potal first
            stair_taken = DNGN_ENTER_HELL;
            find_first = false;
        }
        else if (stair_taken == DNGN_EXIT_PANDEMONIUM)
        {
            stair_taken = DNGN_ENTER_PANDEMONIUM;
            find_first = false;
        }
        else if (stair_taken == DNGN_EXIT_ABYSS)
        {
            stair_taken = DNGN_ENTER_ABYSS;
            find_first = false;
        }
        else if (stair_taken == DNGN_ENTER_HELL 
            || stair_taken == DNGN_ENTER_LABYRINTH)
        {
            // the vestibule and labyrith always start from this stair
            stair_taken = DNGN_STONE_STAIRS_UP_I;
        }
        else if (stair_taken >= DNGN_STONE_STAIRS_DOWN_I 
            && stair_taken <= DNGN_ROCK_STAIRS_DOWN)
        {
            // look for coresponding up stair
            stair_taken += (DNGN_STONE_STAIRS_UP_I - DNGN_STONE_STAIRS_DOWN_I);
        }
        else if (stair_taken >= DNGN_STONE_STAIRS_UP_I 
            && stair_taken <= DNGN_ROCK_STAIRS_UP)
        {
            // look for coresponding down stair
            stair_taken += (DNGN_STONE_STAIRS_DOWN_I - DNGN_STONE_STAIRS_UP_I);
        }
        else if (stair_taken >= DNGN_RETURN_FROM_ORCISH_MINES 
            && stair_taken < 150) // 20 slots reserved
        {
            // find entry point to subdungeon when leaving
            stair_taken += (DNGN_ENTER_ORCISH_MINES - DNGN_RETURN_FROM_ORCISH_MINES);
        }
        else if (stair_taken >= DNGN_ENTER_ORCISH_MINES
             && stair_taken < DNGN_RETURN_FROM_ORCISH_MINES)
        {
            // find exit staircase from subdungeon when entering 
            stair_taken += (DNGN_RETURN_FROM_ORCISH_MINES - DNGN_ENTER_ORCISH_MINES);
        }
        else if (stair_taken >= DNGN_ENTER_DIS 
            && stair_taken <= DNGN_TRANSIT_PANDEMONIUM)
        {
            // when entering a hell or pandemonium
            stair_taken = DNGN_STONE_STAIRS_UP_I;
        }
        else // Note: stair_taken can equal things like DNGN_FLOOR
        {
            // just find a nice empty square
            stair_taken = DNGN_FLOOR;
            find_first = false;
        }

        int found = 0;
        int x_pos = 0, y_pos = 0;

        // Start by looking for the expected entry point:
        for (count_x = 0; count_x < GXM; count_x++)
        {
            for (count_y = 0; count_y < GYM; count_y++)
            {
                if (grd[count_x][count_y] == stair_taken)
                {
                    found++;
                    if (one_chance_in( found ))
                    {
                        x_pos = count_x;
                        y_pos = count_y;
                    }

                    if (find_first)
                        goto found_stair;  // double break
                }
            }
        }

found_stair:
        if (!found)
        {
            // See if we can find a stairway in the "right" direction:
            for (count_x = 0; count_x < GXM; count_x++)
            {
                for (count_y = 0; count_y < GYM; count_y++)
                {
                    if (stair_taken <= DNGN_ROCK_STAIRS_DOWN)
                    {
                        // looking for any down stairs
                        if (grd[count_x][count_y] >= DNGN_STONE_STAIRS_DOWN_I
                            && grd[count_x][count_y] <= DNGN_ROCK_STAIRS_DOWN)
                        {
                            found++;
                            if (one_chance_in( found ))
                            {
                                x_pos = count_x;
                                y_pos = count_y;
                            }
                        }
                    }
                    else 
                    {
                        // looking for any up stairs
                        if (grd[count_x][count_y] >= DNGN_STONE_STAIRS_UP_I
                            && grd[count_x][count_y] <= DNGN_ROCK_STAIRS_UP)
                        {
                            found++;
                            if (one_chance_in( found ))
                            {
                                x_pos = count_x;
                                y_pos = count_y;
                            }
                        }
                    }
                }
            }

            if (!found)
            {
                // Still not found? Look for any clear terrain:
                for (count_x = 0; count_x < GXM; count_x++)
                {
                    for (count_y = 0; count_y < GYM; count_y++)
                    {
                        if (grd[count_x][count_y] >= DNGN_FLOOR)
                        {
                            found++;
                            if (one_chance_in( found ))
                            {
                                x_pos = count_x;
                                y_pos = count_y;
                            }
                        }
                    }
                }
            }
        }

        // If still not found, the level is very buggy.
        ASSERT( found );

        you.x_pos = x_pos;
        you.y_pos = y_pos;
    }
    else if (load_mode != LOAD_RESTART_GAME && you.level_type == LEVEL_ABYSS)
    {
        you.x_pos = 45;
        you.y_pos = 35;
    }

    // This should fix the "monster occuring under the player" bug?
    if (mgrd[you.x_pos][you.y_pos] != NON_MONSTER)
        monster_teleport(&menv[mgrd[you.x_pos][you.y_pos]], true);
    /*
    if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS)
        grd[you.x_pos][you.y_pos] = DNGN_FLOOR;
    */
    following = 0;
    fmenv = -1;

    // actually "move" the followers if applicable
    if ((you.level_type == LEVEL_DUNGEON
            || you.level_type == LEVEL_PANDEMONIUM)
        && load_mode == LOAD_ENTER_LEVEL)
    {
        for (ic = 0; ic < 2; ic++)
        {
            for (count_x = you.x_pos - 6; count_x < you.x_pos + 7;
                 count_x++)
            {
                for (count_y = you.y_pos - 6; count_y < you.y_pos + 7;
                     count_y++)
                {
                    if (ic == 0
                        && ((count_x < you.x_pos - 1)
                            || (count_x > you.x_pos + 1)
                            || (count_y < you.y_pos - 1)
                            || (count_y > you.y_pos + 1)))
                    {
                        continue;
                    }

                    if (count_x == you.x_pos && count_y == you.y_pos)
                        continue;

                    if (mgrd[count_x][count_y] != NON_MONSTER
                        || grd[count_x][count_y] < DNGN_FLOOR)
                    {
                        continue;
                    }

                    while (menv[following].type != -1)
                    {
                        following++;

                        if (following >= MAX_MONSTERS)
                            goto out_of_foll;
                    }

                    while (fmenv < 7)
                    {
                        fmenv++;

                        if (foll_class[fmenv] == -1)
                            continue;

                        menv[following].type = foll_class[fmenv];
                        menv[following].hit_points = foll_hp[fmenv];
                        menv[following].max_hit_points = foll_hp_max[fmenv];
                        menv[following].hit_dice = foll_HD[fmenv];
                        menv[following].armour_class = foll_AC[fmenv];
                        menv[following].evasion = foll_ev[fmenv];
                        menv[following].speed = foll_speed[fmenv];
                        menv[following].x = count_x;
                        menv[following].y = count_y;
                        menv[following].target_x = 0;
                        menv[following].target_y = 0;
                        menv[following].speed_increment = foll_speed_inc[fmenv];

                        for (minvc = 0; minvc < NUM_MONSTER_SLOTS; minvc++)
                        {

                            if (!is_valid_item(foll_item[fmenv][minvc]))
                            {
                                menv[following].inv[minvc] = NON_ITEM;
                                continue;
                            }

                            itmf = get_item_slot(0);
                            if (itmf == NON_ITEM)
                            {
                                menv[following].inv[minvc] = NON_ITEM;
                                continue;
                            }

                            mitm[itmf] = foll_item[fmenv][minvc];
                            mitm[itmf].x = 0;
                            mitm[itmf].y = 0;
                            mitm[itmf].link = NON_ITEM;

                            menv[following].inv[minvc] = itmf;
                        }

                        menv[following].behaviour = foll_beh[fmenv];
                        menv[following].attitude = foll_att[fmenv];
                        menv[following].number = foll_sec[fmenv];
                        menv[following].foe = foll_hit[fmenv];

                        for (j = 0; j < NUM_MON_ENCHANTS; j++)
                            menv[following].enchantment[j]=foll_ench[fmenv][j];

                        menv[following].flags = foll_flags[fmenv];
                        menv[following].flags |= MF_JUST_SUMMONED;

                        mgrd[count_x][count_y] = following;
                        break;
                    }
                }
            }
        }
    }                       // end of moving followers

  out_of_foll:
    redraw_all();

    // Sanity forcing of monster inventory items (required?)
    for (i = 0; i < MAX_MONSTERS; i++)
    {
        if (menv[i].type == -1)
            continue;

        for (j = 0; j < NUM_MONSTER_SLOTS; j++)
        {
            if (menv[i].inv[j] == NON_ITEM)
                continue;

            /* items carried by monsters shouldn't be linked */
            if (mitm[menv[i].inv[j]].link != NON_ITEM)
                mitm[menv[i].inv[j]].link = NON_ITEM;
        }
    }

    // Translate stairs for pandemonium levels:
    if (you.level_type == LEVEL_PANDEMONIUM)
    {
        for (count_x = 0; count_x < GXM; count_x++)
        {
            for (count_y = 0; count_y < GYM; count_y++)
            {
                if (grd[count_x][count_y] >= DNGN_STONE_STAIRS_UP_I
                    && grd[count_x][count_y] <= DNGN_ROCK_STAIRS_UP)
                {
                    if (one_chance_in( you.mutation[MUT_PANDEMONIUM] ? 5 : 50 ))
                        grd[count_x][count_y] = DNGN_EXIT_PANDEMONIUM;
                    else
                        grd[count_x][count_y] = DNGN_FLOOR;
                }

                if (grd[count_x][count_y] >= DNGN_ENTER_LABYRINTH
                    && grd[count_x][count_y] <= DNGN_ROCK_STAIRS_DOWN)
                {
                    grd[count_x][count_y] = DNGN_TRANSIT_PANDEMONIUM;
                }
            }
        }
    }

    // Things to update for player entering level
    if (load_mode == LOAD_ENTER_LEVEL)
    {
        // update corpses and fountains
        if (env.elapsed_time != 0.0)
            update_level( you.elapsed_time - env.elapsed_time );

        // Centaurs have difficulty with stairs
        val = ((you.species != SP_CENTAUR) ? player_movement_speed() : 15); 

        // new levels have less wary monsters: 
        if (just_created_level)
            val /= 2;

        val -= (stepdown_value( check_stealth(), 50, 50, 150, 150 ) / 10);

#if DEBUG_DIAGNOSTICS
#ifdef JP /* 訳不用？ */
        snprintf( info, INFO_SIZE, "arrival time: %d", val );
#else
        snprintf( info, INFO_SIZE, "arrival time: %d", val );
#endif
        mpr( info, MSGCH_DIAGNOSTICS ); 
#endif

        if (val > 0)
        {
            you.time_taken = val;
            handle_monsters();
        }
    }

    // Save the created/updated level out to disk:
    save_level( you.your_level, (you.level_type != LEVEL_DUNGEON),
                you.where_are_you );
}                               // end load()

void save_level(int level_saved, bool was_a_labyrinth, char where_were_you)
{
    char cha_fil[kFileNameSize];

    make_filename( cha_fil, you.your_name, level_saved, where_were_you,
                   was_a_labyrinth, false );

    you.prev_targ = MHITNOT;

#ifdef DOS
    strupr(cha_fil);
#endif

    FILE *saveFile = fopen(cha_fil, "wb");

    if (saveFile == NULL)
    {
#ifdef JP /* 訳不用？ */
        strcpy(info, "Unable to open \"");
#else
        strcpy(info, "Unable to open \"");
#endif
        strcat(info, cha_fil );
#ifdef JP /* 訳不用？ */
        strcat(info, "\" for writing!");
#else
        strcat(info, "\" for writing!");
#endif
        perror(info);
        end(-1);
    }

    // nail all items to the ground
    fix_item_coordinates();

    // 4.0 initial genesis of saved format
    // 4.1 added attitude tag
    // 4.2 replaced old 'enchantment1' and with 'flags' (bitfield)
    // 4.3 changes to make the item structure more sane  
    // 4.4 changes to the ghost save section
    // 4.5 spell and ability letter arrays

    write_tagged_file( saveFile, 4, 5, TAGTYPE_LEVEL );

    fclose(saveFile);

#ifdef SHARED_FILES_CHMOD_PRIVATE
    chmod(cha_fil, SHARED_FILES_CHMOD_PRIVATE);
#endif
}                               // end save_level()

void save_game(bool leave_game)
{
    char charFile[kFileNameSize];
    char stashFile[kFileNameSize + 4];
    char killFile[kFileNameSize + 4];
    char travelCacheFile[kFileNameSize + 4];

#ifdef SAVE_PACKAGE_CMD
    char cmd_buff[1024];
    char name_buff[kFileNameSize];

    snprintf( name_buff, sizeof(name_buff), 
              SAVE_DIR_PATH "%s%d", you.your_name, (int) getuid() );

    snprintf( cmd_buff, sizeof(cmd_buff), 
              SAVE_PACKAGE_CMD, name_buff, name_buff );

    strcpy(stashFile, name_buff);
    strcpy(killFile, name_buff);
    strcpy(travelCacheFile, name_buff);
    snprintf( charFile, sizeof(charFile), 
              "%s.sav", name_buff );

#else
    strncpy(charFile, you.your_name, kFileNameLen);
    charFile[kFileNameLen] = 0;

    strcpy(stashFile, charFile);
    strcpy(killFile, charFile);
    strcpy(travelCacheFile, charFile);
    strcat(charFile, ".sav");

#ifdef DOS
    strupr(charFile);
    strupr(stashFile);
    strupr(killFile);
    strupr(travelCacheFile);
#endif
#endif

    strcat(stashFile, ".st");
    strcat(killFile, ".kil");
    strcat(travelCacheFile, ".tc");

#ifdef STASH_TRACKING
    FILE *stashf = fopen(stashFile, "wb");
    if (stashf) {
        stashes.save(stashf);
        fclose(stashf);

#ifdef SHARED_FILES_CHMOD_PRIVATE
        // change mode (unices)
        chmod(stashFile, SHARED_FILES_CHMOD_PRIVATE);
#endif
    }
#endif // STASH_TRACKING

    FILE *travelf = fopen(travelCacheFile, "wb");
    if (travelf) {
        travel_cache.save(travelf);
        fclose(travelf);
#ifdef SHARED_FILES_CHMOD_PRIVATE
        // change mode (unices)
        chmod(travelCacheFile, SHARED_FILES_CHMOD_PRIVATE);
#endif
    }

    FILE *killf = fopen(killFile, "wb");
    if (killf) {
        you.kills.save(killf);
        fclose(killf);

#ifdef SHARED_FILES_CHMOD_PRIVATE
        // change mode (unices)
        chmod(killFile, SHARED_FILES_CHMOD_PRIVATE);
#endif
    }

    FILE *saveFile = fopen(charFile, "wb");

    if (saveFile == NULL)
    {
#ifdef JP /* 訳不用？ */
        strcpy(info, "Unable to open \"");
#else
        strcpy(info, "Unable to open \"");
#endif
        strcat(info, charFile );
#ifdef JP /* 訳不用？ */
        strcat(info, "\" for writing!");
#else
        strcat(info, "\" for writing!");
#endif
        perror(info);
        end(-1);
    }

    // 4.0 initial genesis of saved format
    // 4.1 changes to make the item structure more sane  
    // 4.2 spell and ability tables
    // 4.3 added you.magic_contamination (05/03/05)

    write_tagged_file( saveFile, 4, 3, TAGTYPE_PLAYER );

    fclose(saveFile);

#ifdef SHARED_FILES_CHMOD_PRIVATE
    // change mode (unices)
    chmod(charFile, SHARED_FILES_CHMOD_PRIVATE);
#endif

    // if just save, early out
    if (!leave_game)
        return;

    // must be exiting -- save level & goodbye!
    save_level(you.your_level, (you.level_type != LEVEL_DUNGEON),
               you.where_are_you);

#ifdef DOS_TERM
    window(1, 1, 80, 25);
#endif

    clrscr();

#ifdef SAVE_PACKAGE_CMD
    if (system( cmd_buff ) != 0)
    {
#ifdef JP /* 訳不用？ */
        cprintf( EOL "Warning: Zip command (SAVE_PACKAGE_CMD) returned non-zero value!" EOL );
#else
        cprintf( EOL "Warning: Zip command (SAVE_PACKAGE_CMD) returned non-zero value!" EOL );
#endif
    }

#ifdef SHARED_FILES_CHMOD_PRIVATE
    strcat( name_buff, PACKAGE_SUFFIX );
    // change mode (unices)
    chmod( name_buff, SHARED_FILES_CHMOD_PRIVATE );
#endif

#endif

#if defined(WIN32CONSOLE) || defined(WINDOWS)
//キャラクター名をファイルに書き込み
    FILE *fp;

    if ( (fp = fopen("latest.nam", "w+")) == NULL )
    {
    }
    else
    {
        fprintf(fp, "YourName=%s\n", you.your_name);
        fclose(fp);
    }
#endif

#ifdef JP /* 訳不用？ */
    cprintf( "See you soon, %s!" EOL , you.your_name );
#else
    cprintf( "See you soon, %s!" EOL , you.your_name );
#endif

#ifdef WINDOWS
    //Windowsでの終了処理はWindowsのメッセージループに任せる
#else
    end(0);
#endif
}                               // end save_game()

void load_ghost(void)
{
    char majorVersion;
    char minorVersion;
    char cha_fil[kFileNameSize];
    int imn;
    int i;

    make_filename( cha_fil, "bones", you.your_level, you.where_are_you,
                   (you.level_type != LEVEL_DUNGEON), true );

    FILE *gfile = fopen(cha_fil, "rb");

    if (gfile == NULL)
        return;                 // no such ghost.

    if (!determine_ghost_version(gfile, majorVersion, minorVersion))
    {
        fclose(gfile);
#if DEBUG_DIAGNOSTICS
#ifdef JP /* 訳不用？ */
        snprintf( info, INFO_SIZE, "Ghost file \"%s\" seems to be invalid.",
#else
        snprintf( info, INFO_SIZE, "Ghost file \"%s\" seems to be invalid.",
#endif
            cha_fil);
        mpr( info, MSGCH_DIAGNOSTICS );
        more();
#endif
        return;
    }

    restore_ghost_version(gfile, majorVersion, minorVersion);

    // sanity check - EOF
    if (!feof(gfile))
    {
        fclose(gfile);
#if DEBUG_DIAGNOSTICS
#ifdef JP /* 訳不用？ */
        snprintf( info, INFO_SIZE, "Incomplete read of \"%s\".", cha_fil);
#else
        snprintf( info, INFO_SIZE, "Incomplete read of \"%s\".", cha_fil);
#endif
        mpr( info, MSGCH_DIAGNOSTICS );
        more();
#endif
        return;
    }

    fclose(gfile);

#if DEBUG_DIAGNOSTICS
#ifdef JP /* 訳不用？ */
        mpr( "Loaded ghost.", MSGCH_DIAGNOSTICS );
#else
        mpr( "Loaded ghost.", MSGCH_DIAGNOSTICS );
#endif
#endif

    // remove bones file - ghosts are hardly permanent.
    unlink(cha_fil);

    // translate ghost to monster and place.
    for (imn = 0; imn < MAX_MONSTERS - 10; imn++)
    {
        if (menv[imn].type != -1)
            continue;

        menv[imn].type = MONS_PLAYER_GHOST;
        menv[imn].hit_dice = ghost.values[ GVAL_EXP_LEVEL ];
        menv[imn].hit_points = ghost.values[ GVAL_MAX_HP ];
        menv[imn].max_hit_points = ghost.values[ GVAL_MAX_HP ];
        menv[imn].armour_class = ghost.values[ GVAL_AC];
        menv[imn].evasion = ghost.values[ GVAL_EV ];
        menv[imn].speed = 10;
        menv[imn].speed_increment = 70;
        menv[imn].attitude = ATT_HOSTILE;
        menv[imn].behaviour = BEH_WANDER;
        menv[imn].flags = 0;
        menv[imn].foe = MHITNOT;
        menv[imn].foe_memory = 0;

        menv[imn].number = 250;

        for (i = GVAL_SPELL_1; i <= GVAL_SPELL_6; i++)
        {
            if (ghost.values[i] != MS_NO_SPELL)
            {
                menv[imn].number = MST_GHOST;
                break;
            }
        }

        for (i = 0; i < NUM_MONSTER_SLOTS; i++)
            menv[imn].inv[i] = NON_ITEM;

        for (i = 0; i < NUM_MON_ENCHANTS; i++)
            menv[imn].enchantment[i] = ENCH_NONE;

        do
        {
            menv[imn].x = random2(GXM - 20) + 10;
            menv[imn].y = random2(GYM - 20) + 10;
        }
        while ((grd[menv[imn].x][menv[imn].y] != DNGN_FLOOR)
               || (mgrd[menv[imn].x][menv[imn].y] != NON_MONSTER));

        mgrd[menv[imn].x][menv[imn].y] = imn;
        break;
    }
}


void restore_game(void)
{
    char char_f[kFileNameSize];
    char stash_f[kFileNameSize];
    char kill_f[kFileNameSize];
    char travel_f[kFileNameSize];
#ifdef SAVE_DIR_PATH
    snprintf( char_f, sizeof(char_f), 
              SAVE_DIR_PATH "%s%d", you.your_name, (int) getuid() );
#else
    strncpy(char_f, you.your_name, kFileNameLen);
    char_f[kFileNameLen] = 0;
#endif

    strcpy(stash_f, char_f);
    strcpy(kill_f, stash_f);
    strcpy(travel_f, char_f);
    strcat(stash_f, ".st");
    strcat(kill_f, ".kil");
    strcat(travel_f, ".tc");
    strcat(char_f, ".sav");

#ifdef DOS
    strupr(char_f);
    strupr(stash_f);
    strupr(kill_f);
    strupr(travel_f);
#endif

    FILE *restoreFile = fopen(char_f, "rb");

    if (restoreFile == NULL)
    {
#ifdef JP /* 訳不用？ */
        strcpy(info, "Unable to open \"");
#else
        strcpy(info, "Unable to open \"");
#endif
        strcat(info, char_f );
#ifdef JP /* 訳不用？ */
        strcat(info, "\" for reading!");
#else
        strcat(info, "\" for reading!");
#endif
        perror(info);
        end(-1);
    }

    char majorVersion;
    char minorVersion;

    if (!determine_version(restoreFile, majorVersion, minorVersion))
    {
#ifdef JP /* 訳不用？ */
        perror("\nSavefile appears to be invalid.\n");
#else
        perror("\nSavefile appears to be invalid.\n");
#endif
        end(-1);
    }

    restore_version(restoreFile, majorVersion, minorVersion);

    // sanity check - EOF
    if (!feof(restoreFile))
    {
#ifdef JP /* 訳不用？ */
        snprintf( info, INFO_SIZE, "\nIncomplete read of \"%s\" - aborting.\n", char_f);
#else
        snprintf( info, INFO_SIZE, "\nIncomplete read of \"%s\" - aborting.\n", char_f);
#endif
        perror(info);
        end(-1);
    }

    fclose(restoreFile);

#ifdef STASH_TRACKING
    FILE *stashFile = fopen(stash_f, "rb");
    if (stashFile) {
        stashes.load(stashFile);
        fclose(stashFile);
    }
#endif

    FILE *travelFile = fopen(travel_f, "rb");
    if (travelFile) {
        travel_cache.load(travelFile);
        fclose(travelFile);
    }

    FILE *killFile = fopen(kill_f, "rb");
    if (killFile) {
        you.kills.load(killFile);
        fclose(killFile);
    }
}

static bool determine_version( FILE *restoreFile, 
                               char &majorVersion, char &minorVersion )
{
    // read first two bytes.
    char buf[2];
    if (read2(restoreFile, buf, 2) != 2)
        return false;               // empty file?

    // check for 3.30
    if (buf[0] == you.your_name[0] && buf[1] == you.your_name[1])
    {
        majorVersion = 0;
        minorVersion = 0;
        rewind(restoreFile);
        return true;
    }

    // otherwise, read version and validate.
    majorVersion = buf[0];
    minorVersion = buf[1];

    if (majorVersion == 1 || majorVersion == 4)
        return true;

    return false;   // if its not 1 or 4, no idea!
}

static void restore_version( FILE *restoreFile, 
                             char majorVersion, char minorVersion )
{
    // assuming the following check can be removed once we can read all
    // savefile versions.
    if (majorVersion < 4)
    {
#ifdef JP /* 訳不用？ */
        snprintf( info, INFO_SIZE, "\nSorry, this release cannot read a v%d.%d savefile.\n",
#else
        snprintf( info, INFO_SIZE, "\nSorry, this release cannot read a v%d.%d savefile.\n",
#endif
            majorVersion, minorVersion);
        perror(info);
        end(-1);
    }

    switch(majorVersion)
    {
        case 4:
            restore_tagged_file(restoreFile, TAGTYPE_PLAYER, minorVersion);
            break;
        default:
            break;
    }
}

// generic v4 restore function
static void restore_tagged_file( FILE *restoreFile, int fileType, 
                                 char minorVersion )
{
    int i;

    char tags[NUM_TAGS];
    tag_set_expected(tags, fileType);

    while(1)
    {
        i = tag_read(restoreFile, minorVersion);
        if (i == 0)                 // no tag!
            break;
        tags[i] = 0;                // tag read
    }

    // go through and init missing tags
    for(i=0; i<NUM_TAGS; i++)
    {
        if (tags[i] == 1)           // expected but never read
            tag_missing(i, minorVersion);
    }
}

static bool determine_level_version( FILE *levelFile, 
                                     char &majorVersion, char &minorVersion )
{
    // read first two bytes.
    char buf[2];
    if (read2(levelFile, buf, 2) != 2)
        return false;               // empty file?

    // check for 3.30 -- simply started right in with player name.
    if (isprint(buf[0]) && buf[0] > 4)      // who knows?
    {
        majorVersion = 0;
        minorVersion = 0;
        rewind(levelFile);
        return true;
    }

    // otherwise, read version and validate.
    majorVersion = buf[0];
    minorVersion = buf[1];

    if (majorVersion == 1 || majorVersion == 4)
        return true;

    return false;   // if its not 1 or 4, no idea!
}

static void restore_level_version( FILE *levelFile, 
                                   char majorVersion, char minorVersion )
{
    // assuming the following check can be removed once we can read all
    // savefile versions.
    if (majorVersion < 4)
    {
#ifdef JP /* 訳不用？ */
        snprintf( info, INFO_SIZE, "\nSorry, this release cannot read a v%d.%d level file.\n",
#else
        snprintf( info, INFO_SIZE, "\nSorry, this release cannot read a v%d.%d level file.\n",
#endif
            majorVersion, minorVersion);
        perror(info);
        end(-1);
    }

    switch(majorVersion)
    {
        case 4:
            restore_tagged_file(levelFile, TAGTYPE_LEVEL, minorVersion);
            break;
        default:
            break;
    }
}

static bool determine_ghost_version( FILE *ghostFile, 
                                     char &majorVersion, char &minorVersion )
{
    // read first two bytes.
    char buf[2];
    if (read2(ghostFile, buf, 2) != 2)
        return false;               // empty file?

    // check for pre-v4 -- simply started right in with ghost name.
    if (isprint(buf[0]) && buf[0] > 4)
    {
        majorVersion = 0;
        minorVersion = 0;
        rewind(ghostFile);
        return true;
    }

    // otherwise, read version and validate.
    majorVersion = buf[0];
    minorVersion = buf[1];

    if (majorVersion == 4)
        return true;

    return false;   // if its not 4, no idea!
}

static void restore_old_ghost( FILE *ghostFile )
{
    char buf[41];

    read2(ghostFile, buf, 41);  // 41 causes EOF. 40 will not.

    // translate
    memcpy( ghost.name, buf, 20 );

    for (int i = 0; i < 20; i++)
        ghost.values[i] = static_cast< unsigned short >( buf[i+20] );

    if (ghost.values[ GVAL_RES_FIRE ] >= 97)
        ghost.values[ GVAL_RES_FIRE ] -= 100;

    if (ghost.values[ GVAL_RES_COLD ] >= 97)
        ghost.values[ GVAL_RES_COLD ] -= 100;
}

static void restore_ghost_version( FILE *ghostFile, 
                                   char majorVersion, char minorVersion )
{
    // currently, we can read all known ghost versions.
    switch(majorVersion)
    {
        case 4:
            restore_tagged_file(ghostFile, TAGTYPE_GHOST, minorVersion);
            break;
        case 0:
            restore_old_ghost(ghostFile);
            break;
        default:
            break;
    }
}

void save_ghost( bool force )
{
    char cha_fil[kFileNameSize];
    const int wpn = you.equip[EQ_WEAPON];

    if (!force && (you.your_level < 2 || you.is_undead))
        return;

    make_filename( cha_fil, "bones", you.your_level, you.where_are_you,
                   (you.level_type != LEVEL_DUNGEON), true );

    FILE *gfile = fopen(cha_fil, "rb");

    // don't overwrite existing bones!
    if (gfile != NULL)
    {
        fclose(gfile);
        return;
    }

    memcpy(ghost.name, you.your_name, 20);

    ghost.values[ GVAL_MAX_HP ]    = ((you.hp_max >= 150) ? 150 : you.hp_max);
    ghost.values[ GVAL_EV ]        = player_evasion();
    ghost.values[ GVAL_AC ]        = player_AC();
    ghost.values[ GVAL_SEE_INVIS ] = player_see_invis();
    ghost.values[ GVAL_RES_FIRE ]  = player_res_fire();
    ghost.values[ GVAL_RES_COLD ]  = player_res_cold();
    ghost.values[ GVAL_RES_ELEC ]  = player_res_electricity();

    /* note - as ghosts, automatically get res poison + prot_life */

    int d = 4;
    int e = 0;

    if (wpn != -1)
    {
        if (you.inv[wpn].base_type == OBJ_WEAPONS
            || you.inv[wpn].base_type == OBJ_STAVES)
        {
            d = property( you.inv[wpn], PWPN_DAMAGE );

            d *= 25 + you.skills[weapon_skill( you.inv[wpn].base_type,
                                               you.inv[wpn].sub_type )];
            d /= 25;

            if (you.inv[wpn].base_type == OBJ_WEAPONS)
            {
                if (is_random_artefact( you.inv[wpn] ))
                    e = randart_wpn_property( you.inv[wpn], RAP_BRAND );
                else
                    e = you.inv[wpn].special;
            }
        }
    }
    else
    {
        /* Unarmed combat */
        if (you.species == SP_TROLL)
            d += you.experience_level;

        d += you.skills[SK_UNARMED_COMBAT];
    }

    d *= 30 + you.skills[SK_FIGHTING];
    d /= 30;

    d += you.strength / 4;

    if (d > 50)
        d = 50;

    ghost.values[ GVAL_DAMAGE ] = d;
    ghost.values[ GVAL_BRAND ]  = e;
    ghost.values[ GVAL_SPECIES ] = you.species;
    ghost.values[ GVAL_BEST_SKILL ] = best_skill(SK_FIGHTING, (NUM_SKILLS - 1), 99);
    ghost.values[ GVAL_SKILL_LEVEL ] = you.skills[best_skill(SK_FIGHTING, (NUM_SKILLS - 1), 99)];
    ghost.values[ GVAL_EXP_LEVEL ] = you.experience_level;
    ghost.values[ GVAL_CLASS ] = you.char_class;

    add_spells(ghost);

    gfile = fopen(cha_fil, "wb");

    if (gfile == NULL)
    {
#ifdef JP /* 訳不用？ */
        strcpy(info, "Error creating ghost file: ");
#else
        strcpy(info, "Error creating ghost file: ");
#endif
        strcat(info, cha_fil);
        mpr(info);
        more();
        return;
    }

    // 4.0-4.3  old tagged savefile (values as unsigned char)
    // 4.4      new tagged savefile (values as signed short)
    write_tagged_file( gfile, 4, 4, TAGTYPE_GHOST );

    fclose(gfile);

#if DEBUG_DIAGNOSTICS
#ifdef JP /* 訳不用？ */
    mpr( "Saved ghost.", MSGCH_DIAGNOSTICS );
#else
    mpr( "Saved ghost.", MSGCH_DIAGNOSTICS );
#endif
#endif

#ifdef SHARED_FILES_CHMOD_PUBLIC
    chmod(cha_fil, SHARED_FILES_CHMOD_PUBLIC);
#endif
}                               // end save_ghost()

/*
   Used when creating ghosts: goes through and finds spells for the ghost to
   cast. Death is a traumatic experience, so ghosts only remember a few spells.
 */
void add_spells( struct ghost_struct &gs )
{
    int i = 0;

    for (i = GVAL_SPELL_1; i <= GVAL_SPELL_6; i++)
        gs.values[i] = SPELL_NO_SPELL;

    gs.values[ GVAL_SPELL_1 ] = search_first_list(SPELL_NO_SPELL);
    gs.values[ GVAL_SPELL_2 ] = search_first_list(gs.values[GVAL_SPELL_1]);
    gs.values[ GVAL_SPELL_3 ] = search_second_list(SPELL_NO_SPELL);
    gs.values[ GVAL_SPELL_4 ] = search_third_list(SPELL_NO_SPELL);

    if (gs.values[ GVAL_SPELL_4 ] == SPELL_NO_SPELL)
        gs.values[ GVAL_SPELL_4 ] = search_first_list(SPELL_NO_SPELL);

    gs.values[ GVAL_SPELL_5 ] = search_first_list(gs.values[GVAL_SPELL_4]);

    if (gs.values[ GVAL_SPELL_5 ] == SPELL_NO_SPELL)
        gs.values[ GVAL_SPELL_5 ] = search_first_list(gs.values[GVAL_SPELL_4]);

    if (player_has_spell( SPELL_DIG ))
        gs.values[ GVAL_SPELL_5 ] = SPELL_DIG;

    /* Looks for blink/tport for emergency slot */
    if (player_has_spell( SPELL_CONTROLLED_BLINK ) 
        || player_has_spell( SPELL_BLINK ))
    {
        gs.values[ GVAL_SPELL_6 ] = SPELL_CONTROLLED_BLINK;
    }

    if (player_has_spell( SPELL_TELEPORT_SELF ))
        gs.values[ GVAL_SPELL_6 ] = SPELL_TELEPORT_SELF;

    for (i = GVAL_SPELL_1; i <= GVAL_SPELL_6; i++)
        gs.values[i] = translate_spell( gs.values[i] );
}                               // end add_spells()

unsigned char search_first_list(unsigned char ignore_spell)
{
    for (int i = 0; i < 20; i++)
     {
        if (search_order_conj[i] == SPELL_NO_SPELL)
            return SPELL_NO_SPELL;

        if (search_order_conj[i] == ignore_spell)
            continue;

        if (player_has_spell(search_order_conj[i]))
            return search_order_conj[i];
    }

    return SPELL_NO_SPELL;
}                               // end search_first_list()

unsigned char search_second_list(unsigned char ignore_spell)
{
    for (int i = 0; i < 20; i++)
    {
        if (search_order_third[i] == SPELL_NO_SPELL)
            return SPELL_NO_SPELL;

        if (search_order_third[i] == ignore_spell)
            continue;

        if (player_has_spell(search_order_third[i]))
            return search_order_third[i];
    }

    return SPELL_NO_SPELL;
}                               // end search_second_list()

unsigned char search_third_list(unsigned char ignore_spell)
{
    for (int i = 0; i < 20; i++)
    {
        if (search_order_misc[i] == SPELL_NO_SPELL)
            return SPELL_NO_SPELL;

        if (search_order_misc[i] == ignore_spell)
            continue;

        if (player_has_spell(search_order_misc[i]))
            return search_order_misc[i];
    }

    return SPELL_NO_SPELL;
}                               // end search_third_list()


/*
   When passed the number for a player spell, returns the equivalent monster
   spell. Returns SPELL_NO_SPELL on failure (no equiv).
 */
unsigned char translate_spell(unsigned char spel)
{
    switch (spel)
    {
    case SPELL_TELEPORT_SELF:
        return (MS_TELEPORT);

    case SPELL_MAGIC_DART:
        return (MS_MMISSILE);
    case SPELL_FIREBALL:
    case SPELL_DELAYED_FIREBALL:
        return (MS_FIREBALL);
    case SPELL_DIG:
        return (MS_DIG);
    case SPELL_BOLT_OF_FIRE:
        return (MS_FIRE_BOLT);
    case SPELL_BOLT_OF_COLD:
        return (MS_COLD_BOLT);
    case SPELL_LIGHTNING_BOLT:
        return (MS_LIGHTNING_BOLT);
    case SPELL_POLYMORPH_OTHER:
        return (MS_MUTATION);
    case SPELL_SLOW:
        return (MS_SLOW);
    case SPELL_HASTE:
        return (MS_HASTE);
    case SPELL_PARALYZE:
        return (MS_PARALYSIS);
    case SPELL_CONFUSE:
        return (MS_CONFUSE);
    case SPELL_INVISIBILITY:
        return (MS_INVIS);
    case SPELL_THROW_FLAME:
        return (MS_FLAME);
    case SPELL_THROW_FROST:
        return (MS_FROST);
    case SPELL_CONTROLLED_BLINK:
        return (MS_BLINK);        /* approximate */
/*  case FREEZING_CLOUD: return ; no freezing/mephitic cloud yet
   case MEPHITIC_CLOUD: return ; */
    case SPELL_VENOM_BOLT:
        return (MS_VENOM_BOLT);
    case SPELL_TELEPORT_OTHER:
        return (MS_TELEPORT_OTHER);
    case SPELL_SUMMON_SMALL_MAMMAL:
        return (MS_VAMPIRE_SUMMON);       /* approximate */
    case SPELL_BOLT_OF_DRAINING:
        return (MS_NEGATIVE_BOLT);
    case SPELL_LEHUDIBS_CRYSTAL_SPEAR:
        return (MS_CRYSTAL_SPEAR);
    case SPELL_BLINK:
        return (MS_BLINK);
    case SPELL_ISKENDERUNS_MYSTIC_BLAST:
        return (MS_ORB_ENERGY);
    case SPELL_SUMMON_HORRIBLE_THINGS:
        return (MS_LEVEL_SUMMON); /* approximate */
    case SPELL_ANIMATE_DEAD:
        return (MS_ANIMATE_DEAD);
    case SPELL_PAIN:
        return (MS_PAIN);
    case SPELL_SUMMON_WRAITHS:
        return (MS_SUMMON_UNDEAD);        /* approximate */
    case SPELL_STICKY_FLAME:
        return (MS_STICKY_FLAME);
    case SPELL_CALL_IMP:
        return (MS_SUMMON_DEMON_LESSER);
    case SPELL_BANISHMENT:
        return (MS_BANISHMENT);
    case SPELL_STING:
        return (MS_STING);
    case SPELL_SUMMON_DEMON:
        return (MS_SUMMON_DEMON);
    case SPELL_DEMONIC_HORDE:
        return (MS_SUMMON_DEMON_LESSER);
    case SPELL_SUMMON_GREATER_DEMON:
        return (MS_SUMMON_DEMON_GREATER);
    case SPELL_BOLT_OF_IRON:
        return (MS_IRON_BOLT);
    case SPELL_STONE_ARROW:
        return (MS_STONE_ARROW);
    case SPELL_DISINTEGRATE:
        return (MS_DISINTEGRATE);
    case SPELL_AGONY:
        /* Too powerful to give ghosts Torment for Agony? Nah. */
        return (MS_TORMENT);
    case SPELL_SYMBOL_OF_TORMENT:
        return (MS_TORMENT);
    default:
        break;
    }

    return (MS_NO_SPELL);
}

void generate_random_demon(void)
{
    int rdem = 0;
    int i = 0;

    for (rdem = 0; rdem < MAX_MONSTERS + 1; rdem++)
    {
        if (rdem == MAX_MONSTERS)
            return;

        if (menv[rdem].type == MONS_PANDEMONIUM_DEMON)
            break;
    }

    char st_p[ITEMNAME_SIZE];

    make_name(random2(250), random2(250), random2(250), 3, st_p);
    strcpy(ghost.name, st_p);

    // hp - could be defined below (as could ev, AC etc). Oh well, too late:
    ghost.values[ GVAL_MAX_HP ] = 100 + roll_dice( 3, 50 );

    ghost.values[ GVAL_EV ] = 5 + random2(20);
    ghost.values[ GVAL_AC ] = 5 + random2(20);

    ghost.values[ GVAL_SEE_INVIS ] = (one_chance_in(10) ? 0 : 1);

    if (!one_chance_in(3))
        ghost.values[ GVAL_RES_FIRE ] = (coinflip() ? 2 : 3);
    else
    {
        ghost.values[ GVAL_RES_FIRE ] = 0;    /* res_fire */

        if (one_chance_in(10))
            ghost.values[ GVAL_RES_FIRE ] = -1;
    }

    if (!one_chance_in(3))
        ghost.values[ GVAL_RES_COLD ] = 2;
    else
    {
        ghost.values[ GVAL_RES_COLD ] = 0;    /* res_cold */

        if (one_chance_in(10))
            ghost.values[ GVAL_RES_COLD ] = -1;
    }

    // demons, like ghosts, automatically get poison res. and life prot.

    // resist electricity:
    ghost.values[ GVAL_RES_ELEC ] = (!one_chance_in(3) ? 1 : 0);

    // HTH damage:
    ghost.values[ GVAL_DAMAGE ] = 20 + roll_dice( 2, 20 );

    // special attack type (uses weapon brand code):
    ghost.values[ GVAL_BRAND ] = SPWPN_NORMAL;

    if (!one_chance_in(3))
    {
        ghost.values[ GVAL_BRAND ] = random2(17);

        /* some brands inappropriate (eg holy wrath) */
        if (ghost.values[ GVAL_BRAND ] == SPWPN_HOLY_WRATH 
            || ghost.values[ GVAL_BRAND ] == SPWPN_ORC_SLAYING
            || ghost.values[ GVAL_BRAND ] == SPWPN_PROTECTION 
            || ghost.values[ GVAL_BRAND ] == SPWPN_FLAME 
            || ghost.values[ GVAL_BRAND ] == SPWPN_FROST 
            || ghost.values[ GVAL_BRAND ] == SPWPN_DISRUPTION)
        {
            ghost.values[ GVAL_BRAND ] = SPWPN_SPEED;
        }
    }

    // is demon a spellcaster?
    // upped from one_chance_in(3)... spellcasters are more interesting
    // and I expect named demons to typically have a trick or two -- bwr
    ghost.values[GVAL_DEMONLORD_SPELLCASTER] = (one_chance_in(10) ? 0 : 1);

    // does demon fly? (0 = no, 1 = fly, 2 = levitate)
    ghost.values[GVAL_DEMONLORD_FLY] = (one_chance_in(3) ? 0 : 
                                        one_chance_in(5) ? 2 : 1);

    // vacant <ghost best skill level>:
    ghost.values[GVAL_DEMONLORD_UNUSED] = 0;

    // hit dice:
    ghost.values[GVAL_DEMONLORD_HIT_DICE] = 10 + roll_dice(2, 10);

    // does demon cycle colours?
    ghost.values[GVAL_DEMONLORD_CYCLE_COLOUR] = (one_chance_in(10) ? 1 : 0);

    menv[rdem].hit_dice = ghost.values[ GVAL_DEMONLORD_HIT_DICE ];
    menv[rdem].hit_points = ghost.values[ GVAL_MAX_HP ];
    menv[rdem].max_hit_points = ghost.values[ GVAL_MAX_HP ];
    menv[rdem].armour_class = ghost.values[ GVAL_AC ];
    menv[rdem].evasion = ghost.values[ GVAL_EV ];
    menv[rdem].speed = (one_chance_in(3) ? 10 : 6 + roll_dice(2, 9));
    menv[rdem].speed_increment = 70;
    menv[rdem].number = random_colour();        // demon's colour

    for (i = GVAL_SPELL_1; i <= GVAL_SPELL_6; i++)
        ghost.values[i] = SPELL_NO_SPELL;

    /* This bit uses the list of player spells to find appropriate spells
       for the demon, then converts those spells to the monster spell indices.
       Some special monster-only spells are at the end. */
    if (ghost.values[ GVAL_DEMONLORD_SPELLCASTER ] == 1)
    {
        if (coinflip())
        {
            for (;;)
            {
                if (one_chance_in(3))
                    break;

                ghost.values[ GVAL_SPELL_1 ] = search_order_conj[i];
                i++;

                if (search_order_conj[i] == SPELL_NO_SPELL)
                    break;
            }
        } 

        if (coinflip())
        {
            for (;;)
            {
                if (one_chance_in(3))
                    break;

                ghost.values[ GVAL_SPELL_2 ] = search_order_conj[i];

                if (search_order_conj[i] == SPELL_NO_SPELL)
                    break;
            }
        }

        if (!one_chance_in(4))
        {
            for (;;)
            {
                if (one_chance_in(3))
                    break;

                ghost.values[ GVAL_SPELL_3 ] = search_order_third[i];
                i++;

                if (search_order_third[i] == SPELL_NO_SPELL)
                    break;
            }
        }

        if (coinflip())
        {
            for (;;)
            {
                if (one_chance_in(3))
                    break;

                ghost.values[ GVAL_SPELL_4 ] = search_order_misc[i];
                i++;

                if (search_order_misc[i] == SPELL_NO_SPELL)
                    break;
            }
        }

        if (coinflip())
        {
            for(;;)
            {
                if (one_chance_in(3))
                    break;

                ghost.values[ GVAL_SPELL_5 ] = search_order_misc[i];
                i++;

                if (search_order_misc[i] == SPELL_NO_SPELL)
                    break;
            }
        }

        if (coinflip())
            ghost.values[ GVAL_SPELL_6 ] = SPELL_BLINK;
        if (coinflip())
            ghost.values[ GVAL_SPELL_6 ] = SPELL_TELEPORT_SELF;

        /* Converts the player spell indices to monster spell ones */
        for (i = GVAL_SPELL_1; i <= GVAL_SPELL_6; i++)
            ghost.values[i] = translate_spell( ghost.values[i] );

        /* give demon a chance for some monster-only spells: */
        /* and demon-summoning should be fairly common: */
        if (one_chance_in(25))
            ghost.values[GVAL_SPELL_1] = MS_HELLFIRE_BURST;
        if (one_chance_in(25))
            ghost.values[GVAL_SPELL_1] = MS_METAL_SPLINTERS;
        if (one_chance_in(25))
            ghost.values[GVAL_SPELL_1] = MS_ENERGY_BOLT;  /* eye of devas */

        if (one_chance_in(25))
            ghost.values[GVAL_SPELL_2] = MS_STEAM_BALL;
        if (one_chance_in(25))
            ghost.values[GVAL_SPELL_2] = MS_PURPLE_BLAST;
        if (one_chance_in(25))
            ghost.values[GVAL_SPELL_2] = MS_HELLFIRE;

        if (one_chance_in(25))
            ghost.values[GVAL_SPELL_3] = MS_SMITE;
        if (one_chance_in(25))
            ghost.values[GVAL_SPELL_3] = MS_HELLFIRE_BURST;
        if (one_chance_in(12))
            ghost.values[GVAL_SPELL_3] = MS_SUMMON_DEMON_GREATER;
        if (one_chance_in(12))
            ghost.values[GVAL_SPELL_3] = MS_SUMMON_DEMON;

        if (one_chance_in(20))
            ghost.values[GVAL_SPELL_4] = MS_SUMMON_DEMON_GREATER;
        if (one_chance_in(20))
            ghost.values[GVAL_SPELL_4] = MS_SUMMON_DEMON;

        /* at least they can summon demons */
        if (ghost.values[17] == SPELL_NO_SPELL)
            ghost.values[GVAL_SPELL_4] = MS_SUMMON_DEMON;

        if (one_chance_in(15))
            ghost.values[GVAL_SPELL_5] = MS_DIG;
    }
}                               // end generate_random_demon()

// Largest string we'll save
#define STR_CAP 1000

using std::string;

void writeShort(FILE *file, short s) {
    char data[2];
    // High byte first - network order
    data[0] = (char)((s >> 8) & 0xFF);
    data[1] = (char)(s & 0xFF);

    write2(file, data, sizeof(data));
}

short readShort(FILE *file) {
    unsigned char data[2];
    read2(file, (char *) data, 2);

    // High byte first
    return (((short) data[0]) << 8) | (short) data[1];
}

void writeByte(FILE *file, unsigned char byte) {
    write2(file, (char *) &byte, sizeof byte);
}

unsigned char readByte(FILE *file) {
    unsigned char byte;
    read2(file, (char *) &byte, sizeof byte);
    return byte;
}

void writeString(FILE* file, const string &s) {
    int length = s.length();
    if (length > STR_CAP) length = STR_CAP;
    writeShort(file, length);
    write2(file, s.c_str(), length);
}

string readString(FILE *file) {
    char buf[STR_CAP + 1];
    short length = readShort(file);
    if (length)
        read2(file, buf, length);
    buf[length] = '\0';
    return string(buf);
}

void writeLong(FILE* file, long num) {
    // High word first, network order
    writeShort(file, (short) ((num >> 16) & 0xFFFFL));
    writeShort(file, (short) (num & 0xFFFFL));
}

long readLong(FILE *file) {
    return ((long) readShort(file)) << 16 | (long) readShort(file);
}
