OSDN Git Service

Merge branch 'develop' into macos-develop
[hengbandforosx/hengbandosx.git] / src / main / angband-initializer.cpp
1 /*!
2  * @file angband-initializer.cpp
3  * @brief 変愚蛮怒のシステム初期化
4  * @date 2014/01/28
5  * @author
6  * <pre>
7  * Copyright (c) 1997 Ben Harrison, James E. Wilson, Robert A. Koeneke
8  * This software may be copied and distributed for educational, research,
9  * and not for profit purposes provided that this copyright and statement
10  * are included in all such copies.  Other copyrights may also apply.
11  * 2014 Deskull rearranged comment for Doxygen.\n
12  * </pre>
13  */
14
15 #include "main/angband-initializer.h"
16 #include "dungeon/quest.h"
17 #include "floor/wild.h"
18 #include "info-reader/feature-reader.h"
19 #include "io/files-util.h"
20 #include "io/read-pref-file.h"
21 #include "io/uid-checker.h"
22 #include "main/game-data-initializer.h"
23 #include "main/info-initializer.h"
24 #include "market/building-initializer.h"
25 #include "monster-race/monster-race.h"
26 #include "monster-race/race-flags7.h"
27 #include "system/angband-version.h"
28 #include "system/dungeon-info.h"
29 #include "system/monster-race-info.h"
30 #include "system/system-variables.h"
31 #include "term/gameterm.h"
32 #include "term/screen-processor.h"
33 #include "term/term-color-types.h"
34 #include "time.h"
35 #include "util/angband-files.h"
36 #include "util/string-processor.h"
37 #include "world/world.h"
38 #include <vector>
39 #include <chrono>
40
41 /*!
42  * @brief 古いデバッグ用セーブファイルを削除する
43  *
44  * 最終更新日時が現在時刻より7日以前のデバッグ用セーブファイルを削除する。
45  * デバッグ用セーブファイルは、ANGBAND_DIR_DEBUG_SAVEディレクトリにある、
46  * ファイル名に '-' を含むファイルであることを想定する。
47  */
48 static void remove_old_debug_savefiles()
49 {
50     namespace fs = std::filesystem;
51     constexpr auto remove_threshold_days = std::chrono::days(7);
52     const auto now = fs::file_time_type::clock::now();
53
54     // アクセスエラーが発生した場合に例外が送出されないようにするため
55     // 例外を送出せず引数でエラーコードを返すオーバーロードを使用する。
56     // アクセスエラーが発生した場合は単に無視し、エラーコードの確認は行わない。
57     std::error_code ec;
58
59     for (const auto &entry : fs::directory_iterator(ANGBAND_DIR_DEBUG_SAVE, ec)) {
60         const auto &path = entry.path();
61         if (path.filename().string().find('-') == std::string::npos) {
62             continue;
63         }
64
65         const auto savefile_timestamp = fs::last_write_time(path);
66         const auto elapsed_days = std::chrono::duration_cast<std::chrono::days>(now - savefile_timestamp);
67         if (elapsed_days >= remove_threshold_days) {
68             fs::remove(path, ec);
69         }
70     }
71 }
72
73 /*!
74  * @brief 各データファイルを読み取るためのパスを取得する.
75  * @param libpath 各PCのインストール環境における"lib/" を表す絶対パス
76  * @param varpath Is the base path for directories that have files which
77  * are not read-only: ANGBAND_DIR_APEX, ANGBAND_DIR_BONE, ANGBAND_DIR_DATA,
78  * and ANGBAND_DIR_SAVE.  If the PRIVATE_USER_PATH preprocessor macro has
79  * not been set, it is also used as the base path for ANGBAND_DIR_USER.
80  */
81 void init_file_paths(const std::filesystem::path &libpath, const std::filesystem::path &varpath)
82 {
83     ANGBAND_DIR = std::filesystem::path(libpath);
84     ANGBAND_DIR_APEX = std::filesystem::path(varpath).append("apex");
85     ANGBAND_DIR_BONE = std::filesystem::path(varpath).append("bone");
86     ANGBAND_DIR_DATA = std::filesystem::path(varpath).append("data");
87     ANGBAND_DIR_EDIT = std::filesystem::path(libpath).append("edit");
88     ANGBAND_DIR_SCRIPT = std::filesystem::path(libpath).append("script");
89     ANGBAND_DIR_FILE = std::filesystem::path(libpath).append("file");
90     ANGBAND_DIR_HELP = std::filesystem::path(libpath).append("help");
91     ANGBAND_DIR_INFO = std::filesystem::path(libpath).append("info");
92     ANGBAND_DIR_PREF = std::filesystem::path(libpath).append("pref");
93     ANGBAND_DIR_SAVE = std::filesystem::path(varpath).append("save");
94     ANGBAND_DIR_DEBUG_SAVE = std::filesystem::path(ANGBAND_DIR_SAVE).append("log");
95 #ifdef PRIVATE_USER_PATH
96     ANGBAND_DIR_USER = path_parse(PRIVATE_USER_PATH).append(VARIANT_NAME);
97 #else
98     ANGBAND_DIR_USER = std::filesystem::path(varpath).append("user");
99 #endif
100     ANGBAND_DIR_XTRA = std::filesystem::path(libpath).append("xtra");
101
102     time_t now = time(nullptr);
103     struct tm *t = localtime(&now);
104     char tmp[128];
105     strftime(tmp, sizeof(tmp), "%Y-%m-%d-%H-%M-%S", t);
106     debug_savefile = path_build(ANGBAND_DIR_DEBUG_SAVE, tmp);
107     remove_old_debug_savefiles();
108 }
109
110 /*
111  * Helper function for create_needed_dirs().  Copied over from PosChengband.
112  */
113 static bool dir_exists(const std::filesystem::path path)
114 {
115     struct stat buf;
116     if (stat(path.native().data(), &buf) != 0)
117         return false;
118 #ifdef WIN32
119     else if (buf.st_mode & S_IFDIR)
120 #else
121     else if (S_ISDIR(buf.st_mode))
122 #endif
123         return true;
124     else
125         return false;
126 }
127
128
129 /*
130  * Helper function for create_needed_dirs().  Copied over from PosChengband
131  * but use the global definition for the path separator rather than a local
132  * one in PosChengband's code and check for paths that end with the path
133  * separator.
134  */
135 static bool dir_create(const std::filesystem::path path)
136 {
137 #ifdef WIN32
138     /* If the directory already exists then we're done */
139     if (dir_exists(path)) return true;
140     return false;
141 #else
142     std::vector<std::filesystem::path> missing;
143     std::filesystem::path next_path = path;
144
145     while (1) {
146         if (dir_exists(next_path)) {
147             break;
148         }
149         missing.push_back(next_path);
150         if (!next_path.has_relative_path()) {
151                 break;
152         }
153         next_path = next_path.parent_path();
154     }
155     for (; !missing.empty(); missing.pop_back()) {
156         if (mkdir(missing.back().native().data(), 0755) != 0) {
157             return false;
158         }
159     }
160     return true;
161 #endif
162 }
163
164
165 /*
166  * Create any missing directories. We create only those dirs which may be
167  * empty (user/, save/, apex/, bone/, data/). Only user/ is created when
168  * the PRIVATE_USER_PATH preprocessor maccro has been set. The others are
169  * assumed to contain required files and therefore must exist at startup
170  * (edit/, pref/, file/, xtra/).
171  *
172  * ToDo: Only create the directories when actually writing files.
173  * Copied over from PosChengband to support main-cocoa.m.  Dropped
174  * creation of help/ (and removed it and info/ in the comment)
175  * since init_file_paths() puts those in libpath which may not be writable
176  * by the user running the application.  Added bone/ since
177  * init_file_paths() puts that in varpath.
178  */
179 void create_needed_dirs(void)
180 {
181     if (!dir_create(ANGBAND_DIR_USER)) quit_fmt("Cannot create '%s'", ANGBAND_DIR_USER.native().data());
182 #ifndef PRIVATE_USER_PATH
183     if (!dir_create(ANGBAND_DIR_SAVE)) quit_fmt("Cannot create '%s'", ANGBAND_DIR_SAVE.native().data());
184     if (!dir_create(ANGBAND_DIR_DEBUG_SAVE)) quit_fmt("Cannot create '%s'", ANGBAND_DIR_DEBUG_SAVE.native().data());
185     if (!dir_create(ANGBAND_DIR_APEX)) quit_fmt("Cannot create '%s'", ANGBAND_DIR_APEX.native().data());
186     if (!dir_create(ANGBAND_DIR_BONE)) quit_fmt("Cannot create '%s'", ANGBAND_DIR_BONE.native().data());
187     if (!dir_create(ANGBAND_DIR_DATA)) quit_fmt("Cannot create '%s'", ANGBAND_DIR_DATA.native().data());
188 #endif /* ndef PRIVATE_USER_PATH */
189 }
190
191 /*!
192  * @brief 画面左下にシステムメッセージを表示する / Take notes on line 23
193  * @param str 初期化中のコンテンツ文字列
194  */
195 static void init_note_term(concptr str)
196 {
197     term_erase(0, 23);
198     term_putstr(20, 23, -1, TERM_WHITE, str);
199     term_fresh();
200 }
201
202 /*!
203  * @brief ゲーム画面無しの時の初期化メッセージ出力
204  * @param str 初期化中のコンテンツ文字列
205  */
206 static void init_note_no_term(concptr str)
207 {
208     /* Don't show initialization message when there is no game terminal. */
209     (void)str;
210 }
211
212 /*!
213  * @brief 全ゲームデータ読み込みのサブルーチン / Explain a broken "lib" folder and quit (see below).
214  * @param なし
215  * @note
216  * <pre>
217  * This function is "messy" because various things
218  * may or may not be initialized, but the "plog()" and "quit()"
219  * functions are "supposed" to work under any conditions.
220  * </pre>
221  */
222 static void init_angband_aux(const std::string &why)
223 {
224     plog(why.data());
225     plog(_("'lib'ディレクトリが存在しないか壊れているようです。", "The 'lib' directory is probably missing or broken."));
226     plog(_("ひょっとするとアーカイブが正しく解凍されていないのかもしれません。", "The 'lib' directory is probably missing or broken."));
227     plog(_("該当する'README'ファイルを読んで確認してみて下さい。", "See the 'README' file for more information."));
228     quit(_("致命的なエラー。", "Fatal Error."));
229 }
230
231 /*!
232  * @brief タイトル記述
233  * @param なし
234  */
235 static void put_title()
236 {
237     const auto title = get_version();
238
239     auto col = (title.length() <= MAIN_TERM_MIN_COLS) ? (MAIN_TERM_MIN_COLS - title.length()) / 2 : 0;
240     constexpr auto VER_INFO_ROW = 3; //!< タイトル表記(行)
241     prt(title, VER_INFO_ROW, col);
242 }
243
244 /*!
245  * @brief 全ゲームデータ読み込みのメインルーチン /
246  * @param player_ptr プレイヤーへの参照ポインタ
247  * @param no_term TRUEならゲーム画面無しの状態で初期化を行う。
248  *                コマンドラインからスポイラーの出力のみを行う時の使用を想定する。
249  */
250 void init_angband(PlayerType *player_ptr, bool no_term)
251 {
252     const auto &path_news = path_build(ANGBAND_DIR_FILE, _("news_j.txt", "news.txt"));
253     auto fd = fd_open(path_news, O_RDONLY);
254     if (fd < 0) {
255         std::string why = _("'", "Cannot access the '");
256         why.append(path_news.string());
257         why.append(_("'ファイルにアクセスできません!", "' file!"));
258         init_angband_aux(why);
259     }
260
261     (void)fd_close(fd);
262     if (!no_term) {
263         term_clear();
264         auto *fp = angband_fopen(path_news, FileOpenMode::READ);
265         if (fp) {
266             int i = 0;
267             char buf[1024]{};
268             while (0 == angband_fgets(fp, buf, sizeof(buf))) {
269                 term_putstr(0, i++, -1, TERM_WHITE, buf);
270             }
271
272             angband_fclose(fp);
273         }
274
275         term_flush();
276     }
277
278     const auto &path_score = path_build(ANGBAND_DIR_APEX, "scores.raw");
279     fd = fd_open(path_score, O_RDONLY);
280     if (fd < 0) {
281         safe_setuid_grab();
282         fd = fd_make(path_score, true);
283         safe_setuid_drop();
284         if (fd < 0) {
285             const auto &filename_score = path_score.string();
286             std::string why = _("'", "Cannot create the '");
287             why.append(filename_score);
288             why.append(_("'ファイルを作成できません!", "' file!"));
289             init_angband_aux(why);
290         }
291     }
292
293     (void)fd_close(fd);
294     if (!no_term) {
295         put_title();
296     }
297
298     void (*init_note)(concptr) = (no_term ? init_note_no_term : init_note_term);
299
300     init_note(_("[データの初期化中... (地形)]", "[Initializing arrays... (features)]"));
301     if (init_terrains_info()) {
302         quit(_("地形初期化不能", "Cannot initialize features"));
303     }
304
305     if (init_feat_variables()) {
306         quit(_("地形初期化不能", "Cannot initialize features"));
307     }
308
309     init_note(_("[データの初期化中... (アイテム)]", "[Initializing arrays... (objects)]"));
310     if (init_baseitems_info()) {
311         quit(_("アイテム初期化不能", "Cannot initialize objects"));
312     }
313
314     init_note(_("[データの初期化中... (伝説のアイテム)]", "[Initializing arrays... (artifacts)]"));
315     if (init_artifacts_info()) {
316         quit(_("伝説のアイテム初期化不能", "Cannot initialize artifacts"));
317     }
318
319     init_note(_("[データの初期化中... (名のあるアイテム)]", "[Initializing arrays... (ego-items)]"));
320     if (init_egos_info()) {
321         quit(_("名のあるアイテム初期化不能", "Cannot initialize ego-items"));
322     }
323
324     init_note(_("[データの初期化中... (モンスター)]", "[Initializing arrays... (monsters)]"));
325     if (init_monster_race_definitions()) {
326         quit(_("モンスター初期化不能", "Cannot initialize monsters"));
327     }
328
329     init_note(_("[データの初期化中... (ダンジョン)]", "[Initializing arrays... (dungeon)]"));
330     if (init_dungeons_info()) {
331         quit(_("ダンジョン初期化不能", "Cannot initialize dungeon"));
332     }
333
334     for (const auto &d_ref : dungeons_info) {
335         if (d_ref.idx > 0 && MonsterRace(d_ref.final_guardian).is_valid()) {
336             monraces_info[d_ref.final_guardian].flags7 |= RF7_GUARDIAN;
337         }
338     }
339
340     init_note(_("[データの初期化中... (魔法)]", "[Initializing arrays... (magic)]"));
341     if (init_class_magics_info()) {
342         quit(_("魔法初期化不能", "Cannot initialize magic"));
343     }
344
345     init_note(_("[データの初期化中... (熟練度)]", "[Initializing arrays... (skill)]"));
346     if (init_class_skills_info()) {
347         quit(_("熟練度初期化不能", "Cannot initialize skill"));
348     }
349
350     init_note(_("[配列を初期化しています... (荒野)]", "[Initializing arrays... (wilderness)]"));
351     if (!init_wilderness()) {
352         quit(_("荒野を初期化できません", "Cannot initialize wilderness"));
353     }
354
355     init_note(_("[配列を初期化しています... (街)]", "[Initializing arrays... (towns)]"));
356     init_towns();
357
358     init_note(_("[配列を初期化しています... (建物)]", "[Initializing arrays... (buildings)]"));
359     init_buildings();
360
361     init_note(_("[配列を初期化しています... (クエスト)]", "[Initializing arrays... (quests)]"));
362     QuestList::get_instance().initialize();
363     if (init_vaults_info()) {
364         quit(_("vault 初期化不能", "Cannot initialize vaults"));
365     }
366
367     init_note(_("[データの初期化中... (その他)]", "[Initializing arrays... (other)]"));
368     init_other(player_ptr);
369     init_note(_("[データの初期化中... (モンスターアロケーション)]", "[Initializing arrays... (monsters alloc)]"));
370     init_monsters_alloc();
371     init_note(_("[データの初期化中... (アイテムアロケーション)]", "[Initializing arrays... (items alloc)]"));
372     init_items_alloc();
373     init_note(_("[ユーザー設定ファイルを初期化しています...]", "[Initializing user pref files...]"));
374     process_pref_file(player_ptr, "pref.prf");
375     process_pref_file(player_ptr, std::string("pref-").append(ANGBAND_SYS).append(".prf"));
376     init_note(_("[初期化終了]", "[Initialization complete]"));
377 }