/*
* File: files.cc
* Summary: Functions used to save and load levels/games.
* Written by: Linley Henzell and Alexey Guzeev
*/
#include "AppHdr.h"
#include "delay.h"
#include "files.h"
#include "mon-place.h"
#include "coord.h"
#include <errno.h>
#include <string.h>
#include <string>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <algorithm>
#include <functional>
#ifdef TARGET_OS_DOS
#include <file.h>
#endif
#ifdef UNIX
#include <fcntl.h>
#include <unistd.h>
#endif
#ifdef TARGET_COMPILER_MINGW
#include <io.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include "externs.h"
#include "artefact.h"
#include "chardump.h"
#include "cloud.h"
#include "clua.h"
#include "coordit.h"
#include "debug.h"
#include "directn.h"
#include "dungeon.h"
#include "effects.h"
#include "env.h"
#include "ghost.h"
#include "initfile.h"
#include "items.h"
#include "jobs.h"
#include "kills.h"
#include "libutil.h"
#include "mapmark.h"
#include "message.h"
#include "misc.h"
#include "mon-act.h"
#include "mon-stuff.h"
#include "mon-util.h"
#include "mon-transit.h"
#include "newgame.h"
#include "notes.h"
#include "options.h"
#include "output.h"
#include "overmap.h"
#include "place.h"
#include "player.h"
#include "random.h"
#include "stash.h"
#include "state.h"
#include "stuff.h"
#include "tags.h"
#ifdef USE_TILE
#include "tiles.h"
#include "tiledef-player.h"
#endif
#include "terrain.h"
#include "travel.h"
#include "tutorial.h"
#include "view.h"
#include "viewgeom.h"
#ifdef TARGET_COMPILER_VC
#include <direct.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <dirent.h>
#endif
#ifndef HAVE_STAT
#if defined(UNIX) || defined(TARGET_COMPILER_MINGW) || defined(TARGET_OS_DOS)
#define HAVE_STAT
#endif
#endif
static std::vector<SavefileCallback::callback>* _callback_list = NULL;
static void _save_level( int level_saved, level_area_type lt,
branch_type where_were_you);
static bool _get_and_validate_version( FILE *restoreFile, char& major,
char& minor, std::string* reason = 0);
static bool _determine_ghost_version( FILE *ghostFile,
char &majorVersion, char &minorVersion );
static void _restore_ghost_version( FILE *ghostFile, char major, char minor );
static void _restore_tagged_file( FILE *restoreFile, int fileType,
char minorVersion );
const short GHOST_SIGNATURE = short( 0xDC55 );
static void _redraw_all(void)
{
you.redraw_hit_points = true;
you.redraw_magic_points = true;
you.redraw_strength = true;
you.redraw_intelligence = true;
you.redraw_dexterity = true;
you.redraw_armour_class = true;
you.redraw_evasion = true;
you.redraw_experience = true;
you.redraw_status_flags =
REDRAW_LINE_1_MASK | REDRAW_LINE_2_MASK | REDRAW_LINE_3_MASK;
}
static std::string _uid_as_string()
{
#ifdef MULTIUSER
return make_stringf("-%d", static_cast<int>(getuid()));
#else
return std::string();
#endif
}
static bool _is_uid_file(const std::string &name, const std::string &ext)
{
std::string save_suffix = get_savedir_filename("", "", "");
save_suffix += ext;
#ifdef TARGET_OS_DOS
// Grumble grumble. Hang all retarded operating systems.
uppercase(save_suffix);
#endif
save_suffix = save_suffix.substr(Options.save_dir.length());
std::string::size_type suffix_pos = name.find(save_suffix);
return (suffix_pos != std::string::npos
&& suffix_pos == name.length() - save_suffix.length()
&& suffix_pos != 0);
}
bool is_save_file_name(const std::string &name)
{
return _is_uid_file(name, ".sav");
}
#ifdef LOAD_UNPACKAGE_CMD
bool is_packed_save(const std::string &name)
{
return _is_uid_file(name, PACKAGE_SUFFIX);
}
#endif
// Returns the save_info from the save.
player_save_info read_character_info(const std::string &savefile)
{
player_save_info fromfile;
FILE *charf = fopen(savefile.c_str(), "rb");
if (!charf)
return fromfile;
char majorVersion;
char minorVersion;
if (_get_and_validate_version(charf, majorVersion, minorVersion))
{
// Backup before we clobber "you".
const player backup(you);
_restore_tagged_file(charf, TAGTYPE_PLAYER_NAME, minorVersion);
fromfile = you;
you.copy_from(backup);
}
fclose(charf);
return fromfile;
}
static inline bool _is_good_filename(const std::string &s)
{
return (s != "." && s != "..");
}
#if defined(TARGET_OS_DOS)
// Abbreviates a given file name to DOS style "xxxxxx~1.txt".
// Does not take into account files with differing suffixes or files
// with a prepended path with more than one separator.
// (It does handle all files included with the distribution except
// changes.stone_soup.)
bool get_dos_compatible_file_name(std::string *fname)
{
std::string::size_type pos1 = fname->find("\\");
if (pos1 == std::string::npos)
pos1 = 0;
const std::string::size_type pos2 = fname->find(".txt");
// Name already fits DOS requirements, nothing to be done.
if (fname->substr(pos1, pos2).length() <= 8)
return (false);
*fname = fname->substr(0,pos1) + fname->substr(pos1, pos1 + 6) + "~1.txt";
return (true);
}
#endif
// Returns the names of all files in the given directory. Note that the
// filenames returned are relative to the directory.
std::vector<std::string> get_dir_files(const std::string &dirname)
{
std::vector<std::string> files;
#ifdef TARGET_COMPILER_VC
WIN32_FIND_DATA lData;
std::string dir = dirname;
if (!dir.empty() && dir[dir.length() - 1] != FILE_SEPARATOR)
dir += FILE_SEPARATOR;
dir += "*";
HANDLE hFind = FindFirstFile(dir.c_str(), &lData);
if (hFind != INVALID_HANDLE_VALUE)
{
if (_is_good_filename(lData.cFileName))
files.push_back(lData.cFileName);
while (FindNextFile(hFind, &lData))
{
if (_is_good_filename(lData.cFileName))
files.push_back(lData.cFileName);
}
FindClose(hFind);
}
#else // non-MS VC++ compilers
DIR *dir = opendir(dirname.c_str());
if (!dir)
return (files);
while (dirent *entry = readdir(dir))
{
std::string name = entry->d_name;
if (_is_good_filename(name))
files.push_back(name);
}
closedir(dir);
#endif
return (files);
}
std::vector<std::string> get_dir_files_ext(const std::string &dir,
const std::string &ext)
{
const std::vector<std::string> allfiles(get_dir_files(dir));
std::vector<std::string> filtered;
for (int i = 0, size = allfiles.size(); i < size; ++i)
if (ends_with(allfiles[i], ext))
filtered.push_back(allfiles[i]);
return (filtered);
}
std::string get_parent_directory(const std::string &filename)
{
std::string::size_type pos = filename.rfind(FILE_SEPARATOR);
if (pos != std::string::npos)
return filename.substr(0, pos + 1);
#ifdef ALT_FILE_SEPARATOR
pos = filename.rfind(ALT_FILE_SEPARATOR);
if (pos != std::string::npos)
return filename.substr(0, pos + 1);
#endif
return ("");
}
std::string get_base_filename(const std::string &filename)
{
std::string::size_type pos = filename.rfind(FILE_SEPARATOR);
if (pos != std::string::npos)
return filename.substr(pos + 1);
#ifdef ALT_FILE_SEPARATOR
pos = filename.rfind(ALT_FILE_SEPARATOR);
if (pos != std::string::npos)
return filename.substr(pos + 1);
#endif
return (filename);
}
bool is_absolute_path(const std::string &path)
{
return (!path.empty()
&& (path[0] == FILE_SEPARATOR
#ifdef TARGET_OS_WINDOWS
|| path.find(':') != std::string::npos
#endif
));
}
// Concatenates two paths, separating them with FILE_SEPARATOR if necessary.
// Assumes that the second path is not absolute.
//
// If the first path is empty, returns the second unchanged. The second path
// may be absolute in this case.
std::string catpath(const std::string &first, const std::string &second)
{
if (first.empty())
return (second);
std::string directory = first;
if (directory[directory.length() - 1] != FILE_SEPARATOR)
directory += FILE_SEPARATOR;
directory += second;
return (directory);
}
// Given a relative path and a reference file name, returns the relative path
// suffixed to the directory containing the reference file name. Assumes that
// the second path is not absolute.
std::string get_path_relative_to(const std::string &referencefile,
const std::string &relativepath)
{
return catpath(get_parent_directory(referencefile),
relativepath);
}
std::string change_file_extension(const std::string &filename,
const std::string &ext)
{
const std::string::size_type pos = filename.rfind('.');
return ((pos == std::string::npos? filename : filename.substr(0, pos))
+ ext);
}
time_t file_modtime(const std::string &file)
{
struct stat filestat;
if (stat(file.c_str(), &filestat))
return (0);
return (filestat.st_mtime);
}
// Returns true if file a is newer than file b.
bool is_newer(const std::string &a, const std::string &b)
{
return (file_modtime(a) > file_modtime(b));
}
void check_newer(const std::string &target,
const std::string &dependency,
void (*action)())
{
if (is_newer(dependency, target))
action();
}
bool file_exists(const std::string &name)
{
#ifdef HAVE_STAT
struct stat st;
const int err = ::stat(name.c_str(), &st);
return (!err);
#else
FILE *f = fopen(name.c_str(), "r");
const bool exists = !!f;
if (f)
fclose(f);
return (exists);
#endif
}
// Low-tech existence check.
bool dir_exists(const std::string &dir)
{
#ifdef TARGET_COMPILER_VC
DWORD lAttr = GetFileAttributes(dir.c_str());
return (lAttr != INVALID_FILE_ATTRIBUTES
&& (lAttr & FILE_ATTRIBUTE_DIRECTORY));
#elif defined(HAVE_STAT)
struct stat st;
const int err = ::stat(dir.c_str(), &st);
return (!err && S_ISDIR(st.st_mode));
#else
DIR *d = opendir(dir.c_str());
const bool exists = !!d;
if (d)
closedir(d);
return (exists);
#endif
}
static int _create_directory(const char *dir)
{
#if defined(MULTIUSER)
return mkdir(dir, SHARED_FILES_CHMOD_PUBLIC | 0111);
#elif defined(TARGET_OS_DOS)
return mkdir(dir, 0755);
#elif defined(TARGET_COMPILER_VC)
return _mkdir(dir);
#else
return mkdir(dir);
#endif
}
static bool _create_dirs(const std::string &dir)
{
std::string sep = " ";
sep[0] = FILE_SEPARATOR;
std::vector<std::string> segments =
split_string(
sep.c_str(),
dir,
false,
false);
std::string path;
for (int i = 0, size = segments.size(); i < size; ++i)
{
path += segments[i];
// Handle absolute paths correctly.
if (i == 0 && dir.size() && dir[0] == FILE_SEPARATOR)
path = FILE_SEPARATOR + path;
if (!dir_exists(path) && _create_directory(path.c_str()))
return (false);
path += FILE_SEPARATOR;
}
return (true);
}
// Checks whether the given path is safe to read from. A path is safe if:
// 1. If Unix: It contains no shell metacharacters.
// 2. If DATA_DIR_PATH is set: the path is not an absolute path.
// 3. If DATA_DIR_PATH is set: the path contains no ".." sequence.
void assert_read_safe_path(const std::string &path) throw (std::string)
{
// Check for rank tomfoolery first:
if (path.empty())
throw "Empty file name.";
#ifdef UNIX
if (!shell_safe(path.c_str()))
throw make_stringf("\"%s\" contains bad characters.",
path.c_str());
#endif
#ifdef DATA_DIR_PATH
if (is_absolute_path(path))
throw make_stringf("\"%s\" is an absolute path.", path.c_str());
if (path.find("..") != std::string::npos)
{
throw make_stringf("\"%s\" contains \"..\" sequences.",
path.c_str());
}
#endif
// Path is okay.
}
bool is_read_safe_path(const std::string &path)
{
try
{
assert_read_safe_path(path);
}
catch (const std::string &)
{
return (false);
}
return (true);
}
std::string canonicalise_file_separator(const std::string &path)
{
#if FILE_SEPARATOR != '/'
return (replace_all_of(path, "/", std::string(1, FILE_SEPARATOR)));
#else
// No action needed here.
return (path);
#endif
}
std::string datafile_path(std::string basename,
bool croak_on_fail,
bool test_base_path,
bool (*thing_exists)(const std::string&))
{
basename = canonicalise_file_separator(basename);
if (test_base_path && thing_exists(basename))
return (basename);
const std::string rawbases[] = {
#ifdef DATA_DIR_PATH
DATA_DIR_PATH,
#else
!SysEnv.crawl_dir.empty()? SysEnv.crawl_dir : "",
#endif
#ifdef TARGET_OS_MACOSX
SysEnv.crawl_base + "../Resources/",
#endif
};
const std::string prefixes[] = {
std::string("dat") + FILE_SEPARATOR,
#ifdef USE_TILE
std::string("dat/tiles") + FILE_SEPARATOR,
#endif
std::string("docs") + FILE_SEPARATOR,
std::string("settings") + FILE_SEPARATOR,
#ifndef DATA_DIR_PATH
std::string("..") + FILE_SEPARATOR + "docs" + FILE_SEPARATOR,
std::string("..") + FILE_SEPARATOR + "dat" + FILE_SEPARATOR,
#ifdef USE_TILE
std::string("..") + FILE_SEPARATOR + "dat/tiles" + FILE_SEPARATOR,
#endif
std::string("..") + FILE_SEPARATOR + "settings" + FILE_SEPARATOR,
std::string("..") + FILE_SEPARATOR,
#endif
"",
};
std::vector<std::string> bases;
for (unsigned i = 0; i < sizeof(rawbases) / sizeof(*rawbases); ++i)
{
std::string base = rawbases[i];
if (base.empty())
continue;
if (base[base.length() - 1] != FILE_SEPARATOR)
base += FILE_SEPARATOR;
bases.push_back(base);
}
#ifndef DATA_DIR_PATH
if (!SysEnv.crawl_base.empty())
bases.push_back(SysEnv.crawl_base);
bases.push_back("");
#endif
for (unsigned b = 0, size = bases.size(); b < size; ++b)
for (unsigned p = 0; p < sizeof(prefixes) / sizeof(*prefixes); ++p)
{
std::string name = bases[b] + prefixes[p] + basename;
if (thing_exists(name))
return (name);
}
// Die horribly.
if (croak_on_fail)
{
end(1, false, "Cannot find data file '%s' anywhere, aborting\n",
basename.c_str());
}
return ("");
}
bool check_dir(const std::string &whatdir, std::string &dir, bool silent)
{
if (dir.empty())
return (true);
const std::string sep(1, FILE_SEPARATOR);
dir = replace_all_of(dir, "/", sep);
dir = replace_all_of(dir, "\\", sep);
// Suffix the separator if necessary
if (dir[dir.length() - 1] != FILE_SEPARATOR)
dir += FILE_SEPARATOR;
if (!dir_exists(dir) && !_create_dirs(dir))
{
if (!silent)
fprintf(stderr, "%s \"%s\" does not exist "
"and I can't create it.\n",
whatdir.c_str(), dir.c_str());
return (false);
}
return (true);
}
// Given a simple (relative) name of a save file, returns the full path of
// the file in the Crawl saves directory. You can use path segments in
// shortpath (separated by /) and get_savedir_path will canonicalise them
// to the platform's native file separator.
std::string get_savedir_path(const std::string &shortpath)
{
const std::string file = Options.save_dir + shortpath;
#if FILE_SEPARATOR != '/'
return (replace_all(file, "/", std::string(1, FILE_SEPARATOR)));
#else
return (file);
#endif
}
#ifdef USE_TILE
static void _fill_player_doll(player_save_info &p, const std::string &dollfile)
{
dolls_data equip_doll;
for (unsigned int j = 0; j < TILEP_PART_MAX; ++j)
equip_doll.parts[j] = TILEP_SHOW_EQUIP;
equip_doll.parts[TILEP_PART_BASE]
= tilep_species_to_base_tile(p.species, p.experience_level);
bool success = false;
FILE *fdoll = fopen(dollfile.c_str(), "r");
if (fdoll)
{
char fbuf[1024];
memset(fbuf, 0, sizeof(fbuf));
if (fscanf(fdoll, "%s", fbuf) != EOF)
{
tilep_scan_parts(fbuf, equip_doll);
tilep_race_default(p.species,
get_gender_from_tile(equip_doll.parts),
p.experience_level,
equip_doll.parts);
success = true;
while (fscanf(fdoll, "%s", fbuf) != EOF)
{
if (strcmp(fbuf, "net") == 0)
p.held_in_net = true;
}
}
fclose(fdoll);
}
if (!success) // Use default doll instead.
{
job_type job = get_class_by_name(p.class_name.c_str());
if (job == -1)
job = JOB_FIGHTER;
int gender = coinflip();
tilep_job_default(job, gender, equip_doll.parts);
}
p.doll = equip_doll;
}
#endif
/*
* Returns a list of the names of characters that are already saved for the
* current user.
*/
std::vector<player_save_info> find_saved_characters()
{
std::vector<player_save_info> chars;
#ifndef DISABLE_SAVEGAME_LISTS
std::string searchpath = Options.save_dir;
if (searchpath.empty())
searchpath = ".";
std::vector<std::string> allfiles = get_dir_files(searchpath);
for (unsigned int i = 0; i < allfiles.size(); ++i)
{
std::string filename = allfiles[i];
std::string::size_type point_pos = filename.find_last_of('.');
std::string basename = filename.substr(0, point_pos);
#ifdef LOAD_UNPACKAGE_CMD
if (!is_packed_save(filename))
continue;
std::string zipname = get_savedir_path(basename);
escape_path_spaces(zipname);
// This is the filename we actually read ourselves.
filename = basename + ".sav";
escape_path_spaces(filename);
std::string dir = get_savedir();
escape_path_spaces(dir);
char cmd_buff[1024];
snprintf( cmd_buff, sizeof(cmd_buff), UNPACK_SPECIFIC_FILE_CMD,
zipname.c_str(),
dir.c_str(),
filename.c_str() );
if (system(cmd_buff) != 0)
continue;
#endif
if (is_save_file_name(filename))
{
const std::string path = get_savedir_path(filename);
player_save_info p = read_character_info(path);
if (!p.name.empty())
{
#ifdef USE_TILE
if (Options.tile_menu_icons)
{
#ifndef LOAD_UNPACKAGE_CMD
basename = filename.substr(0,
filename.length() - strlen(".sav"));
#endif
std::string dollname = basename + ".tdl";
const std::string dollpath = get_savedir_path(dollname);
#ifdef LOAD_UNPACKAGE_CMD
escape_path_spaces(dollname);
snprintf( cmd_buff, sizeof(cmd_buff),
UNPACK_SPECIFIC_FILE_CMD,
zipname.c_str(),
dir.c_str(),
dollname.c_str() );
system(cmd_buff);
#endif
_fill_player_doll(p, dollpath);
#ifdef LOAD_UNPACKAGE_CMD
// Throw away doll file.
if (file_exists(dollpath.c_str()))
unlink( dollpath.c_str() );
#endif
}
#endif
chars.push_back(p);
}
}
#ifdef LOAD_UNPACKAGE_CMD
// If we unpacked the .sav file, throw it away now.
unlink( get_savedir_path(filename).c_str() );
#endif
}
std::sort(chars.rbegin(), chars.rend());
#endif // !DISABLE_SAVEGAME_LISTS
return (chars);
}
std::string get_savedir()
{
const std::string &dir = Options.save_dir;
return (dir.empty() ? "." : dir);
}
std::string get_savedir_filename(const std::string &prefix,
const std::string &suffix,
const std::string &extension,
bool suppress_uid)
{
std::string result = Options.save_dir;
result += get_save_filename(prefix, suffix, extension, suppress_uid);
#ifdef TARGET_OS_DOS
uppercase(result);
#endif
return result;
}
std::string get_save_filename(const std::string &prefix,
const std::string &suffix,
const std::string &extension,
bool suppress_uid)
{
std::string result = "";
// Shorten string as appropriate
result += strip_filename_unsafe_chars(prefix).substr(0, kFileNameLen);
// Technically we should shorten the string first. But if
// MULTIUSER is set we'll have long filenames anyway. Caveat
// emptor.
if ( !suppress_uid )
result += _uid_as_string();
result += suffix;
if (!extension.empty())
{
result += '.';
result += extension;
}
#ifdef TARGET_OS_DOS
uppercase(result);
#endif
return result;
}
std::string get_prefs_filename()
{
#ifdef DGL_STARTUP_PREFS_BY_NAME
return get_savedir_filename("start-" + Options.player_name + "-",
"ns", "prf", true);
#else
return get_savedir_filename("start", "ns", "prf");
#endif
}
static std::string _get_level_suffix(int level, branch_type where,
level_area_type lt)
{
switch (lt)
{
default:
case LEVEL_DUNGEON:
return (make_stringf("%02d%c", subdungeon_depth(where, level),
where + 'a'));
case LEVEL_LABYRINTH:
return ("lab");
case LEVEL_ABYSS:
return ("abs");
case LEVEL_PANDEMONIUM:
return ("pan");
case LEVEL_PORTAL_VAULT:
return ("ptl");
}
}
std::string make_filename(std::string prefix, int level, branch_type where,
level_area_type ltype, bool isGhost)
{
return get_savedir_filename(prefix, "",
_get_level_suffix(level, where, ltype),
isGhost );
}
static void _write_version( FILE *dataFile, int majorVersion, int minorVersion,
bool extended_version )
{
// write version
writer outf(dataFile);
marshallByte(outf, majorVersion);
marshallByte(outf, minorVersion);
// extended_version just pads the version out to four 32-bit words.
// This makes the bones file compatible with Hearse with no extra
// munging needed.
if (extended_version)
{
// Use a single signature 16-bit word to indicate that this is
// Stone Soup and to disambiguate this (unmunged) bones file
// from the munged bones files offered by the old Crawl-aware
// hearse.pl. Crawl-aware hearse.pl will prefix the bones file
// with the first 16-bits of the Crawl version, and the following
// 7 16-bit words set to 0.
marshallShort(outf, GHOST_SIGNATURE);
// Write the three remaining 32-bit words of padding.
for (int i = 0; i < 3; ++i)
marshallLong(outf, 0);
}
}
static void _write_tagged_file( FILE *outf, int fileType,
bool extended_version = false )
{
// find all relevant tags
char tags[NUM_TAGS];
tag_set_expected(tags, fileType);
_write_version( outf, TAG_MAJOR_VERSION, TAG_MINOR_VERSION,
extended_version );
// all other tags
for (int i = 1; i < NUM_TAGS; i++)
if (tags[i] == 1)
tag_write(static_cast<tag_type>(i), outf);
}
bool travel_load_map( branch_type branch, int absdepth )
{
// Try to open level savefile.
FILE *levelFile = fopen(make_filename(you.your_name, absdepth, branch,
LEVEL_DUNGEON, false).c_str(), "rb");
if (!levelFile)
return (false);
char majorVersion;
char minorVersion;
if (!_get_and_validate_version( levelFile, majorVersion, minorVersion ))
{
fclose(levelFile);
return (false);
}
tag_read(levelFile, minorVersion);
fclose( levelFile );
return (true);
}
static void _place_player_on_stair(level_area_type old_level_type,
branch_type old_branch,
int stair_taken)
{
bool find_first = true;
// Order is important here.
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_EXIT_HELL)
{
stair_taken = DNGN_ENTER_HELL;
}
else if (stair_taken == DNGN_ENTER_HELL)
{
// The vestibule and labyrinth always start from this stair.
stair_taken = DNGN_EXIT_HELL;
}
else if (stair_taken == DNGN_EXIT_PORTAL_VAULT
|| ((old_level_type == LEVEL_LABYRINTH
|| old_level_type == LEVEL_PORTAL_VAULT)
&& (stair_taken == DNGN_ESCAPE_HATCH_DOWN
|| stair_taken == DNGN_ESCAPE_HATCH_UP)))
{
stair_taken = DNGN_EXIT_PORTAL_VAULT;
}
else if (stair_taken >= DNGN_STONE_STAIRS_DOWN_I
&& stair_taken <= DNGN_ESCAPE_HATCH_DOWN)
{
// Look for corresponding 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_ESCAPE_HATCH_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_FIRST_BRANCH
&& stair_taken < 150) // 20 slots reserved
{
// Find entry point to subdungeon when leaving.
stair_taken += (DNGN_ENTER_FIRST_BRANCH
- DNGN_RETURN_FROM_FIRST_BRANCH);
}
else if (stair_taken >= DNGN_ENTER_FIRST_BRANCH
&& stair_taken < DNGN_RETURN_FROM_FIRST_BRANCH)
{
// Find exit staircase from subdungeon when entering.
stair_taken += (DNGN_RETURN_FROM_FIRST_BRANCH
- DNGN_ENTER_FIRST_BRANCH);
}
else if (stair_taken >= DNGN_ENTER_DIS
&& stair_taken <= DNGN_ENTER_TARTARUS)
{
// Only when entering a hell - when exiting, go back to the
// entry stair.
if (player_in_hell())
stair_taken = DNGN_STONE_STAIRS_UP_I;
}
else if (stair_taken == DNGN_EXIT_ABYSS)
{
stair_taken = DNGN_STONE_STAIRS_UP_I;
}
else if (stair_taken == DNGN_ENTER_PORTAL_VAULT)
{
stair_taken = DNGN_STONE_ARCH;
}
else if (stair_taken == DNGN_ENTER_LABYRINTH)
{
// dgn_find_nearby_stair uses special logic for labyrinths.
stair_taken = DNGN_ENTER_LABYRINTH;
}
else // Note: stair_taken can equal things like DNGN_FLOOR
{
// Just find a nice empty square.
stair_taken = DNGN_FLOOR;
find_first = false;
}
const coord_def where_to_go =
dgn_find_nearby_stair(static_cast<dungeon_feature_type>(stair_taken),
you.pos(), find_first);
you.moveto(where_to_go);
}
static void _close_level_gates()
{
for (rectangle_iterator ri(0); ri; ++ri)
{
if (you.char_direction == GDT_ASCENDING
&& you.level_type != LEVEL_PANDEMONIUM)
{
if (feat_sealable_portal(grd(*ri)))
{
grd(*ri) = DNGN_STONE_ARCH;
env.markers.remove_markers_at(*ri, MAT_ANY);
}
}
}
}
static void _clear_env_map()
{
env.map_knowledge.init(map_cell());
}
static void _clear_clouds()
{
for (int clouty = 0; clouty < MAX_CLOUDS; ++clouty)
delete_cloud( clouty );
env.cgrid.init(EMPTY_CLOUD);
}
static bool _grab_follower_at(const coord_def &pos)
{
if (pos == you.pos())
return (false);
monsters *fmenv = monster_at(pos);
if (!fmenv || !fmenv->alive())
return (false);
// The monster has to already be tagged in order to follow.
if (!testbits(fmenv->flags, MF_TAKING_STAIRS))
return (false);
level_id dest = level_id::current();
if (you.char_direction == GDT_GAME_START)
dest.depth = 1;
#if DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "%s is following to %s.",
fmenv->name(DESC_CAP_THE, true).c_str(),
dest.describe().c_str());
#endif
fmenv->set_transit(dest);
fmenv->destroy_inventory();
monster_cleanup(fmenv);
return (true);
}
static void _grab_followers()
{
const bool can_follow = level_type_allows_followers(you.level_type);
int non_stair_using_allies = 0;
monsters *dowan = NULL;
monsters *duvessa = NULL;
// Handle nearby ghosts.
for (adjacent_iterator ai(you.pos()); ai; ++ai)
{
monsters *fmenv = monster_at(*ai);
if (fmenv == NULL)
continue;
if ((fmenv->type == MONS_DUVESSA
|| (fmenv->props.exists("original_name")
&& fmenv->props["original_name"].get_string() == "Duvessa"))
&& fmenv->alive())
{
duvessa = fmenv;
}
if ((fmenv->type == MONS_DOWAN
|| (fmenv->props.exists("original_name")
&& fmenv->props["original_name"].get_string() == "Dowan"))
&& fmenv->alive())
{
dowan = fmenv;
}
if (fmenv->wont_attack() && !mons_can_use_stairs(fmenv))
non_stair_using_allies++;
if (fmenv->type == MONS_PLAYER_GHOST
&& fmenv->hit_points < fmenv->max_hit_points / 2)
{
if (fmenv->visible_to(&you))
mpr("The ghost fades into the shadows.");
monster_teleport(fmenv, true);
}
}
// Deal with Dowan and Duvessa here.
if (dowan && duvessa)
{
if (!testbits(dowan->flags, MF_TAKING_STAIRS)
|| !testbits(duvessa->flags, MF_TAKING_STAIRS))
{
dowan->flags &= ~MF_TAKING_STAIRS;
duvessa->flags &= ~MF_TAKING_STAIRS;
}
}
else if (dowan && !duvessa)
{
if (!dowan->props.exists("can_climb"))
dowan->flags &= ~MF_TAKING_STAIRS;
}
else if (!dowan && duvessa)
{
if (!duvessa->props.exists("can_climb"))
duvessa->flags &= ~MF_TAKING_STAIRS;
}
if (can_follow)
{
if (non_stair_using_allies > 0)
{
// XXX: This assumes that the only monsters that are
// incapable of using stairs are zombified.
mprf("Your mindless thrall%s stay%s behind.",
non_stair_using_allies > 1 ? "s" : "",
non_stair_using_allies > 1 ? "" : "s");
}
memset(travel_point_distance, 0, sizeof(travel_distance_grid_t));
std::vector<coord_def> places[2];
int place_set = 0;
places[place_set].push_back(you.pos());
while (!places[place_set].empty())
{
for (int i = 0, size = places[place_set].size(); i < size; ++i)
{
const coord_def &p = places[place_set][i];
coord_def fp;
for (fp.x = p.x - 1; fp.x <= p.x + 1; ++fp.x)
for (fp.y = p.y - 1; fp.y <= p.y + 1; ++fp.y)
{
if (!in_bounds(fp) || travel_point_distance[fp.x][fp.y])
continue;
travel_point_distance[fp.x][fp.y] = 1;
if (_grab_follower_at(fp))
places[!place_set].push_back(fp);
}
}
places[place_set].clear();
place_set = !place_set;
}
}
// Clear flags on the followers that didn't make it.
for (int i = 0; i < MAX_MONSTERS; ++i)
{
monsters *mons = &menv[i];
if (!mons->alive())
continue;
mons->flags &= ~MF_TAKING_STAIRS;
}
}
// Should be called after _grab_followers(), so that items carried by
// followers won't be considered lost.
static void _do_lost_items(level_area_type old_level_type)
{
if (old_level_type == LEVEL_DUNGEON)
return;
for (int i = 0; i < MAX_ITEMS; i++)
{
item_def& item(mitm[i]);
if (!item.is_valid())
continue;
// Item is in player inventory, so it's not lost.
if (item.pos == coord_def(-1,-1))
continue;
item_was_lost(item);
}
}
bool load( dungeon_feature_type stair_taken, load_mode_type load_mode,
level_area_type old_level_type, char old_level,
branch_type old_branch )
{
unwind_var<dungeon_feature_type> stair(
you.transit_stair, stair_taken, DNGN_UNSEEN);
unwind_bool ylev(you.entering_level, true, false);
#ifdef DEBUG_LEVEL_LOAD
mprf(MSGCH_DIAGNOSTICS, "Loading... level type: %d, branch: %d, level: %d",
you.level_type, you.where_are_you, you.your_level);
#endif
// Going up/down stairs, going through a portal, or being banished
// means the previous x/y movement direction is no longer valid.
you.reset_prev_move();
#ifdef USE_TILE
you.last_clicked_grid = coord_def();
#endif
const bool make_changes =
(load_mode != LOAD_RESTART_GAME && load_mode != LOAD_VISITOR);
bool just_created_level = false;
std::string cha_fil = make_filename( you.your_name, you.your_level,
you.where_are_you,
you.level_type,
false );
if (you.level_type == LEVEL_DUNGEON && old_level_type == LEVEL_DUNGEON
|| load_mode == LOAD_START_GAME && you.char_direction != GDT_GAME_START)
{
const level_id current(level_id::current());
if (Generated_Levels.find(current) == Generated_Levels.end())
{
// Make sure the old file is gone.
unlink(cha_fil.c_str());
// Save the information for later deletion -- DML 6/11/99
Generated_Levels.insert(current);
}
}
you.prev_targ = MHITNOT;
you.prev_grd_targ.reset();
// We clear twice - on save and on load.
// Once would be enough...
if (make_changes)
_clear_clouds();
// Lose all listeners.
dungeon_events.clear();
// This block is to grab followers and save the old level to disk.
if (load_mode == LOAD_ENTER_LEVEL && old_level != -1)
{
_grab_followers();
if (old_level_type == LEVEL_DUNGEON
|| old_level_type != you.level_type)
{
_save_level( old_level, old_level_type, old_branch );
}
}
if (make_changes)
_do_lost_items(old_level_type);
#ifdef USE_TILE
if (load_mode != LOAD_VISITOR)
{
tiles.clear_minimap();
tiles.load_dungeon(NULL, crawl_view.vgrdc);
}
#endif
// Try to open level savefile.
#ifdef DEBUG_LEVEL_LOAD
mprf(MSGCH_DIAGNOSTICS, "Try to open file %s", cha_fil.c_str());
#endif
FILE *levelFile = fopen(cha_fil.c_str(), "rb");
// GENERATE new level when the file can't be opened:
if (levelFile == NULL)
{
#ifdef DEBUG_LEVEL_LOAD
mpr("Generating new file...", MSGCH_DIAGNOSTICS);
#endif
ASSERT( load_mode != LOAD_VISITOR );
env.turns_on_level = -1;
if (you.char_direction == GDT_GAME_START
&& you.level_type == LEVEL_DUNGEON)
{
// If we're leaving the Abyss for the first time as a Chaos
// Knight of Lugonu (who start out there), force a return
// into the first dungeon level and enable normal monster
// generation.
you.your_level = 0;
you.char_direction = GDT_DESCENDING;
Generated_Levels.insert(level_id::current());
}
#ifdef USE_TILE
tile_init_default_flavour();
tile_clear_flavour();
#endif
_clear_env_map();
builder(you.your_level, you.level_type);
just_created_level = true;
if ((you.your_level > 1 || you.level_type != LEVEL_DUNGEON)
&& one_chance_in(3))
{
load_ghost(true);
}
env.turns_on_level = 0;
// sanctuary
env.sanctuary_pos = coord_def(-1, -1);
env.sanctuary_time = 0;
}
else
{
#ifdef DEBUG_LEVEL_LOAD
mpr("Loading old file...", MSGCH_DIAGNOSTICS);
#endif
// BEGIN -- must load the old level : pre-load tasks
// LOAD various tags
char majorVersion;
char minorVersion;
std::string reason;
if (!_get_and_validate_version( levelFile, majorVersion, minorVersion,
&reason ))
{
end(-1, false, "\nLevel file is invalid. %s\n",
reason.c_str());
}
_restore_tagged_file(levelFile, TAGTYPE_LEVEL, minorVersion);
// Sanity check - EOF
if (!feof( levelFile ))
{
end(-1, false, "\nIncomplete read of \"%s\" - aborting.\n",
cha_fil.c_str());
}
fclose( levelFile );
// POST-LOAD tasks :
link_items();
_redraw_all();
}
if (load_mode == LOAD_START_GAME)
just_created_level = true;
// Closes all the gates if you're on the way out.
if (you.char_direction == GDT_ASCENDING
&& you.level_type != LEVEL_PANDEMONIUM)
{
_close_level_gates();
}
// Here's the second cloud clearing, on load (see above).
if (make_changes)
{
_clear_clouds();
if (you.level_type != LEVEL_ABYSS)
_place_player_on_stair(old_level_type, old_branch, stair_taken);
else
you.moveto(coord_def(45, 35)); // FIXME: should be abyss_center
}
crawl_view.set_player_at(you.pos(), load_mode != LOAD_VISITOR);
// This should fix the "monster occurring under the player" bug?
if (make_changes && monster_at(you.pos()))
monster_teleport(monster_at(you.pos()), true, true);
// Actually "move" the followers if applicable.
if (level_type_allows_followers(you.level_type)
&& load_mode == LOAD_ENTER_LEVEL)
{
place_followers();
}
// Load monsters in transit.
if (load_mode == LOAD_ENTER_LEVEL)
{
place_transiting_monsters();
place_transiting_items();
}
if (load_mode != LOAD_VISITOR)
{
// Tell stash-tracker and travel that we've changed levels.
trackers_init_new_level(true);
#ifdef USE_TILE
TileNewLevel(just_created_level);
#endif
}
_redraw_all();
if (load_mode != LOAD_VISITOR)
dungeon_events.fire_event(DET_ENTERING_LEVEL);
// Things to update for player entering level
if (load_mode == LOAD_ENTER_LEVEL)
{
if (just_created_level)
level_welcome_messages();
// Activate markers that want activating, but only when
// entering a new level in an existing game. If we're starting
// a new game, or reloading an existing game,
// markers are activated in acr.cc.
env.markers.activate_all();
// Draw view window. update_level may cause monsters to
// act and hence be redrawn.
viewwindow(false, true);
// update corpses and fountains
if (env.elapsed_time != 0.0 && !just_created_level)
update_level( you.elapsed_time - env.elapsed_time );
// Centaurs have difficulty with stairs
int timeval = ((you.species != SP_CENTAUR) ? player_movement_speed()
: 15);
// new levels have less wary monsters:
if (just_created_level)
timeval /= 2;
timeval -= (stepdown_value( check_stealth(), 50, 50, 150, 150 ) / 10);
dprf("arrival time: %d", timeval );
if (timeval > 0)
{
you.time_taken = timeval;
handle_monsters();
}
}
// Save the created/updated level out to disk:
if (make_changes)
_save_level( you.your_level, you.level_type, you.where_are_you );
setup_environment_effects();
setup_vault_mon_list();
setup_feature_descs_short();
// Inform user of level's annotation.
if (load_mode != LOAD_VISITOR
&& !get_level_annotation().empty()
&& !crawl_state.level_annotation_shown)
{
mprf(MSGCH_PLAIN, YELLOW, "Level annotation: %s",
get_level_annotation().c_str());
}
if (load_mode != LOAD_VISITOR)
crawl_state.level_annotation_shown = false;
if (make_changes)
{
// Update PlaceInfo entries
PlaceInfo& curr_PlaceInfo = you.get_place_info();
PlaceInfo delta;
if (load_mode == LOAD_START_GAME
|| (load_mode == LOAD_ENTER_LEVEL
&& (old_branch != you.where_are_you
|| old_level_type != you.level_type)))
{
delta.num_visits++;
}
if (just_created_level)
delta.levels_seen++;
you.global_info += delta;
#ifdef DEBUG_LEVEL_LOAD
mprf(MSGCH_DIAGNOSTICS,
"global_info:: num_visits: %d, levels_seen: %d",
you.global_info.num_visits, you.global_info.levels_seen);
#endif
you.global_info.assert_validity();
curr_PlaceInfo += delta;
#ifdef DEBUG_LEVEL_LOAD
mprf(MSGCH_DIAGNOSTICS,
"curr_PlaceInfo:: num_visits: %d, levels_seen: %d",
curr_PlaceInfo.num_visits, curr_PlaceInfo.levels_seen);
#endif
curr_PlaceInfo.assert_validity();
}
if (just_created_level)
you.attribute[ATTR_ABYSS_ENTOURAGE] = 0;
if (load_mode != LOAD_VISITOR)
dungeon_events.fire_event(DET_ENTERED_LEVEL);
if (load_mode == LOAD_ENTER_LEVEL)
{
// 50% chance of repelling the stair you just came through.
if (you.duration[DUR_REPEL_STAIRS_MOVE]
|| you.duration[DUR_REPEL_STAIRS_CLIMB])
{
dungeon_feature_type feat = grd(you.pos());
if (feat != DNGN_ENTER_SHOP
&& feat_stair_direction(feat) != CMD_NO_CMD
&& feat_stair_direction(stair_taken) != CMD_NO_CMD)
{
std::string stair_str =
feature_description(feat, NUM_TRAPS, false,
DESC_CAP_THE, false);
std::string verb = stair_climb_verb(feat);
if (coinflip()
&& slide_feature_over(you.pos(), coord_def(-1, -1), false))
{
mprf("%s slides away from you right after you %s it!",
stair_str.c_str(), verb.c_str());
}
if (coinflip())
{
// Stairs stop fleeing from you now you actually caught one.
mprf("%s settles down.", stair_str.c_str(), verb.c_str());
you.duration[DUR_REPEL_STAIRS_MOVE] = 0;
you.duration[DUR_REPEL_STAIRS_CLIMB] = 0;
}
}
}
// If butchering was interrupted by switching levels (banishment)
// then switch back from butchering tool if there's no hostiles
// nearby.
handle_interrupted_swap(true);
// Forget about interrupted butchering, since we probably aren't going
// to get back to the corpse in time to finish things.
// But do not reset the weapon swap if we swapped weapons
// because of a transformation.
maybe_clear_weapon_swap();
}
return just_created_level;
}
void _save_level(int level_saved, level_area_type old_ltype,
branch_type where_were_you)
{
std::string cha_fil = make_filename( you.your_name, level_saved,
where_were_you, old_ltype,
false );
you.prev_targ = MHITNOT;
you.prev_grd_targ.reset();
FILE *saveFile = fopen(cha_fil.c_str(), "wb");
if (saveFile == NULL)
{
end(-1, true, "Unable to open \"%s\" for writing",
cha_fil.c_str());
}
// Nail all items to the ground.
fix_item_coordinates();
_write_tagged_file( saveFile, TAGTYPE_LEVEL );
fclose(saveFile);
DO_CHMOD_PRIVATE(cha_fil.c_str());
}
// Stack allocated std::string's go in seperate function, so Valgrind doesn't
// complain.
static void _save_game_base()
{
/* Stashes */
std::string stashFile = get_savedir_filename(you.your_name, "", "st");
FILE *stashf = fopen(stashFile.c_str(), "wb");
if (stashf)
{
writer outf(stashf);
StashTrack.save(outf);
fclose(stashf);
DO_CHMOD_PRIVATE(stashFile.c_str());
}
#ifdef CLUA_BINDINGS
/* lua */
std::string luaFile = get_savedir_filename(you.your_name, "", "lua");
clua.save(luaFile.c_str());
// Note that luaFile may not exist.
DO_CHMOD_PRIVATE(luaFile.c_str());
#endif
/* kills */
std::string killFile = get_savedir_filename(you.your_name, "", "kil");
FILE *killf = fopen(killFile.c_str(), "wb");
if (killf)
{
writer outf(killf);
you.kills->save(outf);
fclose(killf);
DO_CHMOD_PRIVATE(killFile.c_str());
}
/* travel cache */
std::string travelCacheFile = get_savedir_filename(you.your_name,"","tc");
FILE *travelf = fopen(travelCacheFile.c_str(), "wb");
if (travelf)
{
writer outf(travelf);
travel_cache.save(outf);
fclose(travelf);
DO_CHMOD_PRIVATE(travelCacheFile.c_str());
}
/* notes */
std::string notesFile = get_savedir_filename(you.your_name, "", "nts");
FILE *notesf = fopen(notesFile.c_str(), "wb");
if (notesf)
{
writer outf(notesf);
save_notes(outf);
fclose(notesf);
DO_CHMOD_PRIVATE(notesFile.c_str());
}
/* tutorial */
std::string tutorFile = get_savedir_filename(you.your_name, "", "tut");
FILE *tutorf = fopen(tutorFile.c_str(), "wb");
if (tutorf)
{
writer outf(tutorf);
save_tutorial(outf);
fclose(tutorf);
DO_CHMOD_PRIVATE(tutorFile.c_str());
}
/* messages */
std::string msgFile = get_savedir_filename(you.your_name, "", "msg");
FILE *msgf = fopen(msgFile.c_str(), "wb");
if (msgf)
{
writer outf(msgf);
save_messages(outf);
fclose(msgf);
DO_CHMOD_PRIVATE(msgFile.c_str());
}
/* tile dolls (empty for ASCII)*/
std::string dollFile = get_savedir_filename(you.your_name, "", "tdl");
#ifdef USE_TILE
// Save the current equipment into a file.
FILE *dollf = fopen(dollFile.c_str(), "w+");
if (dollf)
{
save_doll_file(dollf);
fclose(dollf);
DO_CHMOD_PRIVATE(dollFile.c_str());
}
#endif
std::string charFile = get_savedir_filename(you.your_name, "", "sav");
FILE *charf = fopen(charFile.c_str(), "wb");
if (!charf)
end(-1, true, "Unable to open \"%s\" for writing!\n", charFile.c_str());
_write_tagged_file( charf, TAGTYPE_PLAYER );
fclose(charf);
DO_CHMOD_PRIVATE(charFile.c_str());
}
// Stack allocated std::string's go in seperate function, so Valgrind doesn't
// complain.
static void _save_game_exit()
{
// Must be exiting -- save level & goodbye!
if (!you.entering_level)
_save_level(you.your_level, you.level_type, you.where_are_you);
clrscr();
#ifdef SAVE_PACKAGE_CMD
std::string dirname = get_savedir();
escape_path_spaces(dirname);
std::string basename = get_save_filename(you.your_name, "", "");
escape_path_spaces(basename);
char cmd_buff[1024];
#ifdef USE_TAR
snprintf( cmd_buff, sizeof(cmd_buff),
"cd %s && "SAVE_PACKAGE_CMD" -zcf %s"PACKAGE_SUFFIX" --remove-files %s.*",
dirname.c_str(), basename.c_str(), basename.c_str());
#else
# ifdef USE_ZIP
snprintf( cmd_buff, sizeof(cmd_buff),
SAVE_PACKAGE_CMD" %s"PACKAGE_SUFFIX" %s.*",
(dirname+basename).c_str(), (dirname+basename).c_str());
# else
No save package defined.
#endif
#endif
if (system( cmd_buff ) != 0)
{
cprintf( EOL "Warning: Zip command (SAVE_PACKAGE_CMD) returned "
"non-zero value!" EOL );
}
DO_CHMOD_PRIVATE ( (dirname + basename + PACKAGE_SUFFIX).c_str() );
#endif
#ifdef DGL_WHEREIS
whereis_record("saved");
#endif
if (_callback_list != NULL)
{
delete _callback_list;
_callback_list = NULL;
}
}
void save_game(bool leave_game, const char *farewellmsg)
{
unwind_bool saving_game(crawl_state.saving_game, true);
SavefileCallback::pre_save();
// Stack allocated std::string's go in seperate function,
// so Valgrind doesn't complain.
_save_game_base();
// If just save, early out.
if (!leave_game)
return;
// Stack allocated std::string's go in seperate function,
// so Valgrind doesn't complain.
_save_game_exit();
end(0, false, farewellmsg? "%s" : "See you soon, %s!",
farewellmsg? farewellmsg : you.your_name.c_str());
} // end save_game()
// Saves the game without exiting.
void save_game_state()
{
save_game(false);
if (crawl_state.seen_hups)
save_game(true);
}
static std::string _make_portal_vault_ghost_suffix()
{
return you.level_type_ext.empty()? "ptl" : you.level_type_ext;
}
static std::string _make_ghost_filename()
{
if (you.level_type == LEVEL_PORTAL_VAULT)
{
const std::string suffix = _make_portal_vault_ghost_suffix();
return get_savedir_filename("bones", "", suffix, true);
}
else
{
return make_filename("bones", you.your_level, you.where_are_you,
you.level_type, true);
}
}
#define BONES_DIAGNOSTICS (WIZARD | DEBUG_BONES | DEBUG_DIAGNOSTICS)
bool load_ghost(bool creating_level)
{
const bool wiz_cmd = (crawl_state.prev_cmd == CMD_WIZARD);
ASSERT(you.transit_stair == DNGN_UNSEEN || creating_level);
ASSERT(!you.entering_level || creating_level);
ASSERT(!creating_level
|| (you.entering_level && you.transit_stair != DNGN_UNSEEN));
// Only way to load a ghost without creating a level is via a wizard
// command.
ASSERT(creating_level || wiz_cmd);
#if !DEBUG && !WIZARD
UNUSED(creating_level);
#endif
#if BONES_DIAGNOSTICS
bool do_diagnostics = false;
#if WIZARD
do_diagnostics = !creating_level;
#endif
#if DEBUG_BONES | DEBUG_DIAGNOSTICS
do_diagnostics = true;
#endif
#endif // BONES_DIAGNOSTICS
char majorVersion;
char minorVersion;
const std::string cha_fil = _make_ghost_filename();
FILE *gfile = fopen(cha_fil.c_str(), "rb");
if (gfile == NULL)
{
if (wiz_cmd && !creating_level)
mpr("No ghost files for this level.", MSGCH_PROMPT);
return (false); // no such ghost.
}
if (!_determine_ghost_version(gfile, majorVersion, minorVersion))
{
fclose(gfile);
#if BONES_DIAGNOSTICS
if (do_diagnostics)
{
mprf(MSGCH_DIAGNOSTICS,
"Ghost file \"%s\" seems to be invalid.", cha_fil.c_str());
more();
}
#endif
return (false);
}
if (majorVersion != TAG_MAJOR_VERSION || minorVersion > TAG_MINOR_VERSION)
{
#if BONES_DIAGNOSTICS
if (do_diagnostics)
{
if (majorVersion != TAG_MAJOR_VERSION)
mprf(MSGCH_DIAGNOSTICS,
"Ghost file major version mismatch");
else
mprf(MSGCH_DIAGNOSTICS,
"Ghost file minor version is too high.");
more();
}
#endif
fclose(gfile);
unlink(cha_fil.c_str());
return (false);
}
ghosts.clear();
_restore_ghost_version(gfile, majorVersion, minorVersion);
// Sanity check - EOF.
if (!feof(gfile))
{
fclose(gfile);
#if BONES_DIAGNOSTICS
if (do_diagnostics)
{
mprf(MSGCH_DIAGNOSTICS, "Incomplete read of \"%s\".",
cha_fil.c_str() );
more();
}
#endif
return (false);
}
fclose(gfile);
// FIXME: This message will have to be shortened again as trunk reaches
// 0.6 state and players using old bones becomes increasingly less
// likely.
if (!debug_check_ghosts())
{
mprf(MSGCH_DIAGNOSTICS,
"Refusing to load buggy ghost from file \"%s\"! "
"Note that all bones files from 0.4.x are invalid, so you should "
"delete them. If this is a newer ghost, please submit a bug "
"report.",
cha_fil.c_str());
return (false);
}
#if BONES_DIAGNOSTICS
if (do_diagnostics)
{
mprf(MSGCH_DIAGNOSTICS, "Loaded ghost file with %lu ghost(s)",
ghosts.size());
}
#endif
// Remove bones file - ghosts are hardly permanent.
unlink(cha_fil.c_str());
#if BONES_DIAGNOSTICS
unsigned long unplaced_ghosts = ghosts.size();
bool ghost_errors = false;
#endif
// Translate ghost to monster and place.
for (int imn = 0; imn < MAX_MONSTERS - 10 && !ghosts.empty(); ++imn)
{
if (menv[imn].alive())
continue;
menv[imn].set_ghost(ghosts[0]);
menv[imn].ghost_init();
ghosts.erase(ghosts.begin());
#if BONES_DIAGNOSTICS
if (do_diagnostics)
{
unplaced_ghosts--;
if (!menv[imn].alive())
{
mpr("Placed ghost is not alive.", MSGCH_DIAGNOSTICS);
ghost_errors = true;
}
else if (menv[imn].type != MONS_PLAYER_GHOST)
{
mprf(MSGCH_DIAGNOSTICS,
"Placed ghost is not MONS_PLAYER_GHOST, but %s",
menv[imn].name(DESC_PLAIN, true).c_str());
ghost_errors = true;
}
}
#endif
}
#if BONES_DIAGNOSTICS
if (do_diagnostics && unplaced_ghosts > 0)
{
mprf(MSGCH_DIAGNOSTICS, "Unable to place %lu ghost(s)",
ghosts.size());
ghost_errors = true;
}
if (ghost_errors)
more();
#endif
return (true);
}
void restore_game(void)
{
std::string charFile = get_savedir_filename(you.your_name, "", "sav");
FILE *charf = fopen(charFile.c_str(), "rb");
if (!charf )
end(-1, true, "Unable to open %s for reading!\n", charFile.c_str() );
char majorVersion;
char minorVersion;
std::string reason;
if (!_get_and_validate_version(charf, majorVersion, minorVersion, &reason))
end(-1, false, "\nSave file %s is invalid. %s\n", charFile.c_str(), reason.c_str());
_restore_tagged_file(charf, TAGTYPE_PLAYER, minorVersion);
// Sanity check - EOF
if (!feof(charf))
{
end(-1, false, "\nIncomplete read of \"%s\" - aborting.\n",
charFile.c_str());
}
fclose(charf);
std::string stashFile = get_savedir_filename( you.your_name, "", "st" );
FILE *stashf = fopen(stashFile.c_str(), "rb");
if (stashf)
{
reader inf(stashf, minorVersion);
StashTrack.load(inf);
fclose(stashf);
}
#ifdef CLUA_BINDINGS
std::string luaFile = get_savedir_filename( you.your_name, "", "lua" );
clua.execfile( luaFile.c_str() );
#endif
std::string killFile = get_savedir_filename( you.your_name, "", "kil" );
FILE *killf = fopen(killFile.c_str(), "rb");
if (killf)
{
reader inf(killf, minorVersion);
you.kills->load(inf);
fclose(killf);
}
std::string travelCacheFile = get_savedir_filename(you.your_name,"","tc");
FILE *travelf = fopen(travelCacheFile.c_str(), "rb");
if (travelf)
{
reader inf(travelf, minorVersion);
travel_cache.load(inf, minorVersion);
fclose(travelf);
}
std::string notesFile = get_savedir_filename(you.your_name, "", "nts");
FILE *notesf = fopen(notesFile.c_str(), "rb");
if (notesf)
{
reader inf(notesf, minorVersion);
load_notes(inf);
fclose(notesf);
}
/* tutorial */
std::string tutorFile = get_savedir_filename(you.your_name, "", "tut");
FILE *tutorf = fopen(tutorFile.c_str(), "rb");
if (tutorf)
{
reader inf(tutorf, minorVersion);
load_tutorial(inf);
fclose(tutorf);
}
/* messages */
std::string msgFile = get_savedir_filename(you.your_name, "", "msg");
FILE *msgf = fopen(msgFile.c_str(), "rb");
if (msgf)
{
reader inf(msgf, minorVersion);
load_messages(inf);
fclose(msgf);
}
SavefileCallback::post_restore();
}
static void _restore_level(const level_id &original)
{
// Reload the original level.
you.where_are_you = original.branch;
you.your_level = original.dungeon_absdepth();
you.level_type = original.level_type;
load( DNGN_STONE_STAIRS_DOWN_I, LOAD_VISITOR,
you.level_type, you.your_level, you.where_are_you );
// Rebuild the show grid, which was cleared out before.
you.update_los();
}
// Given a level returns true if the level has been created already
// in this game.
bool is_existing_level(const level_id &level)
{
return (Generated_Levels.find(level) != Generated_Levels.end());
}
// This class provides a way to walk the dungeon with a bit more flexibility
// than you get with apply_to_all_dungeons.
level_excursion::level_excursion()
: original(level_id::current())
{
}
void level_excursion::go_to(const level_id& next)
{
if (level_id::current() != next)
{
_save_level(you.your_level, you.level_type, you.where_are_you);
_restore_level(next);
LevelInfo &li = travel_cache.get_level_info(next);
li.set_level_excludes();
}
you.on_current_level = (level_id::current() == original);
}
level_excursion::~level_excursion()
{
go_to(original);
}
// Applies an operation (applicator) after switching to the specified level.
// Will reload the original level after modifying the target level.
//
// If the target level has not already been visited by the player, this
// function will assert.
bool apply_to_level(const level_id &level, bool (*applicator)())
{
ASSERT(is_existing_level(level));
level_excursion le;
le.go_to(level);
return applicator();
}
bool apply_to_all_dungeons(bool (*applicator)())
{
level_excursion le;
level_id original(level_id::current());
bool success = applicator();
for (int i = 0; i < MAX_LEVELS; ++i)
for (int j = 0; j < NUM_BRANCHES; ++j)
{
const branch_type br = static_cast<branch_type>(j);
const level_id thislevel(br, subdungeon_depth(br, i));
if (!is_existing_level(thislevel))
continue;
// Don't apply to the original level - already done up top.
if (original == thislevel)
continue;
le.go_to(thislevel);
if (applicator())
success = true;
}
return (success);
}
bool get_save_version(FILE *file, char &major, char &minor)
{
// Read first two bytes.
char buf[2];
if (read2(file, buf, 2) != 2)
{
// Empty file?
major = minor = -1;
return (false);
}
major = buf[0];
minor = buf[1];
return (true);
}
static bool _get_and_validate_version(FILE *restoreFile, char &major,
char &minor, std::string* reason)
{
std::string dummy;
if (reason == 0)
reason = &dummy;
if (!get_save_version(restoreFile, major, minor))
{
*reason = "File is corrupt.";
return (false);
}
if (major != TAG_MAJOR_VERSION)
{
*reason = make_stringf("Major version mismatch: %d (want %d).",
major, TAG_MAJOR_VERSION);
return (false);
}
if (minor < 0)
{
*reason = make_stringf("Minor version %d is negative!",
minor);
return (false);
}
if (minor > TAG_MINOR_VERSION)
{
*reason = make_stringf("Minor version mismatch: %d (want <= %d).",
minor, TAG_MINOR_VERSION);
return (false);
}
return (true);
}
static void _restore_tagged_file( FILE *restoreFile, int fileType,
char minorVersion )
{
char tags[NUM_TAGS];
tag_set_expected(tags, fileType);
while (true)
{
tag_type tt = tag_read(restoreFile, minorVersion);
if (tt == TAG_NO_TAG)
break;
tags[tt] = 0; // tag read
if (fileType == TAGTYPE_PLAYER_NAME)
break;
}
// Go through and init missing tags.
for (int i = 0; i < NUM_TAGS; i++)
if (tags[i] == 1) // expected but never read
tag_missing(i, minorVersion);
}
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?
// Otherwise, read version and validate.
majorVersion = buf[0];
minorVersion = buf[1];
reader inf(ghostFile, minorVersion);
// Check for the DCSS ghost signature.
if (unmarshallShort(inf) != GHOST_SIGNATURE)
return (false);
if (majorVersion == TAG_MAJOR_VERSION
&& minorVersion <= TAG_MINOR_VERSION)
{
// Discard three more 32-bit words of padding.
inf.read(NULL, 3*4);
return !feof(ghostFile);
}
// If its not TAG_MAJOR_VERSION, no idea!
return (false);
}
static void _restore_ghost_version( FILE *ghostFile,
char majorVersion, char minorVersion )
{
switch (majorVersion)
{
case TAG_MAJOR_VERSION:
_restore_tagged_file(ghostFile, TAGTYPE_GHOST, minorVersion);
break;
default:
break;
}
}
void save_ghost( bool force )
{
#if BONES_DIAGNOSTICS
bool do_diagnostics = false;
#if WIZARD
do_diagnostics = you.wizard;
#endif
#if DEBUG_BONES | DEBUG_DIAGNOSTICS
do_diagnostics = true;
#endif
#endif // BONES_DIAGNOSTICS
// No ghosts on levels 1, 2, or the ET.
if (!force && (you.your_level < 2
|| you.where_are_you == BRANCH_ECUMENICAL_TEMPLE))
{
return;
}
const std::string cha_fil = _make_ghost_filename();
FILE *gfile = fopen(cha_fil.c_str(), "rb");
// Don't overwrite existing bones!
if (gfile != NULL)
{
#if BONES_DIAGNOSTICS
if (do_diagnostics)
mpr("Ghost file for this level already exists.",
MSGCH_DIAGNOSTICS);
#endif
fclose(gfile);
return;
}
ghosts = ghost_demon::find_ghosts();
if (ghosts.empty())
{
#if BONES_DIAGNOSTICS
if (do_diagnostics)
mpr("Could not find any ghosts for this level.",
MSGCH_DIAGNOSTICS);
#endif
return;
}
gfile = lk_open("wb", cha_fil);
if (gfile == NULL)
{
mprf(MSGCH_ERROR, "Error creating ghost file: %s", cha_fil.c_str());
more();
return;
}
_write_tagged_file( gfile, TAGTYPE_GHOST, true );
lk_close(gfile, "wb", cha_fil);
#if BONES_DIAGNOSTICS
if (do_diagnostics)
mprf(MSGCH_DIAGNOSTICS, "Saved ghost (%s).", cha_fil.c_str() );
#endif
DO_CHMOD_PRIVATE(cha_fil.c_str());
}
////////////////////////////////////////////////////////////////////////////
// Locking for multiuser systems
// first, some file locking stuff for multiuser crawl
#ifdef USE_FILE_LOCKING
bool lock_file_handle( FILE *handle, int type )
{
struct flock lock;
int status;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
lock.l_type = type;
#ifdef USE_BLOCKING_LOCK
status = fcntl( fileno( handle ), F_SETLKW, &lock );
#else
for (int i = 0; i < 30; i++)
{
status = fcntl( fileno( handle ), F_SETLK, &lock );
// success
if (status == 0)
break;
// known failure
if (status == -1 && (errno != EACCES && errno != EAGAIN))
break;
perror( "Problems locking file... retrying..." );
delay( 1000 );
}
#endif
return (status == 0);
}
bool unlock_file_handle( FILE *handle )
{
struct flock lock;
int status;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
lock.l_type = F_UNLCK;
#ifdef USE_BLOCKING_LOCK
status = fcntl( fileno( handle ), F_SETLKW, &lock );
#else
for (int i = 0; i < 30; i++)
{
status = fcntl( fileno( handle ), F_SETLK, &lock );
// success
if (status == 0)
break;
// known failure
if (status == -1 && (errno != EACCES && errno != EAGAIN))
break;
perror( "Problems unlocking file... retrying..." );
delay( 1000 );
}
#endif
return (status == 0);
}
#endif
FILE *lk_open(const char *mode, const std::string &file)
{
FILE *handle = fopen(file.c_str(), mode);
#ifdef SHARED_FILES_CHMOD_PUBLIC
chmod(file.c_str(), SHARED_FILES_CHMOD_PUBLIC);
#endif
#ifdef USE_FILE_LOCKING
int locktype = F_RDLCK;
if (mode && mode[0] != 'r')
locktype = F_WRLCK;
if (handle && !lock_file_handle( handle, locktype ))
{
perror( "Could not lock file... " );
fclose( handle );
handle = NULL;
}
#endif
return handle;
}
void lk_close(FILE *handle, const char *mode, const std::string &file)
{
UNUSED( mode );
if (handle == NULL || handle == stdin)
return;
#ifdef USE_FILE_LOCKING
unlock_file_handle( handle );
#endif
// actually close
fclose(handle);
#ifdef SHARED_FILES_CHMOD_PUBLIC
if (mode && mode[0] == 'w')
chmod(file.c_str(), SHARED_FILES_CHMOD_PUBLIC);
#endif
}
/////////////////////////////////////////////////////////////////////////////
// file_lock
//
// Locks a named file (usually an empty lock file), creating it if necessary.
file_lock::file_lock(const std::string &s, const char *_mode, bool die_on_fail)
: handle(NULL), mode(_mode), filename(s)
{
#ifdef USE_FILE_LOCKING
if (!(handle = lk_open(mode, filename)) && die_on_fail)
end(1, true, "Unable to open lock file \"%s\"", filename.c_str());
#endif
}
file_lock::~file_lock()
{
#ifdef USE_FILE_LOCKING
if (handle)
lk_close(handle, mode, filename);
#endif
}
/////////////////////////////////////////////////////////////////////////////
// SavefileCallback
//
// Callbacks which are called before a save and after a restore. Can be used
// to move stuff in and out of you.props, or on a restore to recalculate data
// which isn't stored in the savefile. Declare a SavefileCallback variable
// using a C++ global constructor to register the callback.
//
// XXX: Due to some weirdness with C++ global constructors (see below) I'm
// not sure if this will work for all compiler/system combos, so make any
// code which uses this fail gracefully if the callbacks aren't called.
SavefileCallback::SavefileCallback(callback func)
{
ASSERT(func != NULL);
// XXX: For some reason (at least with GCC 4.3.2 on Linux) if the
// callback list is made with a global contructor then it gets emptied
// out by the time that pre_save() or post_restore() is called,
// probably having something to do with the fact that global
// contructors are also used to add the callbacks. Thus we have to do
// it this way.
if (_callback_list == NULL)
_callback_list = new std::vector<SavefileCallback::callback>();
_callback_list->push_back(func);
}
void SavefileCallback::pre_save()
{
ASSERT(crawl_state.saving_game);
if (_callback_list == NULL)
return;
for (unsigned int i = 0; i < _callback_list->size(); i++)
{
callback func = (*_callback_list)[i];
(*func)(true);
}
}
void SavefileCallback::post_restore()
{
ASSERT(!crawl_state.saving_game);
if (_callback_list == NULL)
return;
for (unsigned int i = 0; i < _callback_list->size(); i++)
{
callback func = (*_callback_list)[i];
(*func)(false);
}
}